Analytics Vidhya
Published in

Analytics Vidhya

End to end deep learning-based Mobile app.

COOK SMART

COOK SMART
https://play.google.com/store/apps/details?id=com.amply.recipefinder

WHY?

HOW?

APPLICATION FLOW

WHAT?

Ingredients image classification

Ingredients Image download using google_image_download()
IMG_SHAPE = (IMAGE_SIZE, IMAGE_SIZE, 3)# Create the base model from the pre-trained model MobileNet V2
insp_base_model = tf.keras.applications.InceptionV3(input_shape=IMG_SHAPE,include_top=False,weights=’imagenet’)
Callback List
insp_model.compile(loss=’categorical_crossentropy’,optimizer=adam,metrics=[‘accuracy’])history = insp_model.fit_generator(train_generator, epochs=100, callbacks = callbacks_list, validation_data=val_generator)
The accuracy obtained with various models including my own CNN Model.
saved_model_dir = 'save/fine_tuning'
tf.saved_model.save(model, saved_model_dir)
converter=tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
tflite_model = converter.convert()
with open('model.tflite', 'wb') as f:
f.write(tflite_model)
from google.colab import files
files.download(‘model.tflite’)
files.download(‘labels.txt’)
model = tf.keras.Sequential([tf.keras.layers.Conv2D(filters = 32, kernel_size = 3, input_shape=input_shape_img, activation=’relu’, padding=’same’, ),
tf.keras.layers.MaxPooling2D(2),
tf.keras.layers.BatchNormalization(),
tf.keras.layers.Dropout(0.5),
tf.keras.layers.Conv2D(filters = 32, kernel_size = 3, activation= ‘relu’, padding=’same’, kernel_initializer = ‘he_uniform’),
tf.keras.layers.MaxPooling2D(2),
tf.keras.layers.BatchNormalization(),
tf.keras.layers.Dropout(0.5),
tf.keras.layers.Conv2D(filters = 64, kernel_size = 3, activation= ‘relu’, padding=’same’, kernel_initializer = ‘he_uniform’),
tf.keras.layers.MaxPooling2D(2),
tf.keras.layers.BatchNormalization(),
tf.keras.layers.Dropout(0.5),
tf.keras.layers.Conv2D(filters = 64, kernel_size = 3, activation= ‘relu’, padding=’same’, kernel_initializer = ‘he_uniform’),
tf.keras.layers.MaxPooling2D(2),
tf.keras.layers.BatchNormalization(),
tf.keras.layers.Dropout(0.5),
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(128, activation= ‘relu’),
tf.keras.layers.Dropout(0.5),
tf.keras.layers.Dense(len(train_generator.class_indices), activation=’softmax’)
])
model.compile(loss='categorical_crossentropy',optimizer=adam,metrics=['accuracy'])history = model.fit_generator(train_generator, epochs=100, callbacks = callbacks_list, validation_data=val_generator)#Loss plot
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']
plt.figure(figsize=(8, 8))
plt.subplot(2, 1, 1)
plt.plot(acc, label='Training Accuracy')
plt.plot(val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.ylabel('Accuracy')
plt.ylim([min(plt.ylim()),1.2])
plt.title('Training and Validation Accuracy')
plt.subplot(2, 1, 2)
plt.plot(loss, label='Training Loss')
plt.plot(val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.ylabel('Cross Entropy')
plt.ylim([0,3.0])
plt.title('Training and Validation Loss')
plt.xlabel('epoch')
plt.show()

Making REST API Endpoints

Python Class for Cleaning Recipe data
Recipe Object
Mongo Db insertion
Left (Flask API) Right (Google cloud functions for serverless api)
Sample response for fetching recipes.
// Build off of nightly TensorFlow Lite
implementation 'org.tensorflow:tensorflow-lite:0.0.0-nightly'
implementation 'org.tensorflow:tensorflow-lite-gpu:0.0.0-nightly'
implementation 'org.tensorflow:tensorflow-lite-support:0.0.0-nightly'
@Override
protected String getModelPath() {
return "model.tflite";
}

@Override
protected String getLabelPath() {
return "labels.txt";
}
private void getRecipesData(String ingredientStr) throws JSONException, UnsupportedEncodingException {
avi.show();
mTextProgress.setVisibility(View.VISIBLE);
recipesList = new ArrayList<>();
AsyncHttpClient client = new AsyncHttpClient();
// Log.v("ingr_pass", ingredientStr);

JSONObject jsonParams = new JSONObject();
jsonParams.put("ingredients", "" + ingredientStr); //{"ingredients":"chicken,onion"}
StringEntity entity = new StringEntity(jsonParams.toString());
client.setTimeout(15 * 1000); // 15 sec timeout
client.post(ListOfRecipeActivity.this, RECIPES_URL, entity, "application/json", new AsyncHttpResponseHandler() {
@Override
public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {

Gson gson = new Gson();
String jsonOutput = new String(responseBody);
if (!jsonOutput.trim().equals("[]")) {
mEditSearchRecipe.setVisibility(View.VISIBLE);
llNoData.setVisibility(View.GONE);
avi.hide();
mTextProgress.setVisibility(View.GONE);
Type listType = new TypeToken<ArrayList<Recipes>>() {
}.getType();
recipesList = gson.fromJson(jsonOutput, listType);
populateRecipes(recipesList);
System.out.println("recipes:" + jsonOutput);
ingrListStr = "";
} else {
llNoData.setVisibility(View.VISIBLE);
avi.hide();
mTextProgress.setVisibility(View.GONE);
ingrListStr = "";
mEditSearchRecipe.setVisibility(View.GONE);
}

}

@Override
public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) {
avi.hide();
mTextProgress.setVisibility(View.GONE);
ingrListStr = "";
}
});
}
private void populateRecipes(ArrayList<Recipes> recipesArrayList) {
adapter = new RecipesAdapter(this, recipesArrayList);
adapter.setClickListener(this);
mRecyclerRecipes.setAdapter(adapter);
mEditSearchRecipe.setVisibility(View.VISIBLE);
}
public class RecipesAdapter extends RecyclerView.Adapter<RecipesAdapter.ViewHolder>  {

private ArrayList<Recipes> mRecipeList;
private LayoutInflater mInflater;
private RecipeClickListener mClickListener;

private String mIngrList;

// data is passed into the constructor
public RecipesAdapter(Context context, ArrayList<Recipes> data) {
this.mInflater = LayoutInflater.from(context);
this.mRecipeList = data;
}

// inflates the cell layout from xml when needed
@Override
@NonNull
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.each_recipe_design, parent, false);
return new ViewHolder(view);
}

// binds the data to the TextView in each cell
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {

Recipes recipes = mRecipeList.get(position);

holder.mTextCount.setText(""+String.valueOf(position+1));

Random rnd = new Random();
int color = Color.argb(255, rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256));
holder.mCardViewRecipe.setCardBackgroundColor(color);

holder.mTextRecipeName.setText(""+recipes.getRecipeName().substring(0, 1).toUpperCase() + recipes.getRecipeName().substring(1).toLowerCase());
// holder.mTextIngrList.setText(mIngrList);

holder.mTextIngr1.setText(""+recipes.getRecipeIngredient().get(1).getIngr());
holder.mTextIngr2.setText(""+recipes.getRecipeIngredient().get(2).getIngr());
holder.mTextIngr3.setText(""+recipes.getRecipeIngredient().get(3).getIngr());

mIngrList = "";
if (recipes.getRecipeServings() != null) {
holder.mTextServing.setText("Servings: "+String.valueOf(recipes.getRecipeServings()));
}else {
holder.mTextServing.setText("Data Not found!");
}

if (recipes.getRecipePreptime() != null) {
holder.mTextPrepTime.setText(recipes.getRecipePreptime());
}else {
holder.mTextPrepTime.setText("Data Not found!");
}

if (recipes.getRecipeCalories() != null) {
holder.mTextCalories.setText("Calories: "+String.valueOf(recipes.getRecipeCalories())+" cals");
}else {
holder.mTextCalories.setText("Data Not found!");
}

holder.mTextIngrFirstChr.setText(""+recipes.getRecipeName().substring(0, 1).toUpperCase());

}

public void filterList(ArrayList<Recipes> filterdNames) {
this.mRecipeList = filterdNames;
notifyDataSetChanged();
}

// total number of cells
@Override
public int getItemCount() {
return mRecipeList.size();
}


// stores and recycles views as they are scrolled off screen
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView mTextRecipeName, mTextPrepTime, mTextIngrFirstChr, mTextServing, mTextCalories, mTextCount;
TextView mTextIngr1,mTextIngr2,mTextIngr3;
CardView mCardViewRecipe;

ViewHolder(View itemView) {
super(itemView);
mTextRecipeName = itemView.findViewById(R.id.tv_recipe_name);
mTextIngrFirstChr = itemView.findViewById(R.id.tv_ingr_name);
// mTextIngrList = itemView.findViewById(R.id.tv_recipe_ingr_list);
mTextServing = itemView.findViewById(R.id.tv_recipe_ingr_serv);
mTextPrepTime = itemView.findViewById(R.id.tv_recipe_prep_time);
mTextCalories = itemView.findViewById(R.id.tv_recipe_calories);
mTextCount = itemView.findViewById(R.id.tv_count);
mTextIngr1 = itemView.findViewById(R.id.tv_ingr_1);
mTextIngr2 = itemView.findViewById(R.id.tv_ingr_2);
mTextIngr3 = itemView.findViewById(R.id.tv_ingr_3);

mCardViewRecipe = itemView.findViewById(R.id.card_view_recipe);
itemView.setOnClickListener(this);
}

@Override
public void onClick(View view) {
if (mClickListener != null) mClickListener.onRecipeClick(view, getAdapterPosition());
}
}

// convenience method for getting data at click position
Recipes getItem(int id) {
return mRecipeList.get(id);
}

// allows clicks events to be caught
public void setClickListener(RecipeClickListener itemClickListener) {
this.mClickListener = itemClickListener;
}

// parent activity will implement this method to respond to click events
public interface RecipeClickListener {
void onRecipeClick(View view, int position);
}

public void clear() {
int size = mRecipeList.size();
mRecipeList.clear();
notifyItemRangeRemoved(0, size);
}

}

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Tapan Kumar Patro

📚 Machine learning | 🤖 Deep Learning | 👀 Computer vision | 🗣 Natural Language processing | 👂 Audio Data | 🖥 End to End Software Development | 🖌