Creado por: José Francisco Núñez Obando y María Laura Pizarro
El siguiente documento corresponde con una evaluación del porcentaje de precisión en la identificación de especies de plantas mediante el uso de la aplicación Plantnet.
Para cumplir con este objetivo se descargan una serie de fotografías desde los sitios GBIF y EOL usando sus api's respectivas, las cuales posteriormente se utilizan como objetos de prueba para la identificación de especies en la aplicación de PlantNet que se encuentra en linea, siendo esta la misma que se encuentra en formato de app para android e ios. Al respecto de la puesta a prueba de dicho servicio de identificación, se planteo el uso de la técnica de webscrapping utilizando para ello el webdriver de 'chrome' y la librería de Python llamada Selenium, además de otras librerías que complementan el análisis.
En los siguientes apartados se describen los procedimientos y se anotan las partes de código utilizadas para llavar a cabo cada proceso.
A continuación, algunas de las librerías python utilizadas inicialmente para completar con los primeros procedimientos:
import requests
import urllib.request
from lxml import html, etree
import json
from pandas.io.json import json_normalize
#import squarify
import warnings
warnings.filterwarnings('ignore')
import time
import pandas as pd
import numpy as np
import glob
import os
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
from bs4 import BeautifulSoup
from fuzzywuzzy import fuzz
En esta sección se procede a realizar la consulta a la API de GBIF en la que únicamente se establecen como parámetros el país y el reino.
parameters = {"country": "CR",
"limit":300,
"scientificname" : "Plantae"
}
url = "https://api.gbif.org/v1/occurrence/search?"
q_gbif = requests.request(method="get", url=url, params=parameters)
j_gbif = json.loads(q_gbif.text)
L = json_normalize(j_gbif['results'])
L = L[['kingdom', 'phylum','order', 'family', 'genus', 'species', 'scientificName']]
Una vez se obtienen los resultados de la consulta, se procedio a realizar una selección de 20 muestras aleatorias del total.
L2 = L.sample(n=20, random_state=1)
L2
Con el fin de obtener una cantidad de 3 fotos de cada una especie de la muestra, se realiza una consulta a la API de EOL desde la que se obtiene una referencia a una subpagina de la especie correspondiente. En dicha subpágina se consulta de forma automatizada la sección de media en la que se encuentran las imágenes por especie, por lo que para una descarga de las misma se emplea una técnica de web scrapping. Al final se obtiene un dataframe que contiene el nombre de especie y una URL de las fotografías asociadas a esta.
def eol (data):
L3=data.groupby(['species'])['species'].head(1).reset_index(drop=True)
url_eol = "http://eol.org/api/search/1.0.json?"
df = pd.DataFrame(columns=('specie', 'url'))
species_count=0
rows_list = []
for i in L3:
#EOL DATA
parameters = {"q": i}
q_eol = requests.request(method="get", url=url_eol, params=parameters)
j_eol = json.loads(q_eol.text)
#Se filtran los datos que tengan mas de 3 resultados
jd_eol = json_normalize(j_eol['results'])
rows_temp = []
#Se recorren los links de los resultados y se evalua si el link devuelve informacion
for link in j_eol['results']:
r = requests.get(link['link']+'/media')
html = r.text
soup = BeautifulSoup(html, 'lxml')
links = soup.find_all('div', {'class': 'js-grid-modal-toggle uk-card-media-top uk-inline-clip uk-transition-toggle'})
for y in links:
dict1 = {}
dict1.update({'Specie':i, 'Url':y.find('img')['src']})
rows_temp.append(dict1)
if(len(rows_temp) == 3):
species_count+=1
rows_list += rows_temp
break
if(len(rows_temp) == 3):
break
#Se detiene el ciclo cuando ya se hayan obtenido 3 imagenes de 20 especies
if (species_count == 21):
break
df_eol = pd.DataFrame(rows_list)
return df_eol
La anterior función se aplica la lista de especies obtenida de GBIF y se imprime para conocer los resultados.
df_eol0 = eol(L2)
df_eol0.head()
Con el fin de conocer cuantas de las especies no fue posible obtener datos desde EOL se realiza la siguiente operación.
print('La cantidad de especies sin foto son: ',20-((df_eol0.Specie.value_counts().sum())/3))
Con el dato de la cantidad faltantes, se aplica la extracción de 3 especies del total del dataframe obtenido de GBIF.
L2_1 = L.sample(3, replace=True)
L2_1
Y con ello, se realiza el checkeo de especies entre listas para ver que las últimas no esten incluidas en la primera lista a la que se le aplico la función EOL.
spec1 = list(df_eol0['Specie'].unique())
spec2 = list(L2_1.species.unique())
check = any(item in spec2 for item in spec1)
if check is True:
print("La lista si contiene algunas especies de la primera lista")
else :
print("Las listas no contiene especies iguales.")
El resultado anterior nos da una luz verde para realizar la busqueda de fotografías en EOL para la segunda lista.
df_eol1 = eol(L2_1)
df_eol1
Con el resultado anterior se procede a la unión entre el dataframe 1 y 2 de la obtención desde EOL.
df_eol_f = df_eol0.append(df_eol1)
print('La cantidad de fotos total por especies es: ', df_eol_f['Url'].value_counts().sum())
El resultado mostrado nos indica que se han conseguido la cantidad de 60 fotografías en total para las 20 especies.
De modo tal que se procede a la descarga de estas para poderlas analizar en la plataforma de identificación de especies de PlantNet.
count = 0
for name, url in zip(df_eol_f['Specie'], df_eol_f['Url']):
n = os.path.exists("./imagenes/"+name+'.jpg')
if n:
count +=1
if (count < 3):
urllib.request.urlretrieve(str(url), "./imagenes/"+name+'_'+str(count)+".jpg")
else:
count = 0
urllib.request.urlretrieve(str(url), "./imagenes/"+name+".jpg")
file_list = glob.glob(os.path.join(os.getcwd(), "./imagenes/", "*.jpg"))
print("La cantidad de imagenes descargadas son: ",len(file_list))
A través del siguiente Link se pueden visualizar las fotografias: fotos de especies
En las siguientes líneas de código se realizan una serie de procesos automatizados que corresponden con la técnica de webscrapping los cuales corresponden con ejecución del webdriver en cuyo caso abre el GoogleChrome, posteriormente se busca el sitio web de plantnet que corresponde con la app de identificación y, en este sitio se utiliza el boton de subida de imagenes para su respectiva identificación, con esto se obtiene un resultado del cual únicamente se copia la información de la primera fila, siendo este el resultado top de la lista, y con el que se crea un dataframe que incluye como columnas el nombre del archivo evaluado, la nombre científico, la familia, el nombre común y el porcentaje de precisión que se obtuvo con la identificación que realiza la app.
A continuación, una lista de las librerías utilizadas en esta seccion:
#URL de descarga del webdriver: https://chromedriver.chromium.org/
#Se cargan las librerias respectivas
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.common.exceptions import TimeoutException
options = webdriver.ChromeOptions()
options.add_argument('--headless')
options.add_argument('--no-sandbox')
options.add_argument('--disable-dev-shm-usage')
wd = webdriver.Chrome(options=options)
#Se ejecuta el webdriver para chrome
wd = webdriver.Chrome(executable_path="chromedriver.exe")
wd.implicitly_wait(0.5)
wd.maximize_window()
#Se especifica la pagina web
wd.get("https://identify.plantnet.org/")
#Identificar el elemento donde cargar la foto
s = wd.find_element_by_xpath("//input[@type='file']")
#Se crea un dataset con los datos obtenidos
fname = list()
nsc = list()
fam = list()
ncom = list()
prec = list()
# Procedimiento para enviar las fotografias a la app y del resultado arrojado se agrega a listas los diversos datos
for i in range(0, len(file_list)):
s.send_keys(str(file_list[i]))
fname.append(os.path.basename(file_list[i]))
try:
text_plant = WebDriverWait(wd, 30)\
.until(EC.visibility_of_element_located((By.XPATH, '/html/body/div[2]/div[1]/div/div/div/div[1]/div[1]/div')))
except TimeoutException:
nsc.append("ND")
fam.append("ND")
ncom.append("ND")
prec.append("0%")
continue
text_plant = text_plant.text
text_plant = text_plant.split('\n')
nsc.append(text_plant[0])
fam.append(text_plant[1])
if(len(text_plant) == 4):
ncom.append(text_plant[2])
else:
ncom.append("ND")
prec.append(text_plant[-1])
print("Termino la identificacion de fotos en la app de PlantNet")
wd.quit()
A partir del procedimiento anterior se construye una tabla en la que se agregan los nombres de las fotografías evaluadas, las cuales poseen como nombre aquel extraido de la consulta a GBIF y EOL, además de algunos de los resultados obtenidos con la evaluación realizada en PlantNet, dentro de los que figuran: el nombre científico, Familia, Nombre común y Precisión top obtenida con la identificación de la especie. A esta tabla se agregan otras columnas que corresponden con Ratio y Similitud, las cuales respectivamente corresponden con un cálculo de comparación entre el nombre de la fotografía y el nombre científico dado por PlantNet y, a partir de este, una columna que asigna un 1 a la mayor similitud obtenida en la comparación y a la de menor un 0 y para ello se establecio como 70 el límite Ratio entre la comparación, esto después de evaluar en promedio los diferentes rangos del ratio así como la comparación.
df = pd.DataFrame({'Foto': fname,'Nombre_cientifico': nsc, 'Familia': fam, 'Nombre_comun': ncom, 'Precision':prec})
df['Precision'] = [float(v.replace('%','').strip()) for v in df.Precision]
df['Ratio'] = df.apply(lambda x: fuzz.partial_ratio(x['Foto'], x['Nombre_cientifico']), axis=1)
df['Similitud'] = np.where(df['Ratio'] >= 70, 1, 0)
La tabla resultante es la siguiente:
df
Por último, con el fin de evaluar en términos globales la precisión que se obtuvo con la puesta a prueba de esta app, se aplican estadísticas descriptivas a las columnas 'Precision','Ratio' y 'Similitud', las cuales se presentan a continuación:
df['Precision'].describe()
df['Ratio'].describe()
df['Similitud'].value_counts()
De estas estadísticas se puede observar que se evaluaron 60 fotografías de 20 especies diferentes en las cuales se obtuvo una precision promedio de 26%, siendo 0% la precision mínima y 98% la máxima, tal y como es es posible apreciar en la tabla de resultados. Respecto al ratio en el que se comparan los nombres científicos de la fuente y el resultado de la identificación se tiene un promedio de 54%, siendo el mínimo logrado en 0% y 89% el máximo. Por último, respecto de lo que se podría llamar peso de similitud entre fuente y resultados se logró una cantidad de 26 fotografías con identificación precisa o casi precisa y 34 fotografías en las que no se logró la optima identificación, de modo tal que la evaluación obtenida muestra cantidades similares entre los aciertos y desaciertos, siendo este último el que obtuvo una mayor cantidad desafortunadamente.
Por tanto, se puede concluir que, aunque la herramienta de PlantNet posee un gran potencial en la identificación de especies, aun falta por mejorar en cuanto a su grado de certeza, aunque es importante reconocer que también la calidad de las fotografías para la evaluación no siempre es la más adecuada y por ende pueden desfavorecer la funcionalidad de la app aquí evaluada, por lo que sería importante destacar esto al inicio del uso de la app.