# Import the libraries import numpy as np import pandas as pd from tensorflow.keras.models import load_model from tensorflow.keras.preprocessing.image import load_img, img_to_array from tensorflow.keras.applications.convnext import preprocess_input import gradio as gr # Load the model model = load_model('models/TropiCam-AI_ConvNeXtBase') # Load the taxonomy .csv taxo_df = pd.read_csv('taxonomy/taxonomy_mapping.csv') taxo_df['species'] = taxo_df['species'].str.replace('_', ' ') # Available taxonomic levels for prediction taxonomic_levels = ['species', 'genus', 'family', 'order', 'class'] # Function to map predicted class index to class name at the selected taxonomic level def get_class_name(predicted_class, taxonomic_level): unique_labels = sorted(taxo_df[taxonomic_level].unique()) return unique_labels[predicted_class] # Function to aggregate predictions to a higher taxonomic level def aggregate_predictions(predicted_probs, taxonomic_level, class_names): unique_labels = sorted(taxo_df[taxonomic_level].unique()) aggregated_predictions = np.zeros((predicted_probs.shape[0], len(unique_labels))) for idx, row in taxo_df.iterrows(): species = row['species'] higher_level = row[taxonomic_level] species_index = class_names.index(species) # Index of the species in the prediction array higher_level_index = unique_labels.index(higher_level) aggregated_predictions[:, higher_level_index] += predicted_probs[:, species_index] return aggregated_predictions, unique_labels # Function to load and preprocess the image def load_and_preprocess_image(image, target_size=(224, 224)): # Resize the image img_array = img_to_array(image.resize(target_size)) # Expand the dimensions to match model input img_array = np.expand_dims(img_array, axis=0) # Preprocess the image img_array = preprocess_input(img_array) return img_array # Function to make predictions def make_prediction(image, taxonomic_decision, taxonomic_level): # Preprocess the image img_array = load_and_preprocess_image(image) # Get the class names from the 'species' column class_names = sorted(taxo_df['species'].unique()) # Make a prediction prediction = model.predict(img_array) # Initialize variables for aggregated predictions and level index aggregated_predictions = None current_level_index = 0 # Start from the species level # Determine the initial taxonomic level based on the user's decision if taxonomic_decision == "No, I will let the model decide": current_level_index = 0 # Start at species level if letting the model decide else: current_level_index = taxonomic_levels.index(taxonomic_level) # Use specified level # Aggregate predictions based on the current taxonomic level aggregated_predictions, aggregated_class_labels = aggregate_predictions(prediction, taxonomic_levels[current_level_index], class_names) # If the user specified a taxonomic level, simply get the highest prediction at that level if taxonomic_decision == "Yes, I want to specify the taxonomic level": # Get the predicted class index for the current level predicted_class_index = np.argmax(aggregated_predictions) predicted_class_name = aggregated_class_labels[predicted_class_index] # Check if common name should be displayed (only at species level) if taxonomic_levels[current_level_index] == "species": predicted_common_name = taxo_df[taxo_df[taxonomic_levels[current_level_index]] == predicted_class_name]['common_name'].values[0] output_text = f"

{predicted_class_name} ({predicted_common_name})

" else: output_text = f"

{predicted_class_name}

" # Add the top 5 predictions output_text += "

Top-5 predictions:

" top_indices = np.argsort(aggregated_predictions[0])[-5:][::-1] # Get top 5 predictions for i in top_indices: class_name = aggregated_class_labels[i] confidence_percentage = aggregated_predictions[0][i] * 100 output_text += f"
" \ f"{class_name}" \ f"{confidence_percentage:.2f}%
" return output_text # Confidence checking for the automatic model decision # Loop through taxonomic levels if the user lets the model decide while current_level_index < len(taxonomic_levels): # Aggregate predictions for the next level aggregated_predictions, aggregated_class_labels = aggregate_predictions(prediction, taxonomic_levels[current_level_index], class_names) # Check if the confidence of the top prediction meets the threshold top_prediction_index = np.argmax(aggregated_predictions) top_prediction_confidence = aggregated_predictions[0][top_prediction_index] if top_prediction_confidence >= 0.75: break # Confidence threshold met, exit loop current_level_index += 1 # Move to the next taxonomic level # Check if a valid prediction was made if current_level_index == len(taxonomic_levels): return "

Unknown animal

" # No valid predictions met the confidence criteria # Get the predicted class name for the top prediction predicted_class_index = np.argmax(aggregated_predictions) predicted_class_name = aggregated_class_labels[predicted_class_index] # Check if common name should be displayed (only at species level) if taxonomic_levels[current_level_index] == "species": predicted_common_name = taxo_df[taxo_df[taxonomic_levels[current_level_index]] == predicted_class_name]['common_name'].values[0] output_text = f"

{predicted_class_name} ({predicted_common_name})

" else: output_text = f"

{predicted_class_name}

" # Add the top 5 predictions output_text += "

Top-5 predictions:

" top_indices = np.argsort(aggregated_predictions[0])[-5:][::-1] # Get top 5 predictions for i in top_indices: class_name = aggregated_class_labels[i] if taxonomic_levels[current_level_index] == "species": # Display common names only at species level and make it italic common_name = taxo_df[taxo_df[taxonomic_levels[current_level_index]] == class_name]['common_name'].values[0] confidence_percentage = aggregated_predictions[0][i] * 100 output_text += f"
" \ f"{class_name} ({common_name})" \ f"{confidence_percentage:.2f}%
" else: # No common names at higher taxonomic levels confidence_percentage = aggregated_predictions[0][i] * 100 output_text += f"
" \ f"{class_name}" \ f"{confidence_percentage:.2f}%
" return output_text # Confidence checking for the automatic model decision # Loop through taxonomic levels if the user lets the model decide while current_level_index < len(taxonomic_levels): # Aggregate predictions for the next level aggregated_predictions, aggregated_class_labels = aggregate_predictions(prediction, taxonomic_levels[current_level_index], class_names) # Check if the confidence of the top prediction meets the threshold top_prediction_index = np.argmax(aggregated_predictions) top_prediction_confidence = aggregated_predictions[0][top_prediction_index] if top_prediction_confidence >= 0.75: break # Confidence threshold met, exit loop current_level_index += 1 # Move to the next taxonomic level # Check if a valid prediction was made if current_level_index == len(taxonomic_levels): return "

Unknown animal

" # No valid predictions met the confidence criteria # Get the predicted class name for the top prediction predicted_class_index = np.argmax(aggregated_predictions) predicted_class_name = aggregated_class_labels[predicted_class_index] # Check if common name should be displayed (only at species level) if taxonomic_levels[current_level_index] == "species": predicted_common_name = taxo_df[taxo_df[taxonomic_levels[current_level_index]] == predicted_class_name]['common_name'].values[0] output_text = f"

{predicted_class_name} ({predicted_common_name})

" else: output_text = f"

{predicted_class_name}

" # Add the top-5 predictions output_text += "

Top-5 predictions:

" top_indices = np.argsort(aggregated_predictions[0])[-5:][::-1] # Get top 5 predictions for i in top_indices: class_name = aggregated_class_labels[i] if taxonomic_levels[current_level_index] == "species": # Display common names only at species level and make it italic common_name = taxo_df[taxo_df[taxonomic_levels[current_level_index]] == class_name]['common_name'].values[0] confidence_percentage = aggregated_predictions[0][i] * 100 output_text += f"
" \ f"{class_name} ({common_name})" \ f"{confidence_percentage:.2f}%
" else: # No common names at higher taxonomic levels confidence_percentage = aggregated_predictions[0][i] * 100 output_text += f"
" \ f"{class_name}" \ f"{confidence_percentage:.2f}%
" return output_text # Define the Gradio interface interface = gr.Interface( fn=make_prediction, # Function to be called for predictions inputs=[ gr.Image(type="pil", label="Upload Image"), # Input type: Image (PIL format) gr.Radio(choices=["Yes, I want to specify the taxonomic level", "No, I will let the model decide"], label="Do you want to specify the taxonomic resolution for predictions? If you select 'No', the 'Taxonomic level' drop-down menu will be bypassed.", value="No, I will let the model decide"), # Radio button for taxonomic resolution choice gr.Dropdown(choices=taxonomic_levels, label="Taxonomic level:", value="species") # Dropdown for taxonomic level ], outputs="html", # Output type: HTML for formatting title="Neotropical arboreal species classification", description="Upload an image and our AI will classify the animal. NOTE: it's best not to feed the whole image but just the cropped animal (in the final model this will be done automatically)." ) # Launch the Gradio interface with authentication for the specified users interface.launch()