File size: 11,913 Bytes
9f198ef
 
 
 
 
 
 
 
 
cbaee11
9f198ef
 
 
 
 
52b259c
9f198ef
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d33d3aa
 
 
 
9f198ef
 
 
 
 
 
 
 
 
 
 
 
 
 
ad3d1fe
c80713a
 
a8713e0
c80713a
 
a8713e0
c80713a
 
a8713e0
c80713a
 
 
a8713e0
c80713a
 
 
 
 
 
a8713e0
 
 
8500d79
a8713e0
8500d79
 
 
 
4d59f1b
 
 
 
 
 
8500d79
 
 
 
 
 
 
 
 
 
 
 
 
 
a8713e0
8500d79
 
c80713a
4d59f1b
 
 
 
 
 
 
dca3a9e
4d59f1b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8500d79
 
 
 
 
 
 
32b40a7
c80713a
a8713e0
c80713a
e014372
22a6037
e493bfa
 
8500d79
c80713a
 
 
8500d79
c80713a
 
 
 
 
 
 
955877e
098b334
c80713a
 
e014372
c80713a
 
e014372
c80713a
 
 
 
 
 
 
 
 
 
 
 
 
d33d3aa
c80713a
9f198ef
1364165
 
e93a56e
 
 
 
96a0181
e93a56e
68bd012
e93a56e
 
03f90f2
bbe97e2
1364165
f147a22
 
72cd99a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# 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"<h1 style='font-weight: bold;'><span style='font-style: italic;'>{predicted_class_name}</span> ({predicted_common_name})</h1>"
        else:
            output_text = f"<h1 style='font-weight: bold;'>{predicted_class_name}</h1>"
        
        # Add the top 5 predictions
        output_text += "<h4 style='font-weight: bold; font-size: 1.2em;'>Top-5 predictions:</h4>"

        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"<div style='display: flex; justify-content: space-between;'>" \
                           f"<span>{class_name}</span>" \
                           f"<span style='margin-left: auto;'>{confidence_percentage:.2f}%</span></div>"

        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 "<h1 style='font-weight: bold;'>Unknown animal</h1>"  # 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"<h1 style='font-weight: bold;'><span style='font-style: italic;'>{predicted_class_name}</span> ({predicted_common_name})</h1>"
    else:
        output_text = f"<h1 style='font-weight: bold;'>{predicted_class_name}</h1>"

    # Add the top 5 predictions
    output_text += "<h4 style='font-weight: bold; font-size: 1.2em;'>Top-5 predictions:</h4>"
    
    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"<div style='display: flex; justify-content: space-between;'>" \
                           f"<span style='font-style: italic;'>{class_name}</span>&nbsp;(<span>{common_name}</span>)" \
                           f"<span style='margin-left: auto;'>{confidence_percentage:.2f}%</span></div>"
        else:
            # No common names at higher taxonomic levels
            confidence_percentage = aggregated_predictions[0][i] * 100
            output_text += f"<div style='display: flex; justify-content: space-between;'>" \
                           f"<span>{class_name}</span>" \
                           f"<span style='margin-left: auto;'>{confidence_percentage:.2f}%</span></div>"
    
    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 "<h1 style='font-weight: bold;'>Unknown animal</h1>"  # 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"<h1 style='font-weight: bold;'><span style='font-style: italic;'>{predicted_class_name}</span> ({predicted_common_name})</h1>"
    else:
        output_text = f"<h1 style='font-weight: bold;'>{predicted_class_name}</h1>"

    # Add the top-5 predictions
    output_text += "<h4 style='font-weight: bold; font-size: 1.2em;'>Top-5 predictions:</h4>"
    
    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"<div style='display: flex; justify-content: space-between;'>" \
                           f"<span style='font-style: italic;'>{class_name}</span>&nbsp;(<span>{common_name}</span>)" \
                           f"<span style='margin-left: auto;'>{confidence_percentage:.2f}%</span></div>"
        else:
            # No common names at higher taxonomic levels
            confidence_percentage = aggregated_predictions[0][i] * 100
            output_text += f"<div style='display: flex; justify-content: space-between;'>" \
                           f"<span>{class_name}</span>" \
                           f"<span style='margin-left: auto;'>{confidence_percentage:.2f}%</span></div>"
    
    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()