AnkIdentification/main.py

420 lines
12 KiB
Python

#!/usr/bin/python3.10
# -*- coding: utf-8 -*-
"""************************
AnkIdentification - A project to improve your plant identifcation skills through Anki.
Aurélien VALENTIN - from 06/11/2022 to 29/12/2022
************************"""
# Default parameters, feel free to change them as you want.
NB_SPECIES = (
50 # Number of species wanted by the user. Can't exceed 100 with the GBIF method.
)
LANGUAGE = "fr" # Please choose "fr" or "en" to get respectively French or English vernacular names.
SPECIE_LIST_PATH_IF_GBIF_METHOD = "" # A csv file from https://identify.plantnet.org/prediction. Only if you've chosen the GBIF method.
MY_OWN_LIST_PATH = "" # A txt file with one specie by line. Be careful to write the scientific name used in Pl@ntNet and change NB_SPECIES if needed.
MAX_NB_OF_LINKS_BY_TYPE_AND_BY_SPECIES = 100 # The code will accumulate links of pictures of leaves, flower, fruits and barks of eache species.
# The most photos you want, the most diversified your learning will be, but the longer it will take to load...
# Importation and other initialisations
import json, sys
import genanki # https://github.com/kerrickstaley/genanki.git
from random import shuffle
dict_common_plants = {}
with open("_dict_species.json", "r") as fpi:
dict_common_names = json.load(fpi) # From the 3-1 and 3-2 supplementary codes
# Definition of the progressbar function, from https://gist.github.com/ChesterChowWOV/2b35c551b339adbf459363322aac5b4b
def progressbar(it, prefix="", size=60, file=sys.stdout):
count = len(it)
def show(j):
x = int(size * j / count)
file.write(
"{}[{}{}] {}/{} {}%\r".format(
prefix, "" * x, "." * (size - x), j, count, round(j / count * 100, 2)
)
)
file.flush()
show(0)
for i, item in enumerate(it):
yield item
show(i + 1)
file.write("\n")
file.flush()
"""************************
Step 1 : Listing the most common species (if without a manual list or a GBIF file).
************************"""
if SPECIE_LIST_PATH_IF_GBIF_METHOD == MY_OWN_LIST_PATH:
print("Listing the most common species")
# Loading of the whole list
with open("_dict_species_by_rarety.json") as fpi:
dict_plants_sorted = json.load(fpi) # From the first supplementary code
# Writing of the user's list
with open("Selected_species.txt", "w") as fpo:
for species in list(dict_plants_sorted.keys())[:NB_SPECIES]:
fpo.write(species + "\n")
"""************************
Step 2 : Parsing data for each species.
************************"""
# Initialisation of the dictionary (and addition of the family) from a file from the step 1...
if SPECIE_LIST_PATH_IF_GBIF_METHOD == "":
with open("_dict_classification.json", "r") as fpi:
dict_classification = json.load(fpi) # From the second supplementary code
try:
fpi = open(MY_OWN_LIST_PATH)
except:
fpi = open("Selected_species.txt")
for line in progressbar(fpi.readlines(), "Adding families", 40):
specie = line.split(" ")[0] + " " + line.split(" ")[1].lower().strip()
dict_common_plants[specie] = {
"Occurrence": {"leaf": 0, "flower": 0, "fruit": 0, "bark": 0}
}
try:
dict_common_plants[specie]["Family"] = dict_classification[specie]
except:
dict_common_plants[specie]["Family"] = "Family not found"
dict_common_plants[specie]["Links"] = {
"leaf": "",
"flower": "",
"fruit": "",
"bark": "",
}
dict_common_plants[specie]["Authors"] = {
"leaf": "",
"flower": "",
"fruit": "",
"bark": "",
}
# ...or from a GBIF file from here : https://identify.plantnet.org/prediction
else:
with open(SPECIE_LIST_PATH_IF_GBIF_METHOD) as fpi:
fpi.readline()
for line in progressbar(fpi.readlines(), "Adding families", 40):
specie = (
line.split(",")[1].split(" ")[0]
+ " "
+ line.split(",")[1].split(" ")[1]
)
dict_common_plants[specie] = {
"Occurrence": {"leaf": 0, "flower": 0, "fruit": 0, "bark": 0}
}
dict_common_plants[specie]["Family"] = line.split(",")[2]
dict_common_plants[specie]["Links"] = {
"leaf": "",
"flower": "",
"fruit": "",
"bark": "",
}
dict_common_plants[specie]["Authors"] = {
"leaf": "",
"flower": "",
"fruit": "",
"bark": "",
}
# Addition of vernacular name(s)
for specie in progressbar(dict_common_plants.keys(), "Adding common names", 40):
try:
dict_common_plants[specie]["Common names"] = dict_common_names[specie][LANGUAGE]
except:
dict_common_plants[specie]["Common names"] = "No common name found."
# Addition of photo links and author names
with open("multimedia.txt", encoding="utf-8") as fpi:
fpi.readline()
for line in progressbar(fpi.readlines(), "Adding links and common names", 40):
line_split = line.split("\t")
specie = line_split[5].split(" ")[0] + " " + line_split[5].split(" ")[1]
link = line_split[3].strip(" ").split("o/")[1]
type = line_split[5].split(":")[1].strip(" ")
author = line_split[len(line_split) - 1].strip()
if specie in dict_common_plants.keys() and type in [
"leaf",
"flower",
"fruit",
"bark",
]:
if dict_common_plants[specie]["Occurrence"][type] == 0:
dict_common_plants[specie]["Links"][type] += link
dict_common_plants[specie]["Authors"][type] += author
dict_common_plants[specie]["Occurrence"][type] += 1
elif (
dict_common_plants[specie]["Occurrence"][type]
< MAX_NB_OF_LINKS_BY_TYPE_AND_BY_SPECIES + 1
):
dict_common_plants[specie]["Links"][type] += "," + link
dict_common_plants[specie]["Authors"][type] += "," + author
dict_common_plants[specie]["Occurrence"][type] += 1
"""************************
Step 3 : Generation of the Anki deck.
************************"""
print("Preparing the Anki deck")
# Definition of the Anki card model
my_model = genanki.Model(
1091735104,
"Model for AnkIdentification",
fields=[
{"name": "Name"},
{"name": "Common names"},
{"name": "Family"},
{"name": "Leaf"},
{"name": "Flower"},
{"name": "Fruit"},
{"name": "Bark"},
{"name": "Leaf authors"},
{"name": "Flower authors"},
{"name": "Fruit authors"},
{"name": "Bark authors"},
],
templates=[
{
"name": "Card 1",
"qfmt": """<style>
.card {
font-family: arial;
font-size: 20px;
text-align: center;
color: black;
background-color: white;
}
body{
margin:0;
}
input{
position:absolute;top:0;left:0;
}
#fullscreen{
position:fixed;
top:0;bottom:0;
left:0;right:0;
background-color:rgba(0,0,0,0.7);
display:none;
}
#fImg{
position:absolute;
top:0;
bottom:0;left:0;right:0;
margin:auto;
max-width:100%;
max-height:100%;
}
#container{
display: flex;
width: 100vw;
height: 98vh;
padding: 1vh 0 1vh 0vh;
flex-wrap: wrap;
background-color:black;
}
td{
border: 1px solid;
}
#container>img {
width: 49%;
height:49%;
padding:1px;
object-fit: contain;
}
.half{
display: block;
height: 35vh;
}
</style>
<input type="button" id="qButton" value="" onclick="qSwitch()">
<div id="fullscreen"><img id="fImg"/></div>
<div id=container></div>
<script>
//once
if(typeof o=='undefined'){
var o={};
var quality=3;
var prevName='';
var isRecto=true;
var qTab=["s","m","o","a"];
var qTab2=["150x150","600x600","Original","Automatic"];
var lastRelease=0;
var lastPress=0;
//Quality switcher
function qSwitch(){
quality=(quality+1)%4;
for (const k in o) {
chgImg(o[k],0);
qButton.value=qTab2[quality];
}
}
//fullscreen
function fullscreenImg(img){
fImg.src='https://bs.plantnet.org/image/o/'+img.t[img.i];
fImg.title="Author :\\n"+img.a[img.i];
fullscreen.style.display='block';
}
function deFullscreenImg(){
fImg.src="";
fullscreen.style.display='none';
}
//click handling
function mDown(e){
setTimeout(function(){
if(Date.now()-lastRelease>=500){
chgImg(e.target,-1);
}
},500);
lastPress=Date.now();
}
function mUp(e){
if(e.button==0){
if(Date.now()-lastPress<500){
chgImg(e.target,1);
}
}else if(e.button==2){
if(Date.now()-lastPress<500){
fullscreenImg(e.target);
}
}
lastRelease=Date.now();
}
function chgImg(img,n){
img.i=(img.i+n)%img.t.length;
if(img.i<0){img.i=img.t.length-1;}
img.title="Author :\\n"+img.a[img.i];
if(qTab[quality]!='a'){
img.srcset='';
img.src='https://bs.plantnet.org/image/'+qTab[quality]+'/'+img.t[img.i];
}else{
img.srcset='https://bs.plantnet.org/image/s/'+img.t[img.i]+' 600w,'+'https://bs.plantnet.org/image/m/'+img.t[img.i]+' 2400w,'+'https://bs.plantnet.org/image/o/'+img.t[img.i]+' 4000w';
}
if(img.complete==true){
img.style.opacity=1;
}else{
img.style.opacity=0.5;
}
}
function imgLoad(e){
e.target.style.opacity=1;
}
//main show
function prepImg(list,author,id){
if(isRecto){
img=document.createElement('img');
img.t=list.replace(/<[^>]*>?/gm,'').split(',');
img.a=author.replace(/<[^>]*>?/gm,'').split(',');
img.i=Math.floor(Math.random()*img.t.length);
chgImg(img,0);
img.addEventListener('mousedown',mDown);
img.addEventListener('mouseup',mUp);
img.addEventListener('contextmenu',function(e){e.preventDefault();});
img.addEventListener('load',imgLoad);
o[id]=img;
}
container.appendChild(o[id]);
}
}
//init objects (mist be re-done because recto/verso reload)
var fImg=document.getElementById('fImg');
var fullscreen=document.getElementById('fullscreen');
var qButton=document.getElementById('qButton');
var container=document.getElementById('container');
fullscreen.addEventListener('click',deFullscreenImg);
//recto or verso ?
if(prevName=='{{Name}}'){
isRecto=!isRecto;
}else{
isRecto=true;
}
prevName="{{Name}}";
if(isRecto){
qButton.value=qTab2[quality];
}else{
container.style.height="55vh";
qButton.style.display='none';
}
prepImg("{{Leaf}}","{{Leaf authors}}",'Leaf');
prepImg("{{Flower}}","{{Flower authors}}",'Flower');
prepImg("{{Fruit}}","{{Fruit authors}}",'Fruit');
prepImg("{{Bark}}","{{Bark authors}}",'Bark');
</script>""",
"afmt": """<div class="half">
<h1><i>{{Name}}</i></h1>
<br>
<b><i>{{Family}}</i></b>
<br>
{{Common names}}
</div>
{{FrontSide}}""",
"bfont": "Arial"
#'Styles': ".card {\n font-family: arial;\n font-size: 20px;\n text-align: center;\n color: black;\n background-color: white;\n}"
},
],
css=".card {\n font-family: arial;\n font-size: 20px;\n text-align: center;\n color: black;\n background-color: white;\n}\n",
)
# Creation of the deck
my_deck = genanki.Deck(2059400110, "AnkIdentifaction")
# Randomization of the species order
species_shuffled = list(dict_common_plants.keys())
shuffle(species_shuffled)
# Generation of all notes
for specie in progressbar(species_shuffled, "Creating notes", 40):
dict_specie = dict_common_plants[specie]
my_note = genanki.Note(
model=my_model,
fields=[
specie,
dict_specie["Common names"],
dict_specie["Family"],
dict_specie["Links"]["leaf"],
dict_specie["Links"]["flower"],
dict_specie["Links"]["fruit"],
dict_specie["Links"]["bark"],
dict_specie["Authors"]["leaf"],
dict_specie["Authors"]["flower"],
dict_specie["Authors"]["fruit"],
dict_specie["Authors"]["bark"],
],
tags=["AnkIdentification::" + dict_specie["Family"]],
)
my_deck.add_note(my_note)
# Creation of the final file
print("Almost done!")
genanki.Package(my_deck).write_to_file("AnkIdentification.apkg")
print("Enjoy ^^")