2022-12-29 18:48:05 +01:00
|
|
|
#!/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 = 100 # 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.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 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]
|
2022-12-31 11:41:45 +01:00
|
|
|
except: dict_common_plants[specie]["Family"] = "Family_not_found"
|
2022-12-29 18:48:05 +01:00
|
|
|
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] < 101:
|
|
|
|
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 ^^")
|