From 133a8382e23d080f9ff86447e9fb0cd895eb529f Mon Sep 17 00:00:00 2001 From: FrancoisWerbrouck-CFIA <157528480+FrancoisWerbrouck-CFIA@users.noreply.github.com> Date: Tue, 30 Jan 2024 20:58:19 +0000 Subject: [PATCH 01/19] Fixes 2: Created Sequence doc --- metadata-importation-doc.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 metadata-importation-doc.md diff --git a/metadata-importation-doc.md b/metadata-importation-doc.md new file mode 100644 index 00000000..de6e0f5a --- /dev/null +++ b/metadata-importation-doc.md @@ -0,0 +1,24 @@ +``` mermaid + sequenceDiagram + + actor User + actor Dev + participant Azure Portal + participant Azure Storage Explorer + participant Azure Storage + alt New User + User->>Dev: Storage subscription key request + Note right of User: Email + Dev->>Azure Portal: Create Storage Blob + Azure Portal-)Azure Storage: Create() + Dev->>Azure Storage: Retrieve Key + Dev->>User: Send back subscription key + Note left of Dev: Email + User-> Azure Storage Explorer: SubscribeToStorage(key) + end + loop for each file + User->>Azure Storage Explorer: Upload(file) + Azure Storage Explorer-) Azure Storage: Send(file) + end + +``` \ No newline at end of file From 31d36805d4f3749e3dcd4f374bd0c0caf3f849a8 Mon Sep 17 00:00:00 2001 From: FrancoisWerbrouck-CFIA <157528480+FrancoisWerbrouck-CFIA@users.noreply.github.com> Date: Tue, 30 Jan 2024 21:11:21 +0000 Subject: [PATCH 02/19] Fixes #2: Arrows Standarization --- metadata-importation-doc.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/metadata-importation-doc.md b/metadata-importation-doc.md index de6e0f5a..fd6e5262 100644 --- a/metadata-importation-doc.md +++ b/metadata-importation-doc.md @@ -14,10 +14,10 @@ Dev->>Azure Storage: Retrieve Key Dev->>User: Send back subscription key Note left of Dev: Email - User-> Azure Storage Explorer: SubscribeToStorage(key) + User-)Azure Storage Explorer: SubscribeToStorage(key) end loop for each file - User->>Azure Storage Explorer: Upload(file) + User-)Azure Storage Explorer: Upload(file) Azure Storage Explorer-) Azure Storage: Send(file) end From 3ae33b581c5b91b810b3f57e8342db5f23855820 Mon Sep 17 00:00:00 2001 From: FrancoisWerbrouck-CFIA <157528480+FrancoisWerbrouck-CFIA@users.noreply.github.com> Date: Wed, 31 Jan 2024 21:09:48 +0000 Subject: [PATCH 03/19] Fixes #2: Adding Processing graph + Observation --- metadata-importation-doc.md | 92 +++++++++++++++++++++++++++---------- 1 file changed, 69 insertions(+), 23 deletions(-) diff --git a/metadata-importation-doc.md b/metadata-importation-doc.md index fd6e5262..d951d62d 100644 --- a/metadata-importation-doc.md +++ b/metadata-importation-doc.md @@ -1,24 +1,70 @@ + +## Workflow: Metadata upload to Azure cloud ``` mermaid - sequenceDiagram - - actor User - actor Dev - participant Azure Portal - participant Azure Storage Explorer - participant Azure Storage - alt New User - User->>Dev: Storage subscription key request - Note right of User: Email - Dev->>Azure Portal: Create Storage Blob - Azure Portal-)Azure Storage: Create() - Dev->>Azure Storage: Retrieve Key - Dev->>User: Send back subscription key - Note left of Dev: Email - User-)Azure Storage Explorer: SubscribeToStorage(key) - end - loop for each file - User-)Azure Storage Explorer: Upload(file) - Azure Storage Explorer-) Azure Storage: Send(file) - end - -``` \ No newline at end of file + sequenceDiagram + + actor User + actor Dev + participant Azure Portal + participant Azure Storage Explorer + participant Azure Storage + alt New User + User->>Dev: Storage subscription key request + Note right of User: Email + Dev->>Azure Portal: Create Storage Blob + Azure Portal-)Azure Storage: Create() + Dev->>Azure Storage: Retrieve Key + Dev->>User: Send back subscription key + Note left of Dev: Email + User-)Azure Storage Explorer: SubscribeToStorage(key) + end + loop for each file + User-)Azure Storage Explorer: Upload(file) + Azure Storage Explorer-) Azure Storage: Save(file) + end + +``` +## Sequence of Processing metadata for model + +``` mermaid + sequenceDiagram + + actor Dev + participant Azure Portal + participant Notebook + participant Azure Storage + + Dev->>Azure Storage: Check files structure + alt Wrong structure + Dev->>Azure Portal: Create Alt. Azure Storage + Azure Portal-)Azure Storage: Create(Alt) + Dev-)Azure Storage: Copy files into Alt. Storage + Dev-)Azure Storage: Rework structure + Edit files/folders + Dev-)Azure Storage: Replace Storage content with Alt. Storage content + end + Dev->>Azure Storage: Retrieve extraction code + Dev->>Notebook: Edit parameters of extraction code commands + + Dev-) Notebook: Run extraction code + Note left of Notebook: output source needs to be specified + Notebook -) Azure Storage: Processing files into metadata + Dev->> Azure Storage: Use files to train the model +``` + +### Legend +|Element|Description| +|-------|-----------| +| User | Anyone wanting to upload data. | +| Dev | AI-Lab Team. | +| Azure Portal | Interface managing Azure's services| +| Azure Storage | Interface storing data in the cloud. | +| Azure Storage Explorer | Application with GUI offering a user friendly access to a Azure Storage without granting full acess To the Azure Services. | +| NoteBook | Azure Service enabling to run code with Azure Storage structure| +| Files | All files shouldd follow the file structure present in the README.md | + + +## Observation + +- The process of the user requesting the Subscription Access Key and obtaining it is not really efficient. The Dev hav to manually create Storage space, the key is sent by email and no information links the user to the storage space. It would be interesting to have a SOP (standard operating procedure) + +- Alot of time can be spent on wrong file structure. We need to implement our file structure at the source (User). We also need more doc or SOP on how to deal with wrongfully structured file set. \ No newline at end of file From 4c8d29de0d4ae8a3092255bf5684127ad22da7fc Mon Sep 17 00:00:00 2001 From: FrancoisWerbrouck-CFIA <157528480+FrancoisWerbrouck-CFIA@users.noreply.github.com> Date: Thu, 1 Feb 2024 18:28:15 +0000 Subject: [PATCH 04/19] Fixes 2: Rename file + Added Files Structure --- metadata-doc.md | 186 ++++++++++++++++++++++++++++++++++++ metadata-importation-doc.md | 70 -------------- 2 files changed, 186 insertions(+), 70 deletions(-) create mode 100644 metadata-doc.md delete mode 100644 metadata-importation-doc.md diff --git a/metadata-doc.md b/metadata-doc.md new file mode 100644 index 00000000..431ef19e --- /dev/null +++ b/metadata-doc.md @@ -0,0 +1,186 @@ + + + +## Workflow: Metadata upload to Azure cloud +``` mermaid + sequenceDiagram + + actor User + actor Dev + participant Azure Portal + participant Azure Storage Explorer + participant Azure Storage + alt New User + User->>Dev: Storage subscription key request + Note right of User: Email + Dev->>Azure Portal: Create Storage Blob + Azure Portal-)Azure Storage: Create() + Dev->>Azure Storage: Retrieve Key + Dev->>User: Send back subscription key + Note left of Dev: Email + User-)Azure Storage Explorer: SubscribeToStorage(key) + end + loop for each file + User-)Azure Storage Explorer: Upload(file) + Azure Storage Explorer-) Azure Storage: Save(file) + end + +``` +## Sequence of Processing metadata for model + +``` mermaid + sequenceDiagram + + actor Dev + participant Azure Portal + participant Notebook + participant Azure Storage + + Dev->>Azure Storage: Check files structure + alt Wrong structure + Dev->>Azure Portal: Create Alt. Azure Storage + Azure Portal-)Azure Storage: Create(Alt) + Dev-)Azure Storage: Copy files into Alt. Storage + Dev-)Azure Storage: Rework structure + Edit files/folders + Dev-)Azure Storage: Replace Storage content with Alt. Storage content + end + Dev->>Azure Storage: Retrieve extraction code + Dev->>Notebook: Edit parameters of extraction code commands + + Dev-) Notebook: Run extraction code + Note left of Notebook: output source needs to be specified + Notebook -) Azure Storage: Processing files into metadata + Dev->> Azure Storage: Use files to train the model +``` + +### Legend +|Element|Description| +|-------|-----------| +| User | Anyone wanting to upload data. | +| Dev | AI-Lab Team. | +| Azure Portal | Interface managing Azure's services| +| Azure Storage | Interface storing data in the cloud. | +| Azure Storage Explorer | Application with GUI offering a user friendly access to a Azure Storage without granting full acess To the Azure Services. | +| NoteBook | Azure Service enabling to run code with Azure Storage structure| +| Files | All files should follow the file structure presented bellow. | + +## Files Structure + +We want to have a standard file structure enable the use of macro to manage the importation of files in the system. This would allow us to keep track of the users uploading data and the content uploaded. Giving a default structure to the files will allow us to run scripts through those files efficiently. It will also allow us to populate a DB with the collected information and have a better insight on our models actual performance. + +### Folder + +First, lets take a look at the whole struture of the folder a typical user would like to upload.We require the user to pack the entire upload into one singular folder. Within the project folder, multiple subfolder will exist to enforce an overall structure for the project, while allowing it to be incremental. The project folder should follow the following structure: +``` +project/ +│ index.yaml +│ +└───pictures/ +│ └───session1/ +│ | │ index.yaml +│ | │ 1.tiff +│ | │ 1.yaml +│ | | ... +│ | └───────────── +│ └───session2/ +│ | ... +│ └───────────── +└────────────────── +``` +### Files (.yaml) +#### Index.yaml + +The index is the most important file. It will allow us to have all the knowledge about the user and the categorization of the image. + +*Note: The index file located at the root of the project serves as the project index. Therefore, this index file content will differ from the session index, however it's structure will stay the same* + +```yaml +index: + projectID: projectID + uploadDate: timestamp + clientData: + clientID: assignedID + clientName: firstname lastname + clientOrg: org name + imageData: + numberOfImages: number of image to process in the session/project + uploadCheck: boolean to verify sucessfull upload + qualityCheck: + validData: boolean indicating if the data is valid + errorType: Type of error if the data is erroneous + dataQualityScore: Overall score given to the data + seedData: + seedID: seedID + seedFamily: Family name + seedGenus: Genus name + seedSpecies: Species name + seedCharacteristics: + color: color + shape: shape + size: size + texture: texture + pattern: pattern + weight: weight + seedCount: number of visible seeds + distinctFeatures: other unique feature of the seed + sampleInformation: + geoLocation: coordinates + region: name of the region/area + temperature: temperature of the location + humidity: humidity level + soil: type of soil + classificationData: + model: ID of the used model + prediction: species predicted by the model + confidenceScore: score + clientFeedback: + correctIdentification: boolean confirming the identification + additionalNotes: optional notes from the client + errorHandling: + feedback: feedback received on the classification + correctionApplied: correction applied based on the received feedback + errorDetails: details of the error + auditTrail: + editedBy: last userID that modified the records + editDate: date of the last modification + changeLog: log of all the changes made after the upload + accessLog: log of all the data access + privacyFlag: indicator for special privacy concerns + historicalData: + comparison: links to historical data of similar seeds + annotation: optional annotation by experts or the model + experienceFeedback: + rating: rating given by the client on his overall experience + comment: optional entry; comments, suggestion or improvement. +``` +#### X.yaml + +Each picture should have their .yaml conterpart. This will allow us to run scripts into the session folder and monitor each picture easily. Each .yaml file should have the following structure: + +*Note: X in this exemple is replacing the picture number or name* + +```yaml +X: + projectID: projectID + uploadDate: timestamp + clientData: + clientID: assignedID + clientName: firstname lastname + clientOrg: org name + imageData: + imageSource: url or path to the images + imageFormat: extension (.tiff, .jpg, .png, ...) + imageSize: image size + imageResolution: resolution of the images + imageDescription: description regarding zoom level, angles + imageChecksum: checksum for image data integrity + uploadCheck: boolean to verify sucessfull upload + qualityCheck: + validData: boolean indicating if the data is valid + errorType: Type of error if the data is erroneous + dataQualityScore: Overall score given to the data +``` + +## Observation + +- The process of the user requesting the Subscription Access Key and obtaining it is not really efficient. The Dev has to manually create Storage space, the key is sent by email and no information links the user to the storage space. It would be interesting to have a SOP (standard operating procedure) diff --git a/metadata-importation-doc.md b/metadata-importation-doc.md deleted file mode 100644 index d951d62d..00000000 --- a/metadata-importation-doc.md +++ /dev/null @@ -1,70 +0,0 @@ - -## Workflow: Metadata upload to Azure cloud -``` mermaid - sequenceDiagram - - actor User - actor Dev - participant Azure Portal - participant Azure Storage Explorer - participant Azure Storage - alt New User - User->>Dev: Storage subscription key request - Note right of User: Email - Dev->>Azure Portal: Create Storage Blob - Azure Portal-)Azure Storage: Create() - Dev->>Azure Storage: Retrieve Key - Dev->>User: Send back subscription key - Note left of Dev: Email - User-)Azure Storage Explorer: SubscribeToStorage(key) - end - loop for each file - User-)Azure Storage Explorer: Upload(file) - Azure Storage Explorer-) Azure Storage: Save(file) - end - -``` -## Sequence of Processing metadata for model - -``` mermaid - sequenceDiagram - - actor Dev - participant Azure Portal - participant Notebook - participant Azure Storage - - Dev->>Azure Storage: Check files structure - alt Wrong structure - Dev->>Azure Portal: Create Alt. Azure Storage - Azure Portal-)Azure Storage: Create(Alt) - Dev-)Azure Storage: Copy files into Alt. Storage - Dev-)Azure Storage: Rework structure + Edit files/folders - Dev-)Azure Storage: Replace Storage content with Alt. Storage content - end - Dev->>Azure Storage: Retrieve extraction code - Dev->>Notebook: Edit parameters of extraction code commands - - Dev-) Notebook: Run extraction code - Note left of Notebook: output source needs to be specified - Notebook -) Azure Storage: Processing files into metadata - Dev->> Azure Storage: Use files to train the model -``` - -### Legend -|Element|Description| -|-------|-----------| -| User | Anyone wanting to upload data. | -| Dev | AI-Lab Team. | -| Azure Portal | Interface managing Azure's services| -| Azure Storage | Interface storing data in the cloud. | -| Azure Storage Explorer | Application with GUI offering a user friendly access to a Azure Storage without granting full acess To the Azure Services. | -| NoteBook | Azure Service enabling to run code with Azure Storage structure| -| Files | All files shouldd follow the file structure present in the README.md | - - -## Observation - -- The process of the user requesting the Subscription Access Key and obtaining it is not really efficient. The Dev hav to manually create Storage space, the key is sent by email and no information links the user to the storage space. It would be interesting to have a SOP (standard operating procedure) - -- Alot of time can be spent on wrong file structure. We need to implement our file structure at the source (User). We also need more doc or SOP on how to deal with wrongfully structured file set. \ No newline at end of file From bd424ba57265c08ac980958cb7be1d41c350fb3d Mon Sep 17 00:00:00 2001 From: FrancoisWerbrouck-CFIA <157528480+FrancoisWerbrouck-CFIA@users.noreply.github.com> Date: Thu, 1 Feb 2024 18:44:29 +0000 Subject: [PATCH 05/19] Fixes #2: Text Rewrap --- metadata-doc.md | 45 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/metadata-doc.md b/metadata-doc.md index 431ef19e..4d379fbd 100644 --- a/metadata-doc.md +++ b/metadata-doc.md @@ -1,5 +1,13 @@ +# Metadata importation +## Context + +The following documentation detail all the metadata importation for the Nachet +pipeline. We showcase the workflow of everystep taken until the metadata is +usable by our models. We also discuss the expected files structure and other +important component regarding the metadata. + ## Workflow: Metadata upload to Azure cloud ``` mermaid @@ -20,9 +28,9 @@ Note left of Dev: Email User-)Azure Storage Explorer: SubscribeToStorage(key) end - loop for each file - User-)Azure Storage Explorer: Upload(file) - Azure Storage Explorer-) Azure Storage: Save(file) + loop for each project Folder + User-)Azure Storage Explorer: Upload(Folder) + Azure Storage Explorer-) Azure Storage: Save(folder) end ``` @@ -62,15 +70,24 @@ | Azure Storage | Interface storing data in the cloud. | | Azure Storage Explorer | Application with GUI offering a user friendly access to a Azure Storage without granting full acess To the Azure Services. | | NoteBook | Azure Service enabling to run code with Azure Storage structure| -| Files | All files should follow the file structure presented bellow. | +| Folder | All project folder should follow the files structure presented bellow. | ## Files Structure -We want to have a standard file structure enable the use of macro to manage the importation of files in the system. This would allow us to keep track of the users uploading data and the content uploaded. Giving a default structure to the files will allow us to run scripts through those files efficiently. It will also allow us to populate a DB with the collected information and have a better insight on our models actual performance. +We want to have a standard file structure enable the use of macro to manage the +importation of files in the system. This would allow us to keep track of the +users uploading data and the content uploaded. Giving a default structure to the +files will allow us to run scripts through those files efficiently. It will also +allow us to populate a DB with the collected information and have a better +insight on our models actual performance. ### Folder -First, lets take a look at the whole struture of the folder a typical user would like to upload.We require the user to pack the entire upload into one singular folder. Within the project folder, multiple subfolder will exist to enforce an overall structure for the project, while allowing it to be incremental. The project folder should follow the following structure: +First, lets take a look at the whole struture of the folder a typical user +wouldlike to upload.We require the user to pack the entire upload into one +singular folder. Within the project folder, multiple subfolder will exist to +enforce an overall structure for the project, while allowing it to be +incremental. The project folder should follow the following structure: ``` project/ │ index.yaml @@ -90,9 +107,12 @@ project/ ### Files (.yaml) #### Index.yaml -The index is the most important file. It will allow us to have all the knowledge about the user and the categorization of the image. +The index is the most important file. It will allow us to have all the knowledge +about the user and the categorization of the image. -*Note: The index file located at the root of the project serves as the project index. Therefore, this index file content will differ from the session index, however it's structure will stay the same* +*Note: The index file located at the root of the project serves as the project +index. Therefore, this index file content will differ from the session index, +however it's structure will stay the same* ```yaml index: @@ -155,7 +175,9 @@ index: ``` #### X.yaml -Each picture should have their .yaml conterpart. This will allow us to run scripts into the session folder and monitor each picture easily. Each .yaml file should have the following structure: +Each picture should have their .yaml conterpart. This will allow us to run +scripts into the session folder and monitor each picture easily. Each .yaml file +should have the following structure: *Note: X in this exemple is replacing the picture number or name* @@ -183,4 +205,7 @@ X: ## Observation -- The process of the user requesting the Subscription Access Key and obtaining it is not really efficient. The Dev has to manually create Storage space, the key is sent by email and no information links the user to the storage space. It would be interesting to have a SOP (standard operating procedure) +- The process of the user requesting the Subscription Access Key and obtaining + it is not really efficient. The Dev has to manually create Storage space, the + key is sent by email and no information links the user to the storage space. + It would be interesting to have a SOP (standard operating procedure) From 2bb24b1b1fb9b6704d47bea9545e3ebbe64914af Mon Sep 17 00:00:00 2001 From: FrancoisWerbrouck-CFIA <157528480+FrancoisWerbrouck-CFIA@users.noreply.github.com> Date: Thu, 1 Feb 2024 18:53:22 +0000 Subject: [PATCH 06/19] Fixes #2: small text fixes --- metadata-doc.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/metadata-doc.md b/metadata-doc.md index 4d379fbd..d5826099 100644 --- a/metadata-doc.md +++ b/metadata-doc.md @@ -205,7 +205,12 @@ X: ## Observation -- The process of the user requesting the Subscription Access Key and obtaining - it is not really efficient. The Dev has to manually create Storage space, the - key is sent by email and no information links the user to the storage space. - It would be interesting to have a SOP (standard operating procedure) +The process of the user requesting the Subscription Access Key and obtaining it + is not really efficient. The Dev has to manually create Storage space, the key + is sent by email and no information links the user to the storage space. It + would be interesting to have a SOP (standard operating procedure) automated by + a python script. This would enable us to automate the whole process, freeing + the dev from doing a manual task, we could also incorporate a process to + insert data into a DB with the python script, enabling us to also collect data + about the user. Therefore, we would have a database of our users, their keys, + storage, etc. From 4be0a5b7c9a3c51325cb0b54c0be9ac2b732b6f4 Mon Sep 17 00:00:00 2001 From: FrancoisWerbrouck-CFIA <157528480+FrancoisWerbrouck-CFIA@users.noreply.github.com> Date: Tue, 6 Feb 2024 21:23:10 +0000 Subject: [PATCH 07/19] Fixes #2: Metadata Template + file validator --- X.yaml | 16 ++++ file-validator.py | 68 +++++++++++++++ index-mockData.yaml | 54 ++++++++++++ index-template-system.json | 20 +++++ index-template.yaml | 31 +++++++ index.yaml | 47 ++++++++++ metadata-doc.md | 172 ++++++++++++------------------------- 7 files changed, 292 insertions(+), 116 deletions(-) create mode 100644 X.yaml create mode 100644 file-validator.py create mode 100644 index-mockData.yaml create mode 100644 index-template-system.json create mode 100644 index-template.yaml create mode 100644 index.yaml diff --git a/X.yaml b/X.yaml new file mode 100644 index 00000000..5b07e1a6 --- /dev/null +++ b/X.yaml @@ -0,0 +1,16 @@ +clientData: + email: firstname.lastname@minister.gc.ca +imageData: + source: url or path to the images + format: extension (.tiff, .jpg, .png, ...) + height: image height + width: image width + resolution: resolution of the images + description: description regarding zoom level, angles +qualityCheck: + imageChecksum: checksum for image data integrity + uploadCheck: boolean to verify sucessfull upload + uploadDate: timestamp + validData: boolean indicating if the data is valid + errorType: Type of error if the data is erroneous + dataQualityScore: Overall score given to the data \ No newline at end of file diff --git a/file-validator.py b/file-validator.py new file mode 100644 index 00000000..3abf1c81 --- /dev/null +++ b/file-validator.py @@ -0,0 +1,68 @@ +from datetime import date +from pydantic import BaseModel +from typing import List +import yaml +import json + +def fill_index_system(filename): + if is_json_empty(filename): + with open(filename, 'w') as json_file: + json.dump(default_json_data, json_file, indent=2) + +class ClientData(BaseModel): + clientEmail: str + clientExpertise: int + +class SeedCharacteristics(BaseModel): + color: str + shape: str + size: str + texture: str + pattern: str + weight: float + seedCount: int + distinctFeatures: str + +class SampleInformation(BaseModel): + geoLocation: str + region: str + temperature: float + humidity: float + soil: str + +class ClientFeedback(BaseModel): + correctIdentification: bool + historicalComparison: str + +class SeedData(BaseModel): + seedID: int + seedFamily: str + seedGenus: str + seedSpecies: str + seedCharacteristics: SeedCharacteristics + sampleInformation: SampleInformation + clientFeedback: ClientFeedback + +class ImageData(BaseModel): + numberOfImages: int + seeds: list + +class MetaData(BaseModel): + uploadDate: date + clientData: ClientData + imageData: ImageData + +class Data(BaseModel): + primary_data: MetaData + +# Load YAML template +with open('index-mockData.yaml', 'r') as file: + yaml_data = yaml.safe_load(file) + +# Validate and parse YAML data using Pydantic model +data = MetaData(**yaml_data) + +# Serialize data to JSON +json_data = data.json() + +print(json_data) \ No newline at end of file diff --git a/index-mockData.yaml b/index-mockData.yaml new file mode 100644 index 00000000..0f95c66a --- /dev/null +++ b/index-mockData.yaml @@ -0,0 +1,54 @@ +#This is a Section filled by user +uploadDate: 2024-01-31 +clientData: + clientEmail: francois.werbrouck@inspection.gc.ca + clientExpertise: 2 +imageData: + numberOfImages: 2 + seeds: + - seed1: + seedID: 123456789 + seedFamily: Family name + seedGenus: Genus name + seedSpecies: Species name + seedCharacteristics: + color: color + shape: shape + size: size + texture: texture + pattern: pattern + weight: 0.2532 + seedCount: 3 + distinctFeatures: other unique feature of the seed + sampleInformation: + geoLocation: coordinates + region: name of the region/area + temperature: 23.3 + humidity: 80 + soil: type of soil + clientFeedback: + correctIdentification: true + historicalComparison: www.somesite.com/repos + - seed2: + seedID: 9876543210 + seedFamily: Family name + seedGenus: Genus name + seedSpecies: Species name + seedCharacteristics: + color: color + shape: shape + size: size + texture: texture + pattern: pattern + weight: 0.123 + seedCount: 1 + distinctFeatures: other unique feature of the seed + sampleInformation: + geoLocation: coordinates + region: name of the region/area + temperature: 25.3 + humidity: 70 + soil: type of soil + clientFeedback: + correctIdentification: false + historicalComparison: www.somesite.com/reposs \ No newline at end of file diff --git a/index-template-system.json b/index-template-system.json new file mode 100644 index 00000000..85b33855 --- /dev/null +++ b/index-template-system.json @@ -0,0 +1,20 @@ +{ + "classificationData": { + "model": "", + "prediction": "", + "confidenceScore": "" + }, + "errorHandling": { + "feedback": "", + "correctionApplied": "", + "errorDetails": "" + }, + "auditTrail": { + "editedBy": "", + "editDate": "", + "changeLog": "", + "accessLog": "", + "privacyFlag": "" + } +} + \ No newline at end of file diff --git a/index-template.yaml b/index-template.yaml new file mode 100644 index 00000000..9172224f --- /dev/null +++ b/index-template.yaml @@ -0,0 +1,31 @@ +#This is a Section filled by user +uploadDate: +clientData: + clientEmail: + clientExpertise: +imageData: + numberOfImages: + uploadCheck: +seedData: + seedID: + seedFamily: + seedGenus: + seedSpecies: +seedCharacteristics: + color: + shape: + size: + texture: + pattern: + weight: + seedCount: + distinctFeatures: +sampleInformation: + geoLocation: + region: + temperature: + humidity: + soil: +clientFeedback: + correctIdentification: + comparison: \ No newline at end of file diff --git a/index.yaml b/index.yaml new file mode 100644 index 00000000..227c25a7 --- /dev/null +++ b/index.yaml @@ -0,0 +1,47 @@ +#This is a Section filled by user +uploadDate: date +clientData: + clientEmail: firstname.lastname@minister.gc.ca + clientExpertise: Level of expertise (1-2-3-4-5) +imageData: + numberOfImages: number of image to process in the session/project + uploadCheck: boolean to verify sucessfull upload +seedData: + seedID: seedID + seedFamily: Family name + seedGenus: Genus name + seedSpecies: Species name +seedCharacteristics: + color: color + shape: shape + size: size + texture: texture + pattern: pattern + weight: weight + seedCount: number of visible seeds + distinctFeatures: other unique feature of the seed +sampleInformation: + geoLocation: coordinates + region: name of the region/area + temperature: temperature of the location + humidity: humidity level + soil: type of soil +clientFeedback: + correctIdentification: true/false + comparison: links to historical data of similar seeds +# ------------------------------------------------------ +# Section filled by system +classificationData: + model: ID of the used model + prediction: species predicted by the model + confidenceScore: score + errorHandling: + feedback: feedback received on the classification + correctionApplied: correction applied based on the received feedback + errorDetails: details of the error + auditTrail: + editedBy: last userID that modified the records + editDate: date of the last modification + changeLog: log of all the changes made after the upload + accessLog: log of all the data access + privacyFlag: indicator for special privacy concerns \ No newline at end of file diff --git a/metadata-doc.md b/metadata-doc.md index d5826099..1f6a00ca 100644 --- a/metadata-doc.md +++ b/metadata-doc.md @@ -6,32 +6,52 @@ The following documentation detail all the metadata importation for the Nachet pipeline. We showcase the workflow of everystep taken until the metadata is usable by our models. We also discuss the expected files structure and other -important component regarding the metadata. +important component regarding the metadata. +``` mermaid +--- +title: Nachet folder upload +--- +flowchart LR; + DB[(Metadata)] + blob[(Blob)] + FE(Frontend) + BE(Backend) + file[/Folder/] + + file --> FE + FE-->BE + subgraph tdb [DB Process] + test:::hidden + end + BE -- TODO --- test + file -- In progress --- test + test -- TODO --> DB & blob +``` ## Workflow: Metadata upload to Azure cloud ``` mermaid - sequenceDiagram - - actor User - actor Dev - participant Azure Portal - participant Azure Storage Explorer - participant Azure Storage - alt New User - User->>Dev: Storage subscription key request - Note right of User: Email - Dev->>Azure Portal: Create Storage Blob - Azure Portal-)Azure Storage: Create() - Dev->>Azure Storage: Retrieve Key - Dev->>User: Send back subscription key - Note left of Dev: Email - User-)Azure Storage Explorer: SubscribeToStorage(key) - end - loop for each project Folder - User-)Azure Storage Explorer: Upload(Folder) - Azure Storage Explorer-) Azure Storage: Save(folder) - end +sequenceDiagram + + actor User + actor DataScientist + participant Azure Portal + participant Azure Storage Explorer + participant Azure Storage + alt New User + User->>DataScientist: Storage subscription key request + Note right of User: Email + DataScientist->>Azure Portal: Create Storage Blob + Azure Portal-)Azure Storage: Create() + DataScientist->>Azure Storage: Retrieve Key + DataScientist->>User: Send back subscription key + Note left of DataScientist: Email + User-)Azure Storage Explorer: SubscribeToStorage(key) + end + loop for each project Folder + User-)Azure Storage Explorer: Upload(Folder) + Azure Storage Explorer-) Azure Storage: Save(folder) + end ``` ## Sequence of Processing metadata for model @@ -39,33 +59,33 @@ important component regarding the metadata. ``` mermaid sequenceDiagram - actor Dev + actor DataScientist participant Azure Portal participant Notebook participant Azure Storage - Dev->>Azure Storage: Check files structure + DataScientist->>Azure Storage: Check files structure alt Wrong structure - Dev->>Azure Portal: Create Alt. Azure Storage + DataScientist->>Azure Portal: Create Alt. Azure Storage Azure Portal-)Azure Storage: Create(Alt) - Dev-)Azure Storage: Copy files into Alt. Storage - Dev-)Azure Storage: Rework structure + Edit files/folders - Dev-)Azure Storage: Replace Storage content with Alt. Storage content + DataScientist-)Azure Storage: Copy files into Alt. Storage + DataScientist-)Azure Storage: Rework structure + Edit files/folders + DataScientist-)Azure Storage: Replace Storage content with Alt. Storage content end - Dev->>Azure Storage: Retrieve extraction code - Dev->>Notebook: Edit parameters of extraction code commands + DataScientist->>Azure Storage: Retrieve extraction code + DataScientist->>Notebook: Edit parameters of extraction code commands - Dev-) Notebook: Run extraction code + DataScientist-) Notebook: Run extraction code Note left of Notebook: output source needs to be specified Notebook -) Azure Storage: Processing files into metadata - Dev->> Azure Storage: Use files to train the model + DataScientist->> Azure Storage: Use files to train the model ``` ### Legend |Element|Description| |-------|-----------| | User | Anyone wanting to upload data. | -| Dev | AI-Lab Team. | +| DataScientist | Member of the AI-Lab Team. | | Azure Portal | Interface managing Azure's services| | Azure Storage | Interface storing data in the cloud. | | Azure Storage Explorer | Application with GUI offering a user friendly access to a Azure Storage without granting full acess To the Azure Services. | @@ -105,7 +125,7 @@ project/ └────────────────── ``` ### Files (.yaml) -#### Index.yaml +#### [Index.yaml](index.yaml) The index is the most important file. It will allow us to have all the knowledge about the user and the categorization of the image. @@ -114,66 +134,8 @@ about the user and the categorization of the image. index. Therefore, this index file content will differ from the session index, however it's structure will stay the same* -```yaml -index: - projectID: projectID - uploadDate: timestamp - clientData: - clientID: assignedID - clientName: firstname lastname - clientOrg: org name - imageData: - numberOfImages: number of image to process in the session/project - uploadCheck: boolean to verify sucessfull upload - qualityCheck: - validData: boolean indicating if the data is valid - errorType: Type of error if the data is erroneous - dataQualityScore: Overall score given to the data - seedData: - seedID: seedID - seedFamily: Family name - seedGenus: Genus name - seedSpecies: Species name - seedCharacteristics: - color: color - shape: shape - size: size - texture: texture - pattern: pattern - weight: weight - seedCount: number of visible seeds - distinctFeatures: other unique feature of the seed - sampleInformation: - geoLocation: coordinates - region: name of the region/area - temperature: temperature of the location - humidity: humidity level - soil: type of soil - classificationData: - model: ID of the used model - prediction: species predicted by the model - confidenceScore: score - clientFeedback: - correctIdentification: boolean confirming the identification - additionalNotes: optional notes from the client - errorHandling: - feedback: feedback received on the classification - correctionApplied: correction applied based on the received feedback - errorDetails: details of the error - auditTrail: - editedBy: last userID that modified the records - editDate: date of the last modification - changeLog: log of all the changes made after the upload - accessLog: log of all the data access - privacyFlag: indicator for special privacy concerns - historicalData: - comparison: links to historical data of similar seeds - annotation: optional annotation by experts or the model - experienceFeedback: - rating: rating given by the client on his overall experience - comment: optional entry; comments, suggestion or improvement. -``` -#### X.yaml + +#### [X.yaml](X.yaml) Each picture should have their .yaml conterpart. This will allow us to run scripts into the session folder and monitor each picture easily. Each .yaml file @@ -181,28 +143,6 @@ should have the following structure: *Note: X in this exemple is replacing the picture number or name* -```yaml -X: - projectID: projectID - uploadDate: timestamp - clientData: - clientID: assignedID - clientName: firstname lastname - clientOrg: org name - imageData: - imageSource: url or path to the images - imageFormat: extension (.tiff, .jpg, .png, ...) - imageSize: image size - imageResolution: resolution of the images - imageDescription: description regarding zoom level, angles - imageChecksum: checksum for image data integrity - uploadCheck: boolean to verify sucessfull upload - qualityCheck: - validData: boolean indicating if the data is valid - errorType: Type of error if the data is erroneous - dataQualityScore: Overall score given to the data -``` - ## Observation The process of the user requesting the Subscription Access Key and obtaining it From 051f9af015e30d6160a2127b449809d8fd8cd2bc Mon Sep 17 00:00:00 2001 From: FrancoisWerbrouck-CFIA <157528480+FrancoisWerbrouck-CFIA@users.noreply.github.com> Date: Thu, 8 Feb 2024 16:02:32 +0000 Subject: [PATCH 08/19] Fixes #2: Doc Rework following discussions --- metadata-doc.md | 191 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 140 insertions(+), 51 deletions(-) diff --git a/metadata-doc.md b/metadata-doc.md index 1f6a00ca..b6761d3f 100644 --- a/metadata-doc.md +++ b/metadata-doc.md @@ -3,13 +3,14 @@ ## Context -The following documentation detail all the metadata importation for the Nachet -pipeline. We showcase the workflow of everystep taken until the metadata is -usable by our models. We also discuss the expected files structure and other -important component regarding the metadata. +The following documentation provide an overview of the metadata importation +process for the Nachet pipeline. We outline each steps of the workflow, +illustrating how data progresses until it becomes usable by our models. +Additionally, the upcoming process are showcased with the expected files +structure. ``` mermaid --- -title: Nachet folder upload +title: Nachet data upload --- flowchart LR; DB[(Metadata)] @@ -20,7 +21,7 @@ flowchart LR; file --> FE FE-->BE - subgraph tdb [DB Process] + subgraph tdb [New Process] test:::hidden end BE -- TODO --- test @@ -28,6 +29,9 @@ flowchart LR; test -- TODO --> DB & blob ``` +As shown above, we are currently working on a process to validate the files +uploaded to the cloud. However, since Nachet is still a work in progress, here's +the current workflow for our user to upload their images for the models. ## Workflow: Metadata upload to Azure cloud ``` mermaid @@ -37,11 +41,12 @@ sequenceDiagram actor DataScientist participant Azure Portal participant Azure Storage Explorer - participant Azure Storage + alt New User User->>DataScientist: Storage subscription key request Note right of User: Email DataScientist->>Azure Portal: Create Storage Blob + create participant Azure Storage Azure Portal-)Azure Storage: Create() DataScientist->>Azure Storage: Retrieve Key DataScientist->>User: Send back subscription key @@ -54,7 +59,7 @@ sequenceDiagram end ``` -## Sequence of Processing metadata for model +## Sequence of processing metadata for model ``` mermaid sequenceDiagram @@ -68,9 +73,12 @@ sequenceDiagram alt Wrong structure DataScientist->>Azure Portal: Create Alt. Azure Storage Azure Portal-)Azure Storage: Create(Alt) - DataScientist-)Azure Storage: Copy files into Alt. Storage - DataScientist-)Azure Storage: Rework structure + Edit files/folders - DataScientist-)Azure Storage: Replace Storage content with Alt. Storage content + create participant Azure Storage Alt + DataScientist-)Azure Storage Alt: Copy files + DataScientist-)Azure Storage Alt: Rework structure + Edit files/folders + DataScientist-)Azure Storage Alt: Replace Storage content with Alt. Storage content + destroy Azure Storage Alt + Azure Storage Alt -) Azure Storage: Export files end DataScientist->>Azure Storage: Retrieve extraction code DataScientist->>Notebook: Edit parameters of extraction code commands @@ -82,32 +90,111 @@ sequenceDiagram ``` ### Legend -|Element|Description| -|-------|-----------| -| User | Anyone wanting to upload data. | -| DataScientist | Member of the AI-Lab Team. | -| Azure Portal | Interface managing Azure's services| -| Azure Storage | Interface storing data in the cloud. | -| Azure Storage Explorer | Application with GUI offering a user friendly access to a Azure Storage without granting full acess To the Azure Services. | -| NoteBook | Azure Service enabling to run code with Azure Storage structure| -| Folder | All project folder should follow the files structure presented bellow. | - -## Files Structure - -We want to have a standard file structure enable the use of macro to manage the -importation of files in the system. This would allow us to keep track of the -users uploading data and the content uploaded. Giving a default structure to the -files will allow us to run scripts through those files efficiently. It will also -allow us to populate a DB with the collected information and have a better -insight on our models actual performance. - -### Folder - -First, lets take a look at the whole struture of the folder a typical user -wouldlike to upload.We require the user to pack the entire upload into one -singular folder. Within the project folder, multiple subfolder will exist to -enforce an overall structure for the project, while allowing it to be -incremental. The project folder should follow the following structure: +| Element | Description | +| ---------------------- | -------------------------------------------------------------------------------------------------------------------------- | +| User | Anyone wanting to upload data. | +| DataScientist | Member of the AI-Lab Team. | +| Azure Portal | Interface managing Azure's services | +| Azure Storage | Interface storing data in the cloud. | +| Azure Storage Explorer | Application with GUI offering a user friendly access to a Azure Storage without granting full acess To the Azure Services. | +| NoteBook | Azure Service enabling to run code with Azure Storage structure | +| Folder | All project folder should follow the files structure presented bellow. | + +## Development + +As explained in the context we aimn to implement a folder structure for user +uploads. This approach would allow to us add a data structure validator using +[Pydantic](https://docs.pydantic.dev/latest/). By implementing a validator, we +will be able to remove all manual metadata maintenance. Once the validation +process is complete, the upload process will come into play, enabling the +distribution of the files between the BLOB storage and a PostgreSQL database. + + We are currently working on such process, which will be added to the backend + part of nachet once it is finished. + +``` mermaid +--- +title: Nachet folder upload +--- +flowchart LR; + DB[(Metadata)] + blob[(Blob)] + FE(Frontend) + BE(Backend) + file[/Folder/] + classDef foo stroke:#f00 + + file --> FE + FE-->BE + subgraph dbProcess [New Process] + test:::hidden + end + BE -- TODO --- test + file -- In progress --- test + test -- TODO --> DB & blob + + linkStyle 3,4,5 stroke:#f00,stroke-width:4px,color:red; + style dbProcess stroke:#f00,stroke-width:2px +``` +## New Process + +``` mermaid + sequenceDiagram + + + participant System + participant Folder Controller + Box Pydantic Validation + participant Folder Structure Template + participant Yaml file template + end + System -) Folder Controller: Send(User data) + Activate Folder Controller + note left of Folder Controller: Validation process + alt User first upload + Folder Controller ->> Folder Structure Template: Check Project structure + end + Folder Controller ->>Folder Structure Template: Check Session structure + Folder Controller ->> Yaml file template: Check index structure + Folder Controller ->> Yaml file template: Check picture metadata structure + break Error raised + Folder Controller -) System: Reply(Error) + end + deactivate Folder Controller + Folder Controller -) Folder Controller: Transform User Yaml file into Json + Activate Folder Controller + note left of Folder Controller: Upload process + Folder Controller -) Json file template: Copy system file structure + Folder Controller -) Folder Controller: Append metadata files with system structure + Folder Controller -) Folder Controller: Fill system metadata + alt User first upload + create participant Azure Blob Storage + Folder Controller -) Azure Blob Storage: Create + end + loop each picture in session + Folder Controller -) Azure Blob Storage: upload(picture) + Folder Controller ->> Folder Controller: add info to picture metadata + end + Folder Controller -) Database: Upload(Metadata) + deactivate Folder Controller + Folder Controller ->> System: Reply(Upload successfull) + + +``` + +### Files Structure + +We aim to have a standard file structure to enable the use of a script to manage +the importation of files into the system. This statarized structure will allow +us to keep track of the users uploads, the metadata feedback. By providing a +default structure for the files, we can run scripts through those files and +efficiently add/edit data within. Moreover, this approach will facilitate the +population of a database with the collected information enabling us to have a +better insight on our models actual performance. + +#### Folder + +Lets begin by examinating the overall struture of the folder that a typical user will be expected to upload. We require that users pack their entire upload into a singular folder. Within the project folder, multiple subfolder will be present to enforce an overall structure for the project, while allowing futur addition. The project folder should adhere to the following structure: ``` project/ │ index.yaml @@ -124,8 +211,8 @@ project/ │ └───────────── └────────────────── ``` -### Files (.yaml) -#### [Index.yaml](index.yaml) +#### Files (.yaml) +##### [Index.yaml](index.yaml) The index is the most important file. It will allow us to have all the knowledge about the user and the categorization of the image. @@ -135,7 +222,7 @@ index. Therefore, this index file content will differ from the session index, however it's structure will stay the same* -#### [X.yaml](X.yaml) +##### [X.yaml](X.yaml) Each picture should have their .yaml conterpart. This will allow us to run scripts into the session folder and monitor each picture easily. Each .yaml file @@ -143,14 +230,16 @@ should have the following structure: *Note: X in this exemple is replacing the picture number or name* -## Observation - -The process of the user requesting the Subscription Access Key and obtaining it - is not really efficient. The Dev has to manually create Storage space, the key - is sent by email and no information links the user to the storage space. It - would be interesting to have a SOP (standard operating procedure) automated by - a python script. This would enable us to automate the whole process, freeing - the dev from doing a manual task, we could also incorporate a process to - insert data into a DB with the python script, enabling us to also collect data - about the user. Therefore, we would have a database of our users, their keys, - storage, etc. +## Consequences + Implementing this structure and introducing the new backend features in Nachet will result in the following impact: +- **Automation of file structure maintenance:** This process will autonomously manage the file structure, eliminating the need for manual maintenance and reducing workload for the AI-Lab team. + +- **Streamlined subscription key management:** The new feature will eliminate the need for email communication between users and the AI-Lab team for subscription keys. The system may automatically create and connect to the appropriate BLOB storage without user intervention. Consequently, manual creation of storage by the AI-Lab team will be unnecessary, and all keys will be securely stored in the database. + +- **Enhanced security:** The removal of email exchanges between users and the development team represents a substantial improvement in security protocols. + +- **Improved model tracking and training:** Storing user metadata will enable more effective tracking of model performance and facilitate better training strategies. + +- **Automated metadata enrichment:** The process will enable the automatic addition of additional information to metadata, enhancing the depth of insights available. + +Overall, this new feature will empowers the AI-Lav team to have better control over the content fed to the models and ensures improved tool maintenance capabilities in the future. \ No newline at end of file From 75d47aa5e15b97ba725b104d624cdff78dcf0772 Mon Sep 17 00:00:00 2001 From: FrancoisWerbrouck-CFIA <157528480+FrancoisWerbrouck-CFIA@users.noreply.github.com> Date: Thu, 8 Feb 2024 16:32:13 +0000 Subject: [PATCH 09/19] Fixes #2: Rewrap text + typo --- metadata-doc.md | 54 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 15 deletions(-) diff --git a/metadata-doc.md b/metadata-doc.md index b6761d3f..63446b6b 100644 --- a/metadata-doc.md +++ b/metadata-doc.md @@ -59,6 +59,10 @@ sequenceDiagram end ``` +This workflow showcase the 2 options that a user will face to upload data. The +first one being he's a first time user. Therefore, the current process for a +first time user is to contact the AI-Lab team and subscribe to the Blob storage +with a given subscription key. ## Sequence of processing metadata for model ``` mermaid @@ -88,7 +92,7 @@ sequenceDiagram Notebook -) Azure Storage: Processing files into metadata DataScientist->> Azure Storage: Use files to train the model ``` - +This sequence illustrate the manual task done by our team to maintain the storage of user's data. ### Legend | Element | Description | | ---------------------- | -------------------------------------------------------------------------------------------------------------------------- | @@ -102,7 +106,7 @@ sequenceDiagram ## Development -As explained in the context we aimn to implement a folder structure for user +As explained in the context we aim to implement a folder structure for user uploads. This approach would allow to us add a data structure validator using [Pydantic](https://docs.pydantic.dev/latest/). By implementing a validator, we will be able to remove all manual metadata maintenance. Once the validation @@ -136,6 +140,7 @@ flowchart LR; linkStyle 3,4,5 stroke:#f00,stroke-width:4px,color:red; style dbProcess stroke:#f00,stroke-width:2px ``` +*Note that the bottom process wont be present on the deployed versions of Nachet* ## New Process ``` mermaid @@ -181,7 +186,7 @@ flowchart LR; ``` - +This sequence encapsulate the expected tasks of the new feature. ### Files Structure We aim to have a standard file structure to enable the use of a script to manage @@ -194,7 +199,11 @@ better insight on our models actual performance. #### Folder -Lets begin by examinating the overall struture of the folder that a typical user will be expected to upload. We require that users pack their entire upload into a singular folder. Within the project folder, multiple subfolder will be present to enforce an overall structure for the project, while allowing futur addition. The project folder should adhere to the following structure: +Lets begin by examinating the overall struture of the folder that a typical user +will be expected to upload. We require that users pack their entire upload into +a singular folder. Within the project folder, multiple subfolder will be present +to enforce an overall structure for the project, while allowing futur addition. +The project folder should adhere to the following structure: ``` project/ │ index.yaml @@ -231,15 +240,30 @@ should have the following structure: *Note: X in this exemple is replacing the picture number or name* ## Consequences - Implementing this structure and introducing the new backend features in Nachet will result in the following impact: -- **Automation of file structure maintenance:** This process will autonomously manage the file structure, eliminating the need for manual maintenance and reducing workload for the AI-Lab team. - -- **Streamlined subscription key management:** The new feature will eliminate the need for email communication between users and the AI-Lab team for subscription keys. The system may automatically create and connect to the appropriate BLOB storage without user intervention. Consequently, manual creation of storage by the AI-Lab team will be unnecessary, and all keys will be securely stored in the database. - -- **Enhanced security:** The removal of email exchanges between users and the development team represents a substantial improvement in security protocols. - -- **Improved model tracking and training:** Storing user metadata will enable more effective tracking of model performance and facilitate better training strategies. - -- **Automated metadata enrichment:** The process will enable the automatic addition of additional information to metadata, enhancing the depth of insights available. + Implementing this structure and introducing the new backend features in Nachet + will result in the following impact: +- **Automation of file structure maintenance:** This process will autonomously + manage the file structure, eliminating the need for manual maintenance and + reducing workload for the AI-Lab team. + +- **Streamlined subscription key management:** The new feature will eliminate + the need for email communication between users and the AI-Lab team for + subscription keys. The system may automatically create and connect to the + appropriate BLOB storage without user intervention. Consequently, manual + creation of storage by the AI-Lab team will be unnecessary, and all keys will + be securely stored in the database. + +- **Enhanced security:** The removal of email exchanges between users and the + development team represents a substantial improvement in security protocols. + +- **Improved model tracking and training:** Storing user metadata will enable + more effective tracking of model performance and facilitate better training + strategies. + +- **Automated metadata enrichment:** The process will enable the automatic + addition of additional information to metadata, enhancing the depth of + insights available. -Overall, this new feature will empowers the AI-Lav team to have better control over the content fed to the models and ensures improved tool maintenance capabilities in the future. \ No newline at end of file +Overall, this new feature will empowers the AI-Lav team to have better control +over the content fed to the models and ensures improved tool maintenance +capabilities in the future. \ No newline at end of file From 1e57c162b4caf89cafbf1dd526c52ddb8790f9dd Mon Sep 17 00:00:00 2001 From: FrancoisWerbrouck-CFIA <157528480+FrancoisWerbrouck-CFIA@users.noreply.github.com> Date: Fri, 9 Feb 2024 19:58:15 +0000 Subject: [PATCH 10/19] Fixes #2: Reworked structure following feedback --- X.yaml | 16 ---------- file-validator.py | 52 ++++++++++++++++++++------------ index-mockData.yaml | 58 +++++------------------------------- index-template-system.json | 12 ++------ index-template.yaml | 21 +------------ index.yaml | 56 ++++++++++------------------------ metadata-doc.md | 21 +++++++------ picture-mockData.yaml | 4 +++ picture-template-system.json | 23 ++++++++++++++ picture-template.yaml | 5 ++++ picture.yaml | 53 ++++++++++++++++++++++++++++++++ 11 files changed, 155 insertions(+), 166 deletions(-) delete mode 100644 X.yaml create mode 100644 picture-mockData.yaml create mode 100644 picture-template-system.json create mode 100644 picture-template.yaml create mode 100644 picture.yaml diff --git a/X.yaml b/X.yaml deleted file mode 100644 index 5b07e1a6..00000000 --- a/X.yaml +++ /dev/null @@ -1,16 +0,0 @@ -clientData: - email: firstname.lastname@minister.gc.ca -imageData: - source: url or path to the images - format: extension (.tiff, .jpg, .png, ...) - height: image height - width: image width - resolution: resolution of the images - description: description regarding zoom level, angles -qualityCheck: - imageChecksum: checksum for image data integrity - uploadCheck: boolean to verify sucessfull upload - uploadDate: timestamp - validData: boolean indicating if the data is valid - errorType: Type of error if the data is erroneous - dataQualityScore: Overall score given to the data \ No newline at end of file diff --git a/file-validator.py b/file-validator.py index 3abf1c81..5666db00 100644 --- a/file-validator.py +++ b/file-validator.py @@ -3,6 +3,7 @@ from typing import List import yaml import json +import os def fill_index_system(filename): if is_json_empty(filename): @@ -39,30 +40,43 @@ class SeedData(BaseModel): seedFamily: str seedGenus: str seedSpecies: str - seedCharacteristics: SeedCharacteristics - sampleInformation: SampleInformation - clientFeedback: ClientFeedback - -class ImageData(BaseModel): + +class ImageDataindex(BaseModel): numberOfImages: int - seeds: list + +class ImageDatapic(BaseModel): + description: string + numberOfSeeds: int + zoom: float -class MetaData(BaseModel): - uploadDate: date + +class Index(BaseModel): clientData: ClientData - imageData: ImageData + imageData: ImageDataindex + seedData: SeedData -class Data(BaseModel): - primary_data: MetaData -# Load YAML template -with open('index-mockData.yaml', 'r') as file: - yaml_data = yaml.safe_load(file) +def is_yaml_file(file_path): + _, file_extension = os.path.splitext(file_path) + return file_extension.lower() == '.yaml' -# Validate and parse YAML data using Pydantic model -data = MetaData(**yaml_data) -# Serialize data to JSON -json_data = data.json() + +def openIndex(path): + if is_yaml_file(path): + # Load YAML template + with open(path, 'r') as file: + yaml_data = yaml.safe_load(file) + try: # Validate and parse YAML data using Pydantic model + data = Index(**yaml_data) + except Exception as error: # Error raised by Pydantic Validator + print(error) + + else: + json_data = data.json() + print(json_data) + #return(json_data) -print(json_data) \ No newline at end of file +if __name__ == "__main__": + file_path = input("Path:") + openIndex(file_path) \ No newline at end of file diff --git a/index-mockData.yaml b/index-mockData.yaml index 0f95c66a..d78ec56c 100644 --- a/index-mockData.yaml +++ b/index-mockData.yaml @@ -1,54 +1,10 @@ -#This is a Section filled by user -uploadDate: 2024-01-31 clientData: clientEmail: francois.werbrouck@inspection.gc.ca - clientExpertise: 2 + clientExpertise: dev imageData: - numberOfImages: 2 - seeds: - - seed1: - seedID: 123456789 - seedFamily: Family name - seedGenus: Genus name - seedSpecies: Species name - seedCharacteristics: - color: color - shape: shape - size: size - texture: texture - pattern: pattern - weight: 0.2532 - seedCount: 3 - distinctFeatures: other unique feature of the seed - sampleInformation: - geoLocation: coordinates - region: name of the region/area - temperature: 23.3 - humidity: 80 - soil: type of soil - clientFeedback: - correctIdentification: true - historicalComparison: www.somesite.com/repos - - seed2: - seedID: 9876543210 - seedFamily: Family name - seedGenus: Genus name - seedSpecies: Species name - seedCharacteristics: - color: color - shape: shape - size: size - texture: texture - pattern: pattern - weight: 0.123 - seedCount: 1 - distinctFeatures: other unique feature of the seed - sampleInformation: - geoLocation: coordinates - region: name of the region/area - temperature: 25.3 - humidity: 70 - soil: type of soil - clientFeedback: - correctIdentification: false - historicalComparison: www.somesite.com/reposs \ No newline at end of file + numberOfImages: 4 +seedData: + seedID: BrNa-02 + seedFamily: ddada + seedGenus: dadaa + seedSpecies: dada \ No newline at end of file diff --git a/index-template-system.json b/index-template-system.json index 85b33855..ebf02cd3 100644 --- a/index-template-system.json +++ b/index-template-system.json @@ -1,15 +1,7 @@ { - "classificationData": { - "model": "", - "prediction": "", - "confidenceScore": "" - }, - "errorHandling": { - "feedback": "", - "correctionApplied": "", - "errorDetails": "" - }, "auditTrail": { + "ownerEmail":"", + "uploadDate":"", "editedBy": "", "editDate": "", "changeLog": "", diff --git a/index-template.yaml b/index-template.yaml index 9172224f..0763b935 100644 --- a/index-template.yaml +++ b/index-template.yaml @@ -1,31 +1,12 @@ #This is a Section filled by user -uploadDate: clientData: clientEmail: clientExpertise: imageData: numberOfImages: - uploadCheck: seedData: seedID: seedFamily: seedGenus: seedSpecies: -seedCharacteristics: - color: - shape: - size: - texture: - pattern: - weight: - seedCount: - distinctFeatures: -sampleInformation: - geoLocation: - region: - temperature: - humidity: - soil: -clientFeedback: - correctIdentification: - comparison: \ No newline at end of file +# ------------------------------------------------------ \ No newline at end of file diff --git a/index.yaml b/index.yaml index 227c25a7..be6d0ea9 100644 --- a/index.yaml +++ b/index.yaml @@ -1,47 +1,21 @@ #This is a Section filled by user -uploadDate: date clientData: - clientEmail: firstname.lastname@minister.gc.ca - clientExpertise: Level of expertise (1-2-3-4-5) + clientEmail: email + clientExpertise: level of expertise imageData: - numberOfImages: number of image to process in the session/project - uploadCheck: boolean to verify sucessfull upload + numberOfImages: number of images seedData: - seedID: seedID - seedFamily: Family name - seedGenus: Genus name - seedSpecies: Species name -seedCharacteristics: - color: color - shape: shape - size: size - texture: texture - pattern: pattern - weight: weight - seedCount: number of visible seeds - distinctFeatures: other unique feature of the seed -sampleInformation: - geoLocation: coordinates - region: name of the region/area - temperature: temperature of the location - humidity: humidity level - soil: type of soil -clientFeedback: - correctIdentification: true/false - comparison: links to historical data of similar seeds + seedID: seed ID + seedFamily: seed Family + seedGenus: seed Genus + seedSpecies: seed Species # ------------------------------------------------------ # Section filled by system -classificationData: - model: ID of the used model - prediction: species predicted by the model - confidenceScore: score - errorHandling: - feedback: feedback received on the classification - correctionApplied: correction applied based on the received feedback - errorDetails: details of the error - auditTrail: - editedBy: last userID that modified the records - editDate: date of the last modification - changeLog: log of all the changes made after the upload - accessLog: log of all the data access - privacyFlag: indicator for special privacy concerns \ No newline at end of file +auditTrail: + ownerEmail: email of the user who uploaded the index + uploadDate: date uploaded + editedBy: last userID that modified the records + editDate: date of the last modification + changeLog: log of all the changes made after the upload + accessLog: log of all the data access + privacyFlag: indicator for special privacy concerns diff --git a/metadata-doc.md b/metadata-doc.md index 63446b6b..cbe52c63 100644 --- a/metadata-doc.md +++ b/metadata-doc.md @@ -223,21 +223,24 @@ project/ #### Files (.yaml) ##### [Index.yaml](index.yaml) -The index is the most important file. It will allow us to have all the knowledge -about the user and the categorization of the image. +The index is an most important file. It will allow us to have all the knowledge +about the user and the project/session. -*Note: The index file located at the root of the project serves as the project -index. Therefore, this index file content will differ from the session index, -however it's structure will stay the same* +``` mermaid +erDiagram +Index{ + ClientData expertiseLevel +} + +``` -##### [X.yaml](X.yaml) +##### [picture.yaml](picture.yaml) Each picture should have their .yaml conterpart. This will allow us to run -scripts into the session folder and monitor each picture easily. Each .yaml file -should have the following structure: +scripts into the session folder and monitor each picture easily. -*Note: X in this exemple is replacing the picture number or name* +*Note: 'picture' in this exemple is replacing the picture number or name of the .tiff file* ## Consequences Implementing this structure and introducing the new backend features in Nachet diff --git a/picture-mockData.yaml b/picture-mockData.yaml new file mode 100644 index 00000000..2c9de8e1 --- /dev/null +++ b/picture-mockData.yaml @@ -0,0 +1,4 @@ +ImageData: + description: cool seed picture + numberOfSeeds: 6 + zoom: 30 \ No newline at end of file diff --git a/picture-template-system.json b/picture-template-system.json new file mode 100644 index 00000000..985ecc0d --- /dev/null +++ b/picture-template-system.json @@ -0,0 +1,23 @@ +{ +"OwnerData":{ + "projectID": "" +}, +"imageData":{ + "edited": "", + "parentPath": "", + "uploadDate": "", + "source": "", + "format": "", + "height": "", + "width": "", + "resolution": "" +}, +"qualityCheck":{ + "imageChecksum": "", + "uploadCheck": "", + "uploadDate": "", + "validData": "", + "errorType": "", + "dataQualityScore": "" + } +} \ No newline at end of file diff --git a/picture-template.yaml b/picture-template.yaml new file mode 100644 index 00000000..585cc9bb --- /dev/null +++ b/picture-template.yaml @@ -0,0 +1,5 @@ +# This is a Section filled by user +ImageData: + description: + numberOfSeeds: + zoom: diff --git a/picture.yaml b/picture.yaml new file mode 100644 index 00000000..bd99c4c1 --- /dev/null +++ b/picture.yaml @@ -0,0 +1,53 @@ +# This is a Section filled by user +pictureData: + pictureFileName: name of the picture file with extension + description: description regarding zoom level, angles + numberOfDifferentSample: number of different sample in the picture + zoom: zoom level + + +# ------------------------------------------------------ +# Section filled by system +OwnerData: # TBD the usefullness + projectID: given to this project? # TBD the usefullness +imageData: + edited: boolean + parentPath: pointing towards index if not edited or point towards parent image file #TBD if not simply a DB relationship + uploadDate: timestamp + source: url or path to the images + format: extension (.tiff, .jpg, .png, ...) + height: image height + width: image width + resolution: resolution of the images +qualityCheck: + imageChecksum: checksum for image data integrity + uploadCheck: boolean to verify sucessfull upload + uploadDate: timestamp + validData: boolean indicating if the data is valid + errorType: Type of error if the data is erroneous + dataQualityScore: Overall score given to the data + +# ------------------------------------------------------ +#Section filled after Inference by User +clientData: + clientEmail: email +clientFeedback: + levelOfExpertise: level + correctIdentification: True/False + comparison: link to historical data +# ------------------------------------------------------ +#Section filled after Inference by System +classificationData: + model: modelID + prediction: inference + confidenceScore: score +auditTrail: + editedBy: editorID + editDate: date + changeLog: changes + accessLog: log + privacyFlag: boolean +errorHandling: + feedback: + correctionApplied: + errorDetails: \ No newline at end of file From a9059d20c712162de3ed92cdb35f2ece88ccde3c Mon Sep 17 00:00:00 2001 From: FrancoisWerbrouck-CFIA <157528480+FrancoisWerbrouck-CFIA@users.noreply.github.com> Date: Mon, 12 Feb 2024 15:55:19 +0000 Subject: [PATCH 11/19] Fixes #2: pic system template rework --- picture-template-system.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/picture-template-system.json b/picture-template-system.json index 985ecc0d..95ad7123 100644 --- a/picture-template-system.json +++ b/picture-template-system.json @@ -1,10 +1,11 @@ { -"OwnerData":{ +"info":{ + "uploadDate": "", "projectID": "" }, "imageData":{ "edited": "", - "parentPath": "", + "parent": "", "uploadDate": "", "source": "", "format": "", @@ -15,7 +16,6 @@ "qualityCheck":{ "imageChecksum": "", "uploadCheck": "", - "uploadDate": "", "validData": "", "errorType": "", "dataQualityScore": "" From 9c55b3e4cbc6b5f33ebd13fc315c19cbea684f9d Mon Sep 17 00:00:00 2001 From: FrancoisWerbrouck-CFIA <157528480+FrancoisWerbrouck-CFIA@users.noreply.github.com> Date: Thu, 15 Feb 2024 13:52:46 +0000 Subject: [PATCH 12/19] Fixes #2: MetaData Restructure --- child-picture-template.json | 15 +++++++++++++++ index-template-system.json | 2 +- index.yaml | 14 +++++++------- picture-mockData.yaml | 2 +- picture-template-system.json | 7 +++---- picture-template.yaml | 6 +++--- picture.yaml | 24 +++++++++++------------- 7 files changed, 41 insertions(+), 29 deletions(-) create mode 100644 child-picture-template.json diff --git a/child-picture-template.json b/child-picture-template.json new file mode 100644 index 00000000..44726ce6 --- /dev/null +++ b/child-picture-template.json @@ -0,0 +1,15 @@ +{ +"childData":{ + "children":[ + { + "name":"", + "source": "", + "height": "", + "width": "", + "bbox":"" + } + ], + "count":"" + } + +} diff --git a/index-template-system.json b/index-template-system.json index ebf02cd3..d10614c9 100644 --- a/index-template-system.json +++ b/index-template-system.json @@ -1,6 +1,6 @@ { "auditTrail": { - "ownerEmail":"", + "ownerID":"", "uploadDate":"", "editedBy": "", "editDate": "", diff --git a/index.yaml b/index.yaml index be6d0ea9..bb5bcd9d 100644 --- a/index.yaml +++ b/index.yaml @@ -12,10 +12,10 @@ seedData: # ------------------------------------------------------ # Section filled by system auditTrail: - ownerEmail: email of the user who uploaded the index - uploadDate: date uploaded - editedBy: last userID that modified the records - editDate: date of the last modification - changeLog: log of all the changes made after the upload - accessLog: log of all the data access - privacyFlag: indicator for special privacy concerns + ownerID: ID of the user who uploaded the picture, facilitate search + uploadDate: date uploaded + editedBy: last userID that modified the records + editDate: date of the last modification + changeLog: log of all the changes made after the upload + accessLog: log of all the data access + privacyFlag: indicator for special privacy concerns \ No newline at end of file diff --git a/picture-mockData.yaml b/picture-mockData.yaml index 2c9de8e1..f54cb880 100644 --- a/picture-mockData.yaml +++ b/picture-mockData.yaml @@ -1,4 +1,4 @@ -ImageData: +userData: description: cool seed picture numberOfSeeds: 6 zoom: 30 \ No newline at end of file diff --git a/picture-template-system.json b/picture-template-system.json index 95ad7123..608c60a3 100644 --- a/picture-template-system.json +++ b/picture-template-system.json @@ -1,12 +1,11 @@ { "info":{ "uploadDate": "", - "projectID": "" + "userID": "", + "indexID": "" }, "imageData":{ - "edited": "", "parent": "", - "uploadDate": "", "source": "", "format": "", "height": "", @@ -20,4 +19,4 @@ "errorType": "", "dataQualityScore": "" } -} \ No newline at end of file +} diff --git a/picture-template.yaml b/picture-template.yaml index 585cc9bb..74d02190 100644 --- a/picture-template.yaml +++ b/picture-template.yaml @@ -1,5 +1,5 @@ # This is a Section filled by user -ImageData: - description: - numberOfSeeds: +UserData: + description: + numberOfSeeds: zoom: diff --git a/picture.yaml b/picture.yaml index bd99c4c1..28410b18 100644 --- a/picture.yaml +++ b/picture.yaml @@ -1,19 +1,18 @@ # This is a Section filled by user -pictureData: - pictureFileName: name of the picture file with extension - description: description regarding zoom level, angles - numberOfDifferentSample: number of different sample in the picture - zoom: zoom level +UserData: + description: + numberOfSeeds: + zoom: # ------------------------------------------------------ # Section filled by system -OwnerData: # TBD the usefullness - projectID: given to this project? # TBD the usefullness -imageData: - edited: boolean - parentPath: pointing towards index if not edited or point towards parent image file #TBD if not simply a DB relationship +info: uploadDate: timestamp + userID: ID of the user who uploaded the picture to facilitate search + indexID: ID of the index linked to this pîcture to facilitate search +imageData: + parent: boolean if the picture has child pictures source: url or path to the images format: extension (.tiff, .jpg, .png, ...) height: image height @@ -22,13 +21,12 @@ imageData: qualityCheck: imageChecksum: checksum for image data integrity uploadCheck: boolean to verify sucessfull upload - uploadDate: timestamp validData: boolean indicating if the data is valid errorType: Type of error if the data is erroneous dataQualityScore: Overall score given to the data # ------------------------------------------------------ -#Section filled after Inference by User +#Section filled after Inference by User TBD clientData: clientEmail: email clientFeedback: @@ -36,7 +34,7 @@ clientFeedback: correctIdentification: True/False comparison: link to historical data # ------------------------------------------------------ -#Section filled after Inference by System +#Section filled after Inference by System TBD classificationData: model: modelID prediction: inference From fcac02cb3e8143fa1432549721adcc9856c1d1d2 Mon Sep 17 00:00:00 2001 From: FrancoisWerbrouck-CFIA <157528480+FrancoisWerbrouck-CFIA@users.noreply.github.com> Date: Mon, 19 Feb 2024 13:39:06 +0000 Subject: [PATCH 13/19] Fixes #2: Adding db-creation script --- db-creation.py | 68 ++++++++++++++ file-validator.py | 219 +++++++++++++++++++++++++++++++++++++++------- metadata-doc.md | 31 +++++++ 3 files changed, 287 insertions(+), 31 deletions(-) create mode 100644 db-creation.py diff --git a/db-creation.py b/db-creation.py new file mode 100644 index 00000000..4ef380b4 --- /dev/null +++ b/db-creation.py @@ -0,0 +1,68 @@ +import psycopg2 +import os + +print(os.getenv("NACHET_DB_URL")) +# Connect to your PostgreSQL database +conn = psycopg2.connect(os.getenv("NACHET_DB_URL")) +#conn = psycopg2.connect( +# host="", +# dbname='', +# port='', +# user="", +# password="") +# Create a cursor object +cur = conn.cursor() + +# # Create Schema +# cur.execute("CREATE SCHEMA nachetdb_0.0.1") + +# # Create Users table +# cur.execute(""" +# CREATE TABLE nachetdb_0.0.1.users ( +# id uuid PRIMARY KEY, +# email VARCHAR(255), +# ) +# """) + +# # Create Indexes table +# cur.execute(""" +# CREATE TABLE nachetdb_0.0.1.indexes ( +# id SERIAL PRIMARY KEY, +# index JSON, +# ownerID INTEGER REFERENCES users(id) +# ) +# """) + +# # Create Pictures table +# cur.execute(""" +# CREATE TABLE nachetdb_0.0.1.pictures ( +# id SERIAL PRIMARY KEY, +# picture JSON, +# indexID INTEGER REFERENCES indexes(id) +# ) +# """) + +## Commit the transaction +#conn.commit() + +# Check if the table exists +cur.execute(""" + SELECT EXISTS ( + SELECT 1 + FROM information_schema.tables + WHERE table_schema = 'nachetdb_0.0.1' + AND table_name = 'users' + ) +""") + +exists = cur.fetchone()[0] + +if exists: + print("Table nachetdb_0.0.1.users exists.") +else: + print("Table nachetdb_0.0.1.users does not exist.") + +# Close the cursor and connection +cur.close() +conn.close() +print("done") \ No newline at end of file diff --git a/file-validator.py b/file-validator.py index 5666db00..41507ce0 100644 --- a/file-validator.py +++ b/file-validator.py @@ -4,6 +4,8 @@ import yaml import json import os +from azure.storage.blob import BlobServiceClient +from PIL import Image def fill_index_system(filename): if is_json_empty(filename): @@ -12,28 +14,7 @@ def fill_index_system(filename): class ClientData(BaseModel): clientEmail: str - clientExpertise: int - -class SeedCharacteristics(BaseModel): - color: str - shape: str - size: str - texture: str - pattern: str - weight: float - seedCount: int - distinctFeatures: str - -class SampleInformation(BaseModel): - geoLocation: str - region: str - temperature: float - humidity: float - soil: str - -class ClientFeedback(BaseModel): - correctIdentification: bool - historicalComparison: str + clientExpertise: str class SeedData(BaseModel): seedID: int @@ -44,23 +25,115 @@ class SeedData(BaseModel): class ImageDataindex(BaseModel): numberOfImages: int -class ImageDatapic(BaseModel): - description: string - numberOfSeeds: int - zoom: float - +class AuditTrail(BaseModel): + uploadDate: date + editedBy: str + editDate: date + changeLog: str + accessLog: str + privacyFlag: bool class Index(BaseModel): clientData: ClientData imageData: ImageDataindex seedData: SeedData +class PIndex(BaseModel): + clientData: ClientData + imageData: ImageDataindex + seedData: SeedData + auditTrail: AuditTrail + +class UserData(BaseModel): + description: str + numberOfSeeds: int + zoom: float + +class Picture(BaseModel): + userData: UserData + +class ClientFeedback(BaseModel): + correctIdentification: bool + historicalComparison: str + def is_yaml_file(file_path): _, file_extension = os.path.splitext(file_path) return file_extension.lower() == '.yaml' +def manualMetaDataImport(): + #ask name for the folder + folderName = input("Folder Name: ") + os.makedirs(folderName) + #build index + nbPic=buildIndex(folderName) + #for each picture in field + zoomlevel = input("Zoom level for this index: ") + seedNumber = input("Number of seed for this index: ") + for x in range(nbPic): + buildPicture(folderName,zoomlevel,seedNumber) + +def buildIndex(output:str): + # Ask for the url in the console + + # # Retrieve some client info from the URL + + # Ask Data (I dont have access to the data url atm) + clientEmail=input("clientEmail: ") + clientExpertise= input("Expertise: ") + clientData=ClientData(clientEmail=clientEmail,clientExpertise=clientExpertise) + nb=input("numberOfImages: ") + imageData=ImageDataindex(numberOfImages=nb) + seedID=input("SeedID: ") + family = input("Family: ") + genus = input("Genus: ") + species= input("Species: ") + seedData=SeedData(seedID=seedID,seedFamily=family,seedGenus=genus,seedSpecies=species) + + index = Index(clientData=clientData,imageData=imageData,seedData=seedData) + name = input("File created, name: ") + + #createJsonIndex(index=index,name=name,output=output) + indexJson=index.dict() + sysData=IndexProcessing(openIndexSystem()) + indexJson.update(sysData) + print(indexJson) + createJsonIndex(index=indexJson,name=name,output=output) + return nb + +def buildPicture(output:str,number:int,level): + # Ask for the url in the console + + # # Retrieve some client info from the URL + + # Ask Data (I dont have access to the data url atm) + desc= "" + nb=number + zoom=level + userData = UserData(description=desc,numberOfSeeds=nb,zoom=zoom) + + pic = Picture(UserData=userData) + name = input("File created, name: ") + + createJsonPicture(pic=pic,name=name,output=output) + +#Create Json form Picture data model +def createJsonPicture(pic:Picture,name:str,output:str): + data = pic.dict() + filePath = f'{output}/{name}.json' + with open(filePath,'w') as json_file: + json.dump(data,json_file,indent=2) + +# Create Json from Index data model +def createJsonIndex(index,name: str,output:str): + #data=index.dict() + data = PIndex(**index) + filePath = f'{output}/{name}.json' + with open(filePath,'w') as json_file: + json.dump(index,json_file,indent=2) + + def openIndex(path): if is_yaml_file(path): @@ -69,14 +142,98 @@ def openIndex(path): yaml_data = yaml.safe_load(file) try: # Validate and parse YAML data using Pydantic model data = Index(**yaml_data) - except Exception as error: # Error raised by Pydantic Validator + except Exception as error: # Errtor raised by Pydantic Validator print(error) else: json_data = data.json() print(json_data) - #return(json_data) + return(json_data) + +def openIndexSystem(): + with open('index-template-system.json','r') as file: + sysData=json.load(file) + #print(sysData) + return sysData + +def IndexProcessing(jsonData: str): + today = date.today() + editedBy = "Francois Werbrouck" #not permanent, will be changed once the mass import is over + editDate = date.today() + change="" + access="" + privacy=False + return IndexSystemPopulating(jsonData,today,editedBy,editDate,change,access,privacy) + +def PictureProcessing(jsonData:str): + #ajouter structure + today=date.today() + userID="" + indexID="" + parent="" + source=input("Picture link: ") + format="" + height="" + width="" + resolution="" + return "" + +def IndexSystemPopulating(data,date:date,editedBy,editDate:date,changes:str,access,privacy): + #print(data) + data["auditTrail"]["uploadDate"]=date.strftime("%Y-%m-%d") + data["auditTrail"]["editedBy"]=editedBy + data["auditTrail"]["editDate"]=editDate.strftime("%Y-%m-%d") + data["auditTrail"]["changeLog"]=changes + data["auditTrail"]["accessLog"]=access + data["auditTrail"]["privacyFlag"]=privacy + return data +def folderProcessing(storage_url): + # Parse the storage URL + account_url = storage_url.split('/')[2] + container_name = storage_url.split('/')[3] + + # Create a blob service client + blob_service_client = BlobServiceClient(account_url=account_url) + + # Get the container client + container_client = blob_service_client.get_container_client(container_name) + + # List blobs in the container + blob_list = container_client.list_blobs() + + # Iterate through each blob + for blob in blob_list: + # Construct the blob URL + blob_url = f"{storage_url}/{blob.name}" + + # Print file information + print(f"File Name: {blob.name}") + print(f"File URL: {blob_url}") + + # Download the blob to the local file path + local_file_path = f"{local_download_path}/{blob.name}" + print(f"Downloading to: {local_file_path}") + + # Create a blob client + blob_client = blob_service_client.get_blob_client(container=container_name, blob=blob.name) + + # Download the blob + with open(local_file_path, "wb") as file: + blob_data = blob_client.download_blob() + blob_data.readinto(file) + + # Get image information + with Image.open(local_file_path) as img: + width, height = img.size + img_format = img.format + + # Print image information + print(f"Width: {width}") + print(f"Height: {height}") + print(f"Format: {img_format}") + print("\n") + if __name__ == "__main__": - file_path = input("Path:") - openIndex(file_path) \ No newline at end of file + #manualMetaDataImport() + buildIndex("test") \ No newline at end of file diff --git a/metadata-doc.md b/metadata-doc.md index cbe52c63..dbfb0485 100644 --- a/metadata-doc.md +++ b/metadata-doc.md @@ -241,6 +241,37 @@ Each picture should have their .yaml conterpart. This will allow us to run scripts into the session folder and monitor each picture easily. *Note: 'picture' in this exemple is replacing the picture number or name of the .tiff file* +## Database + +``` mermaid +--- +title: Nachet DB Structure +--- +erDiagram + users{ + uuid id PK + string email + } + indexes{ + int id PK + json index + int ownerID FK + } + pictures{ + int id PK + json picture + int indexID FK + } + Feedbacks{ + int ID PK + json feedback + } + + Users ||--|{ Indexes: uploads + Indexes ||--o{Pictures: contains + Pictures ||--o{Pictures: cropped + +``` ## Consequences Implementing this structure and introducing the new backend features in Nachet From 4da6c8bea241caa8aff80c3dbe48472bb19f3ba7 Mon Sep 17 00:00:00 2001 From: Francois-Werbrouck <122839953+Francois-Werbrouck@users.noreply.github.com> Date: Tue, 20 Feb 2024 15:17:08 +0000 Subject: [PATCH 14/19] Fixes #2: Added Backend Request Doc --- file-validator.py | 2 +- metadata-doc.md | 61 ++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 56 insertions(+), 7 deletions(-) diff --git a/file-validator.py b/file-validator.py index 41507ce0..f4e2855f 100644 --- a/file-validator.py +++ b/file-validator.py @@ -172,7 +172,7 @@ def PictureProcessing(jsonData:str): indexID="" parent="" source=input("Picture link: ") - format="" + format=".tiff" height="" width="" resolution="" diff --git a/metadata-doc.md b/metadata-doc.md index dbfb0485..084d6edf 100644 --- a/metadata-doc.md +++ b/metadata-doc.md @@ -186,7 +186,36 @@ flowchart LR; ``` -This sequence encapsulate the expected tasks of the new feature. +This sequence encapsulate the expected tasks of the new feature. + +### Requests (Backend) + +Nachet backend will need the following requests to be able to handle the new process. + +*Note the name of the requests are subject to change and are currently meant to be explicit about the purpose of the call* + +#### User requests +| Name | Description | +| ---------------------- | -------------------------------------------------------------------------------------------------------------------------- | +| isUserRegister | Is this user uuid stored in the database | +| getUserID | Retrieve the uuid of the current user | +| userRegister | This will serve as creating an instance of the user in the DB. I assume this will also be used as a way to create the containers if the user is an expert and has the responsability to upload a data set for testing the models| + +#### Upload requests + +| Name | Description | +| ---------------------- | -------------------------------------------------------------------------------------------------------------------------- | +| ValidateDataSet | Scans the folder uploaded by the user and checks if the standard structure is respected and if the metadata files are present (Index.yml + picture.yml). This will also check if the metadata files respect the structure explained in the documentation bellow. If the basic structure is not respected an error will be send. | +| uploadDataSet | This request happen once the data set receive the ok to all the validation checks. It serves as uploading all the folder to diverse endpoint depending on the file type. | +#### Validation Errors +Here's a list of the errors that can be returned turing the validation of the upload +| Name | Description | +| ---------------------- | -------------------------------------------------------------------------------------------------------------------------- | +| Wrong Structure | This type if error indicate the folder uploaded by the user doesn't follow the required structure. | +| Missing Index | an Index is missing which means the whole folder of picture couldn't be processed. This might stop the upload process as a whole | +|Missing picture yaml| Returns a set of picture which couldn't be processed. **TBD: This shouldn't stop the upload process** | +|Index data error | This indicate that one of the index files has a issue with one of the data field. | +| data error | This error indicate there's an issue with one of the data field in the file called 'picture.yml' | ### Files Structure We aim to have a standard file structure to enable the use of a script to manage @@ -242,7 +271,7 @@ scripts into the session folder and monitor each picture easily. *Note: 'picture' in this exemple is replacing the picture number or name of the .tiff file* ## Database - +We plan on storing the metadata of the user's files in a postgreSQL Database. The database should have the following structure: ``` mermaid --- title: Nachet DB Structure @@ -250,7 +279,8 @@ title: Nachet DB Structure erDiagram users{ uuid id PK - string email + string email + string container } indexes{ int id PK @@ -267,11 +297,30 @@ erDiagram json feedback } - Users ||--|{ Indexes: uploads - Indexes ||--o{Pictures: contains - Pictures ||--o{Pictures: cropped + users ||--|{ indexes: uploads + indexes ||--o{pictures: contains + pictures ||--o{pictures: cropped ``` +## Blob Storage + +Finally the picture uploaded by the users will need to be stored in a blob storage. Therefore we are using a Azure blob Storage account which currently contains a list of containers either for the users upload or our Data scientists training sets. The current structure needs to be revised and a standarized structure needs to pe applied for the futur of Nachet. + +``` +Storage account +│ +│ +└───container +│ └───folder/ +│ | │ 1.tiff +│ | │ 2.tiff +│ | | ... +│ | └───────────── +│ └───folder/ +│ | ... +│ └───────────── +└────────────────── +``` ## Consequences Implementing this structure and introducing the new backend features in Nachet From 7e27acd01ff98781f52fc98155fcb955364630f5 Mon Sep 17 00:00:00 2001 From: Francois-Werbrouck <122839953+Francois-Werbrouck@users.noreply.github.com> Date: Wed, 21 Feb 2024 13:50:36 +0000 Subject: [PATCH 15/19] Fixes #2: DB table created --- db-creation.py | 77 ++++++++------------- metadata-doc.md | 179 ++++++++++++++++++++++-------------------------- 2 files changed, 111 insertions(+), 145 deletions(-) diff --git a/db-creation.py b/db-creation.py index 4ef380b4..c3c12504 100644 --- a/db-creation.py +++ b/db-creation.py @@ -1,66 +1,45 @@ -import psycopg2 +import psycopg import os print(os.getenv("NACHET_DB_URL")) # Connect to your PostgreSQL database -conn = psycopg2.connect(os.getenv("NACHET_DB_URL")) -#conn = psycopg2.connect( -# host="", -# dbname='', -# port='', -# user="", -# password="") +conn = psycopg.connect(os.getenv("NACHET_DB_URL")) # Create a cursor object cur = conn.cursor() -# # Create Schema -# cur.execute("CREATE SCHEMA nachetdb_0.0.1") +# Create Schema +cur.execute("CREATE SCHEMA \"%s\"" % ("nachetdb_1.0.0")) -# # Create Users table -# cur.execute(""" -# CREATE TABLE nachetdb_0.0.1.users ( -# id uuid PRIMARY KEY, -# email VARCHAR(255), -# ) -# """) - -# # Create Indexes table -# cur.execute(""" -# CREATE TABLE nachetdb_0.0.1.indexes ( -# id SERIAL PRIMARY KEY, -# index JSON, -# ownerID INTEGER REFERENCES users(id) -# ) -# """) - -# # Create Pictures table -# cur.execute(""" -# CREATE TABLE nachetdb_0.0.1.pictures ( -# id SERIAL PRIMARY KEY, -# picture JSON, -# indexID INTEGER REFERENCES indexes(id) -# ) -# """) +#Create Users table +cur.execute(""" + CREATE TABLE \"%s\".users ( + id uuid PRIMARY KEY, + email VARCHAR(255) + ) +""" % ("nachetdb_1.0.0")) -## Commit the transaction -#conn.commit() +# Create Indexes table +cur.execute(""" + CREATE TABLE \"%s\".indexes ( + id SERIAL PRIMARY KEY, + index JSON, + ownerID uuid REFERENCES "nachetdb_1.0.0".users(id) + ) +""" % ("nachetdb_1.0.0")) -# Check if the table exists +# Create Pictures table cur.execute(""" - SELECT EXISTS ( - SELECT 1 - FROM information_schema.tables - WHERE table_schema = 'nachetdb_0.0.1' - AND table_name = 'users' + CREATE TABLE \"%s\".pictures ( + id SERIAL PRIMARY KEY, + picture JSON, + indexID INTEGER REFERENCES "nachetdb_1.0.0".indexes(id) ) -""") +""" % ("nachetdb_1.0.0")) + +## Commit the transaction +conn.commit() -exists = cur.fetchone()[0] -if exists: - print("Table nachetdb_0.0.1.users exists.") -else: - print("Table nachetdb_0.0.1.users does not exist.") # Close the cursor and connection cur.close() diff --git a/metadata-doc.md b/metadata-doc.md index 084d6edf..88e7d02c 100644 --- a/metadata-doc.md +++ b/metadata-doc.md @@ -35,28 +35,27 @@ the current workflow for our user to upload their images for the models. ## Workflow: Metadata upload to Azure cloud ``` mermaid -sequenceDiagram - - actor User - actor DataScientist - participant Azure Portal - participant Azure Storage Explorer - - alt New User - User->>DataScientist: Storage subscription key request - Note right of User: Email - DataScientist->>Azure Portal: Create Storage Blob - create participant Azure Storage - Azure Portal-)Azure Storage: Create() - DataScientist->>Azure Storage: Retrieve Key - DataScientist->>User: Send back subscription key - Note left of DataScientist: Email - User-)Azure Storage Explorer: SubscribeToStorage(key) - end - loop for each project Folder - User-)Azure Storage Explorer: Upload(Folder) - Azure Storage Explorer-) Azure Storage: Save(folder) - end +sequenceDiagram; + actor User + actor DataScientist + participant Azure Portal + participant Azure Storage Explorer + + alt New User + User->>DataScientist: Storage subscription key request + Note right of User: Email + DataScientist->>Azure Portal: Create Storage Blob + create participant Azure Storage + Azure Portal-)Azure Storage: Create() + DataScientist->>Azure Storage: Retrieve Key + DataScientist->>User: Send back subscription key + Note left of DataScientist: Email + User-)Azure Storage Explorer: SubscribeToStorage(key) + end + loop for each project Folder + User-)Azure Storage Explorer: Upload(Folder) + Azure Storage Explorer-) Azure Storage: Save(folder) + end ``` This workflow showcase the 2 options that a user will face to upload data. The @@ -66,31 +65,29 @@ with a given subscription key. ## Sequence of processing metadata for model ``` mermaid - sequenceDiagram - - actor DataScientist - participant Azure Portal - participant Notebook - participant Azure Storage - - DataScientist->>Azure Storage: Check files structure - alt Wrong structure - DataScientist->>Azure Portal: Create Alt. Azure Storage - Azure Portal-)Azure Storage: Create(Alt) - create participant Azure Storage Alt - DataScientist-)Azure Storage Alt: Copy files - DataScientist-)Azure Storage Alt: Rework structure + Edit files/folders - DataScientist-)Azure Storage Alt: Replace Storage content with Alt. Storage content - destroy Azure Storage Alt - Azure Storage Alt -) Azure Storage: Export files - end - DataScientist->>Azure Storage: Retrieve extraction code - DataScientist->>Notebook: Edit parameters of extraction code commands - - DataScientist-) Notebook: Run extraction code - Note left of Notebook: output source needs to be specified - Notebook -) Azure Storage: Processing files into metadata - DataScientist->> Azure Storage: Use files to train the model +sequenceDiagram; + actor DataScientist + participant Azure Portal + participant Notebook + participant Azure Storage + DataScientist->>Azure Storage: Check files structure + alt Wrong structure + DataScientist->>Azure Portal: Create Alt. Azure Storage + Azure Portal-)Azure Storage: Create(Alt) + create participant Azure Storage Alt + DataScientist-)Azure Storage Alt: Copy files + DataScientist-)Azure Storage Alt: Rework structure + Edit files/folders + DataScientist-)Azure Storage Alt: Replace Storage content with Alt. Storage content + destroy Azure Storage Alt + Azure Storage Alt -) Azure Storage: Export files + end + DataScientist->>Azure Storage: Retrieve extraction code + DataScientist->>Notebook: Edit parameters of extraction code commands + + DataScientist-) Notebook: Run extraction code + Note left of Notebook: output source needs to be specified + Notebook -) Azure Storage: Processing files into metadata + DataScientist->> Azure Storage: Use files to train the model ``` This sequence illustrate the manual task done by our team to maintain the storage of user's data. ### Legend @@ -144,45 +141,43 @@ flowchart LR; ## New Process ``` mermaid - sequenceDiagram - - - participant System - participant Folder Controller - Box Pydantic Validation - participant Folder Structure Template - participant Yaml file template - end - System -) Folder Controller: Send(User data) - Activate Folder Controller - note left of Folder Controller: Validation process - alt User first upload - Folder Controller ->> Folder Structure Template: Check Project structure - end - Folder Controller ->>Folder Structure Template: Check Session structure - Folder Controller ->> Yaml file template: Check index structure - Folder Controller ->> Yaml file template: Check picture metadata structure - break Error raised - Folder Controller -) System: Reply(Error) - end - deactivate Folder Controller - Folder Controller -) Folder Controller: Transform User Yaml file into Json - Activate Folder Controller - note left of Folder Controller: Upload process - Folder Controller -) Json file template: Copy system file structure - Folder Controller -) Folder Controller: Append metadata files with system structure - Folder Controller -) Folder Controller: Fill system metadata - alt User first upload - create participant Azure Blob Storage - Folder Controller -) Azure Blob Storage: Create - end - loop each picture in session - Folder Controller -) Azure Blob Storage: upload(picture) - Folder Controller ->> Folder Controller: add info to picture metadata - end - Folder Controller -) Database: Upload(Metadata) - deactivate Folder Controller - Folder Controller ->> System: Reply(Upload successfull) +sequenceDiagram; + participant System + participant Folder Controller + Box Pydantic Validation + participant Folder Structure Template + participant Yaml file template + end + System -) Folder Controller: Send(User data) + Activate Folder Controller + note left of Folder Controller: Validation process + alt User first upload + Folder Controller ->> Folder Structure Template: Check Project structure + end + Folder Controller ->>Folder Structure Template: Check Session structure + Folder Controller ->> Yaml file template: Check index structure + Folder Controller ->> Yaml file template: Check picture metadata structure + break Error raised + Folder Controller -) System: Reply(Error) + end + deactivate Folder Controller + Folder Controller -) Folder Controller: Transform User Yaml file into Json + Activate Folder Controller + note left of Folder Controller: Upload process + Folder Controller -) Json file template: Copy system file structure + Folder Controller -) Folder Controller: Append metadata files with system structure + Folder Controller -) Folder Controller: Fill system metadata + alt User first upload + create participant Azure Blob Storage + Folder Controller -) Azure Blob Storage: Create + end + loop each picture in session + Folder Controller -) Azure Blob Storage: upload(picture) + Folder Controller ->> Folder Controller: add info to picture metadata + end + Folder Controller -) Database: Upload(Metadata) + deactivate Folder Controller + Folder Controller ->> System: Reply(Upload successfull) ``` @@ -255,14 +250,6 @@ project/ The index is an most important file. It will allow us to have all the knowledge about the user and the project/session. -``` mermaid -erDiagram -Index{ - ClientData expertiseLevel -} - -``` - ##### [picture.yaml](picture.yaml) @@ -292,7 +279,7 @@ erDiagram json picture int indexID FK } - Feedbacks{ + feedbacks{ int ID PK json feedback } @@ -310,7 +297,7 @@ Finally the picture uploaded by the users will need to be stored in a blob stora Storage account │ │ -└───container +└───container │ └───folder/ │ | │ 1.tiff │ | │ 2.tiff From f7c752b07ee37dc5eda5672e02b093b3a85f4be9 Mon Sep 17 00:00:00 2001 From: Francois-Werbrouck <122839953+Francois-Werbrouck@users.noreply.github.com> Date: Wed, 28 Feb 2024 20:36:34 +0000 Subject: [PATCH 16/19] Fixes #2: Restructuration and import process is working --- db-creation.py | 47 --- file-validator.py | 231 +++------- metadata-doc.md | 25 +- nachetDb-1.0.0/db-creation.py | 57 +++ nachetDb-1.0.0/db-queries.py | 25 ++ nachetDb-1.0.0/deployment-mass-import.py | 511 +++++++++++++++++++++++ nachetDb-1.0.0/storage-download.py | 50 +++ 7 files changed, 709 insertions(+), 237 deletions(-) delete mode 100644 db-creation.py create mode 100644 nachetDb-1.0.0/db-creation.py create mode 100644 nachetDb-1.0.0/db-queries.py create mode 100644 nachetDb-1.0.0/deployment-mass-import.py create mode 100644 nachetDb-1.0.0/storage-download.py diff --git a/db-creation.py b/db-creation.py deleted file mode 100644 index c3c12504..00000000 --- a/db-creation.py +++ /dev/null @@ -1,47 +0,0 @@ -import psycopg -import os - -print(os.getenv("NACHET_DB_URL")) -# Connect to your PostgreSQL database -conn = psycopg.connect(os.getenv("NACHET_DB_URL")) -# Create a cursor object -cur = conn.cursor() - -# Create Schema -cur.execute("CREATE SCHEMA \"%s\"" % ("nachetdb_1.0.0")) - -#Create Users table -cur.execute(""" - CREATE TABLE \"%s\".users ( - id uuid PRIMARY KEY, - email VARCHAR(255) - ) -""" % ("nachetdb_1.0.0")) - -# Create Indexes table -cur.execute(""" - CREATE TABLE \"%s\".indexes ( - id SERIAL PRIMARY KEY, - index JSON, - ownerID uuid REFERENCES "nachetdb_1.0.0".users(id) - ) -""" % ("nachetdb_1.0.0")) - -# Create Pictures table -cur.execute(""" - CREATE TABLE \"%s\".pictures ( - id SERIAL PRIMARY KEY, - picture JSON, - indexID INTEGER REFERENCES "nachetdb_1.0.0".indexes(id) - ) -""" % ("nachetdb_1.0.0")) - -## Commit the transaction -conn.commit() - - - -# Close the cursor and connection -cur.close() -conn.close() -print("done") \ No newline at end of file diff --git a/file-validator.py b/file-validator.py index f4e2855f..9d20fbea 100644 --- a/file-validator.py +++ b/file-validator.py @@ -4,14 +4,11 @@ import yaml import json import os +import psycopg from azure.storage.blob import BlobServiceClient +from azure.core import paging from PIL import Image -def fill_index_system(filename): - if is_json_empty(filename): - with open(filename, 'w') as json_file: - json.dump(default_json_data, json_file, indent=2) - class ClientData(BaseModel): clientEmail: str clientExpertise: str @@ -33,6 +30,31 @@ class AuditTrail(BaseModel): accessLog: str privacyFlag: bool +class Info(BaseModel): + userID: int + uploadDate: date + indexID: int + +class ImageData(BaseModel): + format: str + height: int + width: int + resolution: str + source: str + parent: str + +class QualityCheck(BaseModel): + imageChecksum: str + uploadCheck: bool + validData: bool + errorType: str + dataQualityScore: float + +class UserData(BaseModel): + description: str + numberOfSeeds: int + zoom: float + class Index(BaseModel): clientData: ClientData imageData: ImageDataindex @@ -44,196 +66,37 @@ class PIndex(BaseModel): seedData: SeedData auditTrail: AuditTrail -class UserData(BaseModel): - description: str - numberOfSeeds: int - zoom: float - class Picture(BaseModel): userData: UserData +class PPicture(BaseModel): + userData: UserData + info: Info + imageData: ImageData + qualityCheck: QualityCheck + class ClientFeedback(BaseModel): correctIdentification: bool historicalComparison: str - -def is_yaml_file(file_path): - _, file_extension = os.path.splitext(file_path) - return file_extension.lower() == '.yaml' - -def manualMetaDataImport(): - #ask name for the folder - folderName = input("Folder Name: ") - os.makedirs(folderName) - #build index - nbPic=buildIndex(folderName) - #for each picture in field - zoomlevel = input("Zoom level for this index: ") - seedNumber = input("Number of seed for this index: ") - for x in range(nbPic): - buildPicture(folderName,zoomlevel,seedNumber) -def buildIndex(output:str): - # Ask for the url in the console +# def is_json_empty(filename): +# with open(filename) as json_file: +# data = json.load(json_file) +# return len(data) == 0 - # # Retrieve some client info from the URL - - # Ask Data (I dont have access to the data url atm) - clientEmail=input("clientEmail: ") - clientExpertise= input("Expertise: ") - clientData=ClientData(clientEmail=clientEmail,clientExpertise=clientExpertise) - nb=input("numberOfImages: ") - imageData=ImageDataindex(numberOfImages=nb) - seedID=input("SeedID: ") - family = input("Family: ") - genus = input("Genus: ") - species= input("Species: ") - seedData=SeedData(seedID=seedID,seedFamily=family,seedGenus=genus,seedSpecies=species) - - index = Index(clientData=clientData,imageData=imageData,seedData=seedData) - name = input("File created, name: ") +# def fill_index_system(filename): +# if is_json_empty(filename): +# with open(filename, 'w') as json_file: +# json.dump(default_json_data, json_file, indent=2) - #createJsonIndex(index=index,name=name,output=output) - indexJson=index.dict() - sysData=IndexProcessing(openIndexSystem()) - indexJson.update(sysData) - print(indexJson) - createJsonIndex(index=indexJson,name=name,output=output) - return nb - - -def buildPicture(output:str,number:int,level): - # Ask for the url in the console - - # # Retrieve some client info from the URL - - # Ask Data (I dont have access to the data url atm) - desc= "" - nb=number - zoom=level - userData = UserData(description=desc,numberOfSeeds=nb,zoom=zoom) - - pic = Picture(UserData=userData) - name = input("File created, name: ") - - createJsonPicture(pic=pic,name=name,output=output) - -#Create Json form Picture data model -def createJsonPicture(pic:Picture,name:str,output:str): - data = pic.dict() - filePath = f'{output}/{name}.json' - with open(filePath,'w') as json_file: - json.dump(data,json_file,indent=2) - -# Create Json from Index data model -def createJsonIndex(index,name: str,output:str): - #data=index.dict() - data = PIndex(**index) - filePath = f'{output}/{name}.json' - with open(filePath,'w') as json_file: - json.dump(index,json_file,indent=2) - - - -def openIndex(path): - if is_yaml_file(path): - # Load YAML template - with open(path, 'r') as file: - yaml_data = yaml.safe_load(file) - try: # Validate and parse YAML data using Pydantic model - data = Index(**yaml_data) - except Exception as error: # Errtor raised by Pydantic Validator - print(error) - - else: - json_data = data.json() - print(json_data) - return(json_data) - -def openIndexSystem(): - with open('index-template-system.json','r') as file: - sysData=json.load(file) - #print(sysData) - return sysData - -def IndexProcessing(jsonData: str): - today = date.today() - editedBy = "Francois Werbrouck" #not permanent, will be changed once the mass import is over - editDate = date.today() - change="" - access="" - privacy=False - return IndexSystemPopulating(jsonData,today,editedBy,editDate,change,access,privacy) - -def PictureProcessing(jsonData:str): - #ajouter structure - today=date.today() - userID="" - indexID="" - parent="" - source=input("Picture link: ") - format=".tiff" - height="" - width="" - resolution="" - return "" - -def IndexSystemPopulating(data,date:date,editedBy,editDate:date,changes:str,access,privacy): - #print(data) - data["auditTrail"]["uploadDate"]=date.strftime("%Y-%m-%d") - data["auditTrail"]["editedBy"]=editedBy - data["auditTrail"]["editDate"]=editDate.strftime("%Y-%m-%d") - data["auditTrail"]["changeLog"]=changes - data["auditTrail"]["accessLog"]=access - data["auditTrail"]["privacyFlag"]=privacy - return data - -def folderProcessing(storage_url): - # Parse the storage URL - account_url = storage_url.split('/')[2] - container_name = storage_url.split('/')[3] - - # Create a blob service client - blob_service_client = BlobServiceClient(account_url=account_url) - - # Get the container client - container_client = blob_service_client.get_container_client(container_name) - - # List blobs in the container - blob_list = container_client.list_blobs() +# def is_yaml_file(file_path): +# _, file_extension = os.path.splitext(file_path) +# return file_extension.lower() == '.yaml' - # Iterate through each blob - for blob in blob_list: - # Construct the blob URL - blob_url = f"{storage_url}/{blob.name}" - - # Print file information - print(f"File Name: {blob.name}") - print(f"File URL: {blob_url}") +container_URL="https://seedgroup.blob.core.windows.net/sas1706-tagarno-exp2-3-15species" - # Download the blob to the local file path - local_file_path = f"{local_download_path}/{blob.name}" - print(f"Downloading to: {local_file_path}") - - # Create a blob client - blob_client = blob_service_client.get_blob_client(container=container_name, blob=blob.name) - - # Download the blob - with open(local_file_path, "wb") as file: - blob_data = blob_client.download_blob() - blob_data.readinto(file) - - # Get image information - with Image.open(local_file_path) as img: - width, height = img.size - img_format = img.format - - # Print image information - print(f"Width: {width}") - print(f"Height: {height}") - print(f"Format: {img_format}") - print("\n") + if __name__ == "__main__": - #manualMetaDataImport() - buildIndex("test") \ No newline at end of file + print("test") diff --git a/metadata-doc.md b/metadata-doc.md index 88e7d02c..d30dc47f 100644 --- a/metadata-doc.md +++ b/metadata-doc.md @@ -200,17 +200,25 @@ Nachet backend will need the following requests to be able to handle the new pro | Name | Description | | ---------------------- | -------------------------------------------------------------------------------------------------------------------------- | -| ValidateDataSet | Scans the folder uploaded by the user and checks if the standard structure is respected and if the metadata files are present (Index.yml + picture.yml). This will also check if the metadata files respect the structure explained in the documentation bellow. If the basic structure is not respected an error will be send. | +| ValidateDataSet | Scans the folder uploaded by the user and checks if the standard structure is respected. | +| ValidateIndexes | Check if there is an index file for each sub folders with pictures, and if all the keys have a value associated to them (This does not verify the value)| +| ValidateIndexContent | This validates that a single index has all his fields correctly filled out (input type check, input is valid, seed info is ok, ...) | +| ValidateFileCount | This scans each subfolders and verify that the file count correspond to the expected amount of file, based on the number of images in the index metadata. | +|**CreateNewSeed** | This request will depend on either we receive a populated DB from our partners. If not, if the see submitted by is a new one, we will have to create an instance of it in the DB. | +| ValidatePictureTandem | This validates that each picture have their corresponding YAML counterpart.
*(If the number of seeds and zoom field are not removed from picture.yaml)* | +| ValidatePictureContent | This will not be a request related to the DB or metadata, but it will be necessary to check if the picture uploaded is related to Nachet's content and it's properties are ok (size, format, etc.) +| MaliciousPictureCheck|This will not be a request related to the DB or metadata, but it is imperative to check the picture file and make sure it's a picture and not a malicious file with hidden code or content into it. | | uploadDataSet | This request happen once the data set receive the ok to all the validation checks. It serves as uploading all the folder to diverse endpoint depending on the file type. | #### Validation Errors Here's a list of the errors that can be returned turing the validation of the upload | Name | Description | | ---------------------- | -------------------------------------------------------------------------------------------------------------------------- | -| Wrong Structure | This type if error indicate the folder uploaded by the user doesn't follow the required structure. | -| Missing Index | an Index is missing which means the whole folder of picture couldn't be processed. This might stop the upload process as a whole | -|Missing picture yaml| Returns a set of picture which couldn't be processed. **TBD: This shouldn't stop the upload process** | -|Index data error | This indicate that one of the index files has a issue with one of the data field. | -| data error | This error indicate there's an issue with one of the data field in the file called 'picture.yml' | +| Wrong structure | This type if error indicate the folder uploaded by the user doesn't follow the required structure. | +| Missing Index | An Index is missing which means the whole folder of picture couldn't be processed. This might stop the upload process as a whole | +|Index content | A specific Index either has missing fields or unexpected values | +|Unexpected file | Based on the value given by the user within the index, there are more files present in the subfolder than expected | +|Missing file | Based on the value given by the user within the index, there are less picture files than expected| +| content | This error indicate there's an issue with one of the data field in the file called 'picture.yml'
*(If the number of seeds and zoom field are not removed from picture.yaml)* | ### Files Structure We aim to have a standard file structure to enable the use of a script to manage @@ -283,6 +291,11 @@ erDiagram int ID PK json feedback } + seeds{ + uuid id PK + string name + json info + } users ||--|{ indexes: uploads indexes ||--o{pictures: contains diff --git a/nachetDb-1.0.0/db-creation.py b/nachetDb-1.0.0/db-creation.py new file mode 100644 index 00000000..ea7eb738 --- /dev/null +++ b/nachetDb-1.0.0/db-creation.py @@ -0,0 +1,57 @@ +import psycopg +import os + +print(os.getenv("NACHET_DB_URL")) +# Connect to your PostgreSQL database +conn = psycopg.connect(os.getenv("NACHET_DB_URL")) +# Create a cursor object +cur = conn.cursor() + +# # Create Schema +# cur.execute("CREATE SCHEMA \"%s\"" % ("nachetdb_1.0.0")) + +# #Create Users table +# cur.execute(""" +# CREATE TABLE \"%s\".users ( +# id uuid PRIMARY KEY, +# email VARCHAR(255) +# ) +# """ % ("nachetdb_1.0.0")) + +# # Create Indexes table +# cur.execute(""" +# CREATE TABLE \"%s\".indexes ( +# id SERIAL PRIMARY KEY, +# index JSON, +# ownerID uuid REFERENCES "nachetdb_1.0.0".users(id) +# ) +# """ % ("nachetdb_1.0.0")) + +# # Create Pictures table +# cur.execute(""" +# CREATE TABLE \"%s\".pictures ( +# id SERIAL PRIMARY KEY, +# picture JSON, +# indexID INTEGER REFERENCES "nachetdb_1.0.0".indexes(id) +# ) +# """ % ("nachetdb_1.0.0")) + +# check if the table exists +cur.execute(""" + SELECT EXISTS ( + SELECT 1 + FROM information_schema.tables + WHERE table_schema = 'nachetdb_1.0.0' + AND table_name = 'users' + ) +""") + +## Commit the transaction +conn.commit() + +print(cur.fetchone()[0]) + +# Close the cursor and connection +cur.close() +conn.close() +print("done") \ No newline at end of file diff --git a/nachetDb-1.0.0/db-queries.py b/nachetDb-1.0.0/db-queries.py new file mode 100644 index 00000000..ce108f64 --- /dev/null +++ b/nachetDb-1.0.0/db-queries.py @@ -0,0 +1,25 @@ +import psycopg +import os + +print(os.getenv("NACHET_DB_URL")) +# Connect to your PostgreSQL database +conn = psycopg.connect(os.getenv("NACHET_DB_URL")) +# Create a cursor object +cur = conn.cursor() + +# check if the table exists +cur.execute(""" + SELECT id,picture from "nachetdb_1.0.0".pictures +""") + +## Commit the transaction +conn.commit() + +for record in cur: + print(record) +#print(cur.fetchone()[1]) + +# Close the cursor and connection +cur.close() +conn.close() +print("done") \ No newline at end of file diff --git a/nachetDb-1.0.0/deployment-mass-import.py b/nachetDb-1.0.0/deployment-mass-import.py new file mode 100644 index 00000000..fd3a6247 --- /dev/null +++ b/nachetDb-1.0.0/deployment-mass-import.py @@ -0,0 +1,511 @@ +from datetime import date +from pydantic import BaseModel +import uuid +import yaml +import json +import os +import psycopg +from azure.storage.blob import BlobServiceClient +from PIL import Image + +# File: nachetDb-1.0.0/mass-import.py +# This script is used to import the missing metadata from an Azure container to the database + + +# Constants +container_URL="" + + +# Class to represent the files metadata + +class ClientData(BaseModel): + clientEmail: str + clientExpertise: str + +class SeedData(BaseModel): + seedID: int + seedFamily: str + seedGenus: str + seedSpecies: str + +class ImageDataindex(BaseModel): + numberOfImages: int + +class AuditTrail(BaseModel): + uploadDate: date + editedBy: str + editDate: date + changeLog: str + accessLog: str + privacyFlag: bool + +class Info(BaseModel): + userID: str + uploadDate: date + indexID: int + +class ImageData(BaseModel): + format: str + height: int + width: int + resolution: str + source: str + parent: str + +class QualityCheck(BaseModel): + imageChecksum: str + uploadCheck: bool + validData: bool + errorType: str + dataQualityScore: float + +class UserData(BaseModel): + description: str + numberOfSeeds: int + zoom: float + +class Index(BaseModel): + clientData: ClientData + imageData: ImageDataindex + seedData: SeedData + +class PIndex(BaseModel): + clientData: ClientData + imageData: ImageDataindex + seedData: SeedData + auditTrail: AuditTrail + +class Picture(BaseModel): + userData: UserData + +class PPicture(BaseModel): + userData: UserData + info: Info + imageData: ImageData + qualityCheck: QualityCheck + +class ClientFeedback(BaseModel): + correctIdentification: bool + historicalComparison: str + + +def manualMetaDataImport(picturefolder:str): + """ + Template function to do the importation process of the metadata from the Azure container to the database. + The user is prompted to input the client email, the zoom level and the number of seeds for the index. + The container needs to be downloaded locally before running this function. + """ + + # Manually define the picture folder + picturefolder = "" #manually inputted before each import sequence + + # Input the client email to identify the user or register him if needed + clientEmail = input("clientEmail: ") + userID = getUserID(clientEmail) + + # #build index + nbPic = buildIndex(picturefolder,userID) + + # upload index to database + indexID = uploadIndexDB(f'{picturefolder}/index.json',userID=userID) + indexID=1 + print("indexID : " + str(indexID)) + + # for each picture in field + zoomlevel = input("Zoom level for this index: ") + seedNumber = input("Number of seed for this index: ") + + # Get a list of files in the directory + files = [f for f in os.listdir(picturefolder) if os.path.isfile(os.path.join(picturefolder, f))] + i = 0 + # Loop through each file in the folder + for filename in files: + if filename.endswith(".tiff") or filename.endswith(".tif"): + buildPicture(picturefolder,seedNumber,zoomlevel,indexID,filename,userID) + picPath = f'{picturefolder}/{filename.removesuffix(".tiff")}.json' + #upload picture to database + uploadPictureDB(picPath,userID,indexID) + i=i+1 + + if i != nbPic: + print("Error: number of pictures processed does not match the index") + print("Number of picture processed: " + str(i)) + print("Number of picture in index: " + str(nbPic)) + else: + print("importation of " + picturefolder + "/ complete") + print("Number of picture processed: " + str(i)) + + +# Function to retrieve the userID from the database based on the email +# Returns the userID if the email is already registered, else it registers the email and returns the userID +def getUserID(email:str): + """ + Function to retrieve the userID from the database based on the email. + If the email is not already registered, it registers the email and returns the userID. + + Parameters: + - email (str): The email of the user. + + Returns: + - The userID of the user. + """ + # Connect to your PostgreSQL database + conn = psycopg.connect(os.getenv("NACHET_DB_URL")) + # Create a cursor object + cur = conn.cursor() + # Check if the email is already registered + cur.execute(f"SELECT Exists(SELECT 1 FROM \"nachetdb_1.0.0\".users WHERE email='{email}')") + if cur.fetchone()[0]: #Already registered + cur.execute(f"SELECT id FROM\"nachetdb_1.0.0\".users WHERE email='{email}'") + res = cur.fetchone()[0] + else: #Not registered -> Creates new user + cur.execute(f"INSERT INTO \"nachetdb_1.0.0\".users (id,email) VALUES (,'{email}')") + cur.execute(f"SELECT id FROM \"nachetdb_1.0.0\".users WHERE email='{email}'") + res = cur.fetchone()[0] + cur.close() + conn.close() + return res + +# Function to build the index metadata file +def buildIndex(output:str,clientID): + """ + This function builds the index needed to represent each folder (with pictures in it) of a container. + It prompts the user to input the expertise of the client and the number of images in the folder. + + Parameters: + - output (str): The path to the folder. + - clientID (str): The UUID of the client. + + Returns: + - The number of pictures in the folder that are supposed to be processed. + """ + + #clientEmail = input("clientEmail: ") + clientEmail="test@email" + print("clientEmail: " + clientEmail) + clientExpertise = input("Expertise: ") + + #Create the index metadata (sub part) normally filled by the user + clientData = ClientData(clientEmail=clientEmail,clientExpertise=clientExpertise) + nb = input("numberOfImages: ") + imageData = ImageDataindex(numberOfImages=nb) + + # ATM===> I dont have access to the seedID db + #seedID = input("SeedID: ") + seedID=0 + + family = input("Family: ") + genus = input("Genus: ") + species = input("Species: ") + seedData = SeedData(seedID=seedID,seedFamily=family,seedGenus=genus,seedSpecies=species) + + # Create the Index object + index = Index(clientData=clientData,imageData=imageData,seedData=seedData) + print("File created, name: " + output + "/index.json") + indexJson=index.dict() + + # Creating index metadata collected from the system + sysData=IndexProcessing(openIndexSystem()) + + #Append the system data to the index + indexJson.update(sysData) + + #Create the index file + createJsonIndex(index=indexJson,name="index",output=output) + return nb + +def buildPicture(output:str,number:int,level, indexID:int,name:str,userID:str): + """ + This function builds the picture metadata file (.json) for each picture in the folder. + + Parameters: + - output (str): The path to the folder. + - number (int): The number of seeds in the picture. + - level (float): The zoom level of the picture. + - indexID (int): The ID of the index. + - name (str): The name of the picture. + - userID (str): The UUID of the user. + + Returns: + None + """ + + desc= "This image was uploaded before the creation of the database and was apart of the first importation batch." + nb=number + zoom=level + userData = UserData(description=desc,numberOfSeeds=nb,zoom=zoom) + + # Create the Picture object with the user data + pic = Picture(userData=userData) + picJson=pic.dict() + print("File created, name: " + name) + picturePath=f'{output}/{name}' + + # Creating picture metadata collected from the system + picData=PictureProcessing(openPictureSystem(),userID,indexID,picturePath) + picJson.update(picData) + createJsonPicture(pic=picJson,name=name,output=output) + +def IndexProcessing(jsonData: str): + """ + Function to create the system index metadata. + + Parameters: + - jsonData (str): The JSON data of the index. + + Returns: + - The system index object populated with the metadata. + """ + today = date.today() + editedBy = "Francois Werbrouck" #not permanent, will be changed once the mass import is over + editDate = date.today() + change="" + access="" + privacy=False + return IndexSystemPopulating(jsonData,today,editedBy,editDate,change,access,privacy) + +# Function to create the system picture metadata +# Returns the system picture object populated with the metadata +def PictureProcessing(jsonData:str,userID:str,indexID:int,picturePath:str): + """ + Function to create the system picture metadata. + + Parameters: + - jsonData (str): The JSON data of the picture. + - userID (str): The UUID of the user. + - indexID (int): The ID of the index. + - picturePath (str): The path to the picture. + + Returns: + - The system picture object populated with the metadata. + """ + today=date.today() + # userID + # indexID + info=getImageProperties(picturePath) + parent="" + source=container_URL+picturePath.removesuffix("test") + format=info[2] + height=info[1] + width=info[0] + resolution="" + return PictureSystemPopulating(jsonData,today,str(userID),indexID,format,height,width,resolution,source,parent) + +# Function to populate the index metadata file provided +# Returns the index metadata object populated with the metadata +def IndexSystemPopulating(data,date:date,editedBy,editDate:date,changes:str,access,privacy): + """ + Function to populate the index metadata file provided. + + Parameters: + - data (str): The JSON data template of the index. + - date (date): The date of the upload + - editedBy (str): The name of the person who edited the file + - editDate (date): The date of the last edit + - changes (str): The changes made to the file + - access (str): The access log + + Returns: + - The index metadata object populated with the metadata. + """ + #print(data) + data["auditTrail"]["uploadDate"]=date.strftime("%Y-%m-%d") + data["auditTrail"]["editedBy"]=editedBy + data["auditTrail"]["editDate"]=editDate.strftime("%Y-%m-%d") + data["auditTrail"]["changeLog"]=changes + data["auditTrail"]["accessLog"]=access + data["auditTrail"]["privacyFlag"]=privacy + return data + +# Function to populate the picture metadata file provided +# Returns the picture metadata object populated with the metadata +def PictureSystemPopulating(data,date:date,userID:str,indexID:int,format:str,height:int,width:int,resolution:str,source:str,parent:str): + """ + Function to populate the Picture metadata file provided. + + Parameters: + - data (str): The JSON data template of the Picture. + - date (date): The date of the upload + - userID (str): The UUID of the user + - indexID (int): The ID of the index + - format (str): The format of the picture + - height (int): The height of the picture + - width (int): The width of the picture + - resolution (str): The resolution of the picture + - source (str): The source of the picture + - parent (str): The parent of the picture + + Returns: + - The Picture metadata object populated with the metadata. + """ + data["info"]["userID"]=userID + data["info"]["uploadDate"]=date.strftime("%Y-%m-%d") + data["info"]["indexID"]=indexID + data["imageData"]["height"]=height + data["imageData"]["width"]=width + data["imageData"]["format"]=format + data["imageData"]["source"]=source + data["imageData"]["parent"]=parent + data["imageData"]["resolution"]=resolution + + data["qualityCheck"]["imageChecksum"]="" + data["qualityCheck"]["uploadCheck"]=True + data["qualityCheck"]["validData"]=True + data["qualityCheck"]["errorType"]="" + data["qualityCheck"]["dataQualityScore"]=1.0 + + return data + +def getImageProperties(path:str): + """ + Function to retrieve an image's properties. + + Parameters: + - path (str): The path to the image. + + Returns: + - The image's width, height and format as a tuple. + """ + with Image.open(path) as img: + width, height = img.size + img_format = img.format + return width,height,img_format + +# Function to open the system index metadata template file +# Returns an empty system index metadata object +def openIndexSystem(): + """ + Function to open the system index metadata template file. + + Returns: + - An empty system index metadata object. + """ + with open('index-template-system.json','r') as file: + sysData=json.load(file) + #print(sysData) + return sysData + +# Function to open the system picture metadata template file +# Returns an empty system picture metadata object +def openPictureSystem(): + """ + Function to open the system picture metadata template file. + + Returns: + - An empty system picture metadata object. + """ + with open('picture-template-system.json','r') as file: + sysData=json.load(file) + #print(sysData) + return sysData + +# Create .json from Index data model +def createJsonIndex(index,name: str,output:str): + """ + Create .json from index data model to the specified output. + + Parameters: + - index (dict): The index data model. + - name (str): The name of the file. + - output (str): The path to the folder. + + Returns: + None + """ + #data=index.dict() + data = PIndex(**index) + filePath = f'{output}/{name}.json' + with open(filePath,'w') as json_file: + json.dump(index,json_file,indent=2) + +#Create .json form Picture data model +def createJsonPicture(pic,name:str,output:str): + """ + Create .json from picture data model to the specified output. + + Parameters: + - pic (dict): The picture data model. + - name (str): The name of the file. + - output (str): The path to the folder. + + Returns: + None + """ + data = PPicture(**pic) + extension=name.split(".")[-1] + filename = name.removesuffix("."+extension) + filePath = f'{output}/{filename}.json' + with open(filePath,'w') as json_file: + json.dump(pic,json_file,indent=2) + +# Function to upload the index file located @path to the database +# RETURNS the indexID of the uploaded index +def uploadIndexDB(path:str,userID:str): + """ + Upload the index.json file located at the specified path to the database. + + Parameters: + - path (str): The path to the index file. + - userID (str): The ID of the user. + + Returns: + - The ID of the index. + """ + + # Connect to your PostgreSQL database + conn = psycopg.connect(os.getenv("NACHET_DB_URL")) + + # Create a cursor object + cur = conn.cursor() + + # Open the file + with open(path, 'r') as file: + data = file.read() + + # Execute the INSERT statement + print(userID) + cur.execute("INSERT INTO \"nachetdb_1.0.0\".indexes (index, ownerID) VALUES (%s, %s)", (data, userID)) + conn.commit() + + #Retrieve the index id + cur.execute("SELECT id FROM \"nachetdb_1.0.0\".indexes ORDER BY id DESC LIMIT 1") + indexID=cur.fetchone()[0] + + cur.close() + conn.close() + return indexID + + +def uploadPictureDB(path:str,userID:str,indexID:int): + """ + Uploads the picture file located at the specified path to the database. + + Parameters: + - path (str): The path to the picture file. + - userID (str): The ID of the user. + - indexID (int): The ID of the index. + + Returns: + None + """ + # Connect to your PostgreSQL howatabase + conn = psycopg.connect(os.getenv("NACHET_DB_URL")) + + # Create a cursor object + cur = conn.cursor() + + # Open the file + with open(path, 'r') as file: + data = file.read() + + # Execute the INSERT statement + cur.execute("INSERT INTO \"nachetdb_1.0.0\".pictures (picture, indexID) VALUES (%s, %s)", (data, indexID)) + conn.commit() + + cur.close() + conn.close() + + +if __name__ == "__main__": + manualMetaDataImport("") + \ No newline at end of file diff --git a/nachetDb-1.0.0/storage-download.py b/nachetDb-1.0.0/storage-download.py new file mode 100644 index 00000000..e04d1dfd --- /dev/null +++ b/nachetDb-1.0.0/storage-download.py @@ -0,0 +1,50 @@ +import os +from azure.storage.blob import BlobServiceClient + + +def folderProcessing(storage_url,container_name): + """ + This function downloads all the files from a container in a storage account + to the local directory "test" + + This serves as a way to locally download the container files for processing and importing within the db + + Parameters: + - storage_url: the url of the storage account + - container_name: the name of the container + + Returns: None + """ + + # Create a blob service client + blob_service_client = BlobServiceClient.from_connection_string(conn_str=storage_url) + + # # Get the container client + container_client = blob_service_client.get_container_client(container_name) + if container_client.exists() : + print("Container exists") + else: + print("Container does not exist") + # print("===============================================") + # print(container_name) + # Specify the local directory + local_dir = "test" + + # List blobs in the container + blob_list = container_client.list_blobs() + i = 0 + # Iterate through each blob + for blob in blob_list: + # Download the blob + local_file_path = f"{local_dir}/{blob.name}" + os.makedirs(os.path.dirname(local_file_path), exist_ok=True) + + with open(local_file_path, "wb") as file: + blob_data = container_client.download_blob(blob=blob.name) + blob_data.readinto(file) + i= i + 1 + print("downloaded files: " + str(i) ) + +if __name__ == "__main__": + folderProcessing("","") + From 3fec061e5c655d71883a499d5d050eebcbc293e2 Mon Sep 17 00:00:00 2001 From: Francois-Werbrouck <122839953+Francois-Werbrouck@users.noreply.github.com> Date: Tue, 5 Mar 2024 13:59:04 +0000 Subject: [PATCH 17/19] Fixes #2: Changed id to uuid --- metadata-doc.md | 2 +- nachetDb-1.0.0/db-creation.py | 42 ++++++++++++------------ nachetDb-1.0.0/deployment-mass-import.py | 18 ++++++---- 3 files changed, 34 insertions(+), 28 deletions(-) diff --git a/metadata-doc.md b/metadata-doc.md index d30dc47f..66e7f0a3 100644 --- a/metadata-doc.md +++ b/metadata-doc.md @@ -294,7 +294,7 @@ erDiagram seeds{ uuid id PK string name - json info + json information } users ||--|{ indexes: uploads diff --git a/nachetDb-1.0.0/db-creation.py b/nachetDb-1.0.0/db-creation.py index ea7eb738..ddf91725 100644 --- a/nachetDb-1.0.0/db-creation.py +++ b/nachetDb-1.0.0/db-creation.py @@ -2,7 +2,7 @@ import os print(os.getenv("NACHET_DB_URL")) -# Connect to your PostgreSQL database +# Connect to your PostgreSQL database with the DB URL conn = psycopg.connect(os.getenv("NACHET_DB_URL")) # Create a cursor object cur = conn.cursor() @@ -18,34 +18,34 @@ # ) # """ % ("nachetdb_1.0.0")) -# # Create Indexes table -# cur.execute(""" -# CREATE TABLE \"%s\".indexes ( -# id SERIAL PRIMARY KEY, -# index JSON, -# ownerID uuid REFERENCES "nachetdb_1.0.0".users(id) -# ) -# """ % ("nachetdb_1.0.0")) +# Create Indexes table +cur.execute(""" + CREATE TABLE \"%s\".indexes ( + id uuid PRIMARY KEY, + index JSON, + ownerID uuid REFERENCES "nachetdb_1.0.0".users(id) + ) +""" % ("nachetdb_1.0.0")) -# # Create Pictures table -# cur.execute(""" -# CREATE TABLE \"%s\".pictures ( -# id SERIAL PRIMARY KEY, -# picture JSON, -# indexID INTEGER REFERENCES "nachetdb_1.0.0".indexes(id) -# ) -# """ % ("nachetdb_1.0.0")) +# Create Pictures table +cur.execute(""" + CREATE TABLE \"%s\".pictures ( + id uuid PRIMARY KEY, + picture JSON, + indexID uuid REFERENCES "nachetdb_1.0.0".indexes(id) + ) +""" % ("nachetdb_1.0.0")) -# check if the table exists +# check if the schema exists cur.execute(""" SELECT EXISTS ( SELECT 1 - FROM information_schema.tables - WHERE table_schema = 'nachetdb_1.0.0' - AND table_name = 'users' + FROM information_schema.schemata + WHERE schema_name = 'nachetdb_1.0.0' ) """) + ## Commit the transaction conn.commit() diff --git a/nachetDb-1.0.0/deployment-mass-import.py b/nachetDb-1.0.0/deployment-mass-import.py index fd3a6247..23c30798 100644 --- a/nachetDb-1.0.0/deployment-mass-import.py +++ b/nachetDb-1.0.0/deployment-mass-import.py @@ -42,7 +42,7 @@ class AuditTrail(BaseModel): class Info(BaseModel): userID: str uploadDate: date - indexID: int + indexID: str class ImageData(BaseModel): format: str @@ -108,7 +108,7 @@ def manualMetaDataImport(picturefolder:str): # upload index to database indexID = uploadIndexDB(f'{picturefolder}/index.json',userID=userID) - indexID=1 + #indexID=1 print("indexID : " + str(indexID)) # for each picture in field @@ -462,14 +462,17 @@ def uploadIndexDB(path:str,userID:str): with open(path, 'r') as file: data = file.read() + #Build indexID + indexID = uuid.uuid4() + # Execute the INSERT statement print(userID) - cur.execute("INSERT INTO \"nachetdb_1.0.0\".indexes (index, ownerID) VALUES (%s, %s)", (data, userID)) + cur.execute("INSERT INTO \"nachetdb_1.0.0\".indexes (id,index, ownerID) VALUES (%s,%s, %s)", (indexID,data, userID)) conn.commit() #Retrieve the index id - cur.execute("SELECT id FROM \"nachetdb_1.0.0\".indexes ORDER BY id DESC LIMIT 1") - indexID=cur.fetchone()[0] + #cur.execute("SELECT id FROM \"nachetdb_1.0.0\".indexes ORDER BY id DESC LIMIT 1") + cur.close() conn.close() @@ -498,8 +501,11 @@ def uploadPictureDB(path:str,userID:str,indexID:int): with open(path, 'r') as file: data = file.read() + #Generate pictureID + pictureID = uuid.uuid4() + # Execute the INSERT statement - cur.execute("INSERT INTO \"nachetdb_1.0.0\".pictures (picture, indexID) VALUES (%s, %s)", (data, indexID)) + cur.execute("INSERT INTO \"nachetdb_1.0.0\".pictures (id,picture, indexID) VALUES (%s,%s, %s)", (pictureID,data, indexID)) conn.commit() cur.close() From e580996defc19e47b349ff8f96c682e49273896a Mon Sep 17 00:00:00 2001 From: Francois-Werbrouck <122839953+Francois-Werbrouck@users.noreply.github.com> Date: Tue, 5 Mar 2024 19:46:45 +0000 Subject: [PATCH 18/19] Fixes #2: Added Mock Seed DB --- metadata-doc.md | 7 +-- nachetDb-1.0.0/db-creation.py | 57 +++++++++++----------- nachetDb-1.0.0/db-mock-seeds-population.py | 31 ++++++++++++ nachetDb-1.0.0/deployment-mass-import.py | 13 +++-- 4 files changed, 71 insertions(+), 37 deletions(-) create mode 100644 nachetDb-1.0.0/db-mock-seeds-population.py diff --git a/metadata-doc.md b/metadata-doc.md index 66e7f0a3..537ce3b4 100644 --- a/metadata-doc.md +++ b/metadata-doc.md @@ -278,12 +278,12 @@ erDiagram string container } indexes{ - int id PK + uuid id PK json index int ownerID FK } pictures{ - int id PK + uuid id PK json picture int indexID FK } @@ -292,7 +292,7 @@ erDiagram json feedback } seeds{ - uuid id PK + int id PK string name json information } @@ -300,6 +300,7 @@ erDiagram users ||--|{ indexes: uploads indexes ||--o{pictures: contains pictures ||--o{pictures: cropped + pictures ||--||seeds: has ``` ## Blob Storage diff --git a/nachetDb-1.0.0/db-creation.py b/nachetDb-1.0.0/db-creation.py index ddf91725..670c3bd4 100644 --- a/nachetDb-1.0.0/db-creation.py +++ b/nachetDb-1.0.0/db-creation.py @@ -8,7 +8,7 @@ cur = conn.cursor() # # Create Schema -# cur.execute("CREATE SCHEMA \"%s\"" % ("nachetdb_1.0.0")) +# cur.execute("CREATE SCHEMA \"%s\"") % ("nachetdb_0.0.1")) # #Create Users table # cur.execute(""" @@ -16,35 +16,34 @@ # id uuid PRIMARY KEY, # email VARCHAR(255) # ) -# """ % ("nachetdb_1.0.0")) - -# Create Indexes table -cur.execute(""" - CREATE TABLE \"%s\".indexes ( - id uuid PRIMARY KEY, - index JSON, - ownerID uuid REFERENCES "nachetdb_1.0.0".users(id) - ) -""" % ("nachetdb_1.0.0")) - -# Create Pictures table -cur.execute(""" - CREATE TABLE \"%s\".pictures ( - id uuid PRIMARY KEY, - picture JSON, - indexID uuid REFERENCES "nachetdb_1.0.0".indexes(id) - ) -""" % ("nachetdb_1.0.0")) - -# check if the schema exists -cur.execute(""" - SELECT EXISTS ( - SELECT 1 - FROM information_schema.schemata - WHERE schema_name = 'nachetdb_1.0.0' - ) -""") +# """ % ("nachetdb_0.0.1")) +# # Create Indexes table +# cur.execute(""" +# CREATE TABLE \"%s\".indexes ( +# id uuid PRIMARY KEY, +# index JSON, +# ownerID uuid REFERENCES "nachetdb_0.0.1".users(id) +# ) +# """ % ("nachetdb_0.0.1")) + +# # Create Pictures table +# cur.execute(""" +# CREATE TABLE \"%s\".pictures ( +# id uuid PRIMARY KEY, +# picture JSON, +# indexID uuid REFERENCES "nachetdb_0.0.1".indexes(id) +# ) +# """ % ("nachetdb_0.0.1")) + +# # check if the schema exists +# cur.execute(""" +# SELECT EXISTS ( +# SELECT 1 +# FROM information_schema.schemata +# WHERE schema_name = 'nachetdb_0.0.1' +# ) +# """) ## Commit the transaction conn.commit() diff --git a/nachetDb-1.0.0/db-mock-seeds-population.py b/nachetDb-1.0.0/db-mock-seeds-population.py new file mode 100644 index 00000000..9447f0d0 --- /dev/null +++ b/nachetDb-1.0.0/db-mock-seeds-population.py @@ -0,0 +1,31 @@ +import psycopg +import os +import uuid + +# Connect to your PostgreSQL database with the DB URL +conn = psycopg.connect(os.getenv("NACHET_DB_URL")) +# Create a cursor object +cur = conn.cursor() + +id = uuid.uuid4() + +#Query to insert a seed +query="INSERT INTO \"nachetdb_1.0.0\".seeds (name) VALUES ('Solanum nigrum')" + +#query = "Select id,name from \"nachetdb_1.0.0\".seeds" + +print(query) + +cur.execute(query) + +## Commit the transaction +conn.commit() + +for row in cur.fetchall(): + print(row) + + +# Close the cursor and connection +cur.close() +conn.close() +print("done") diff --git a/nachetDb-1.0.0/deployment-mass-import.py b/nachetDb-1.0.0/deployment-mass-import.py index 23c30798..0708663c 100644 --- a/nachetDb-1.0.0/deployment-mass-import.py +++ b/nachetDb-1.0.0/deployment-mass-import.py @@ -191,12 +191,15 @@ def buildIndex(output:str,clientID): imageData = ImageDataindex(numberOfImages=nb) # ATM===> I dont have access to the seedID db - #seedID = input("SeedID: ") - seedID=0 + seedID = input("SeedID: ") + - family = input("Family: ") - genus = input("Genus: ") - species = input("Species: ") + #family = input("Family: ") + #genus = input("Genus: ") + #species = input("Species: ") + family="" + genus="" + species="" seedData = SeedData(seedID=seedID,seedFamily=family,seedGenus=genus,seedSpecies=species) # Create the Index object From d221bab6ef2487058220b59e7f3603db7d56de6b Mon Sep 17 00:00:00 2001 From: Francois-Werbrouck <122839953+Francois-Werbrouck@users.noreply.github.com> Date: Thu, 14 Mar 2024 14:36:29 +0000 Subject: [PATCH 19/19] Fixes #2: Queries module --- .gitignore | 3675 +++++++++++++++++ db/queries/queries.py | 41 + {nachetDb-1.0.0 => db/setup}/db-creation.py | 40 +- db/setup/db-mock-seeds-population.py | 66 + db/setup/deployment-mass-import.py | 528 +++ .../setup}/storage-download.py | 22 +- file-validator.py => db/validator.py | 57 +- img/malicious-site.png | Bin 0 -> 103753 bytes index-mockData.yaml | 8 +- index.yaml | 2 +- metadata-doc.md | 123 +- nachetDb-1.0.0/db-mock-seeds-population.py | 31 - nachetDb-1.0.0/db-queries.py | 25 - nachetDb-1.0.0/deployment-mass-import.py | 520 --- 14 files changed, 4477 insertions(+), 661 deletions(-) create mode 100644 .gitignore create mode 100644 db/queries/queries.py rename {nachetDb-1.0.0 => db/setup}/db-creation.py (58%) create mode 100644 db/setup/db-mock-seeds-population.py create mode 100644 db/setup/deployment-mass-import.py rename {nachetDb-1.0.0 => db/setup}/storage-download.py (74%) rename file-validator.py => db/validator.py (60%) create mode 100644 img/malicious-site.png delete mode 100644 nachetDb-1.0.0/db-mock-seeds-population.py delete mode 100644 nachetDb-1.0.0/db-queries.py delete mode 100644 nachetDb-1.0.0/deployment-mass-import.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..8e3d19ba --- /dev/null +++ b/.gitignore @@ -0,0 +1,3675 @@ +test/2-seed/Testing/0 Brassica napus/001.json +test/2-seed/Testing/0 Brassica napus/001.tiff +test/2-seed/Testing/0 Brassica napus/004.json +test/2-seed/Testing/0 Brassica napus/004.tiff +test/2-seed/Testing/0 Brassica napus/005.json +test/2-seed/Testing/0 Brassica napus/005.tiff +test/2-seed/Testing/0 Brassica napus/006.json +test/2-seed/Testing/0 Brassica napus/006.tiff +test/2-seed/Testing/0 Brassica napus/007.json +test/2-seed/Testing/0 Brassica napus/007.tiff +test/2-seed/Testing/0 Brassica napus/008.json +test/2-seed/Testing/0 Brassica napus/008.tiff +test/2-seed/Testing/0 Brassica napus/009.json +test/2-seed/Testing/0 Brassica napus/009.tiff +test/2-seed/Testing/0 Brassica napus/010.json +test/2-seed/Testing/0 Brassica napus/010.tiff +test/2-seed/Testing/0 Brassica napus/011.json +test/2-seed/Testing/0 Brassica napus/011.tiff +test/2-seed/Testing/0 Brassica napus/012.json +test/2-seed/Testing/0 Brassica napus/012.tiff +test/2-seed/Testing/0 Brassica napus/013.json +test/2-seed/Testing/0 Brassica napus/013.tiff +test/2-seed/Testing/0 Brassica napus/014.json +test/2-seed/Testing/0 Brassica napus/014.tiff +test/2-seed/Testing/0 Brassica napus/015.json +test/2-seed/Testing/0 Brassica napus/015.tiff +test/2-seed/Testing/0 Brassica napus/016.json +test/2-seed/Testing/0 Brassica napus/016.tiff +test/2-seed/Testing/0 Brassica napus/017.json +test/2-seed/Testing/0 Brassica napus/017.tiff +test/2-seed/Testing/0 Brassica napus/018.json +test/2-seed/Testing/0 Brassica napus/018.tiff +test/2-seed/Testing/0 Brassica napus/019.json +test/2-seed/Testing/0 Brassica napus/019.tiff +test/2-seed/Testing/0 Brassica napus/020.json +test/2-seed/Testing/0 Brassica napus/020.tiff +test/2-seed/Testing/0 Brassica napus/021.json +test/2-seed/Testing/0 Brassica napus/021.tiff +test/2-seed/Testing/0 Brassica napus/022.json +test/2-seed/Testing/0 Brassica napus/022.tiff +test/2-seed/Testing/0 Brassica napus/023.json +test/2-seed/Testing/0 Brassica napus/023.tiff +test/2-seed/Testing/0 Brassica napus/024.json +test/2-seed/Testing/0 Brassica napus/024.tiff +test/2-seed/Testing/0 Brassica napus/025.json +test/2-seed/Testing/0 Brassica napus/025.tiff +test/2-seed/Testing/0 Brassica napus/026.json +test/2-seed/Testing/0 Brassica napus/026.tiff +test/2-seed/Testing/0 Brassica napus/027.json +test/2-seed/Testing/0 Brassica napus/027.tiff +test/2-seed/Testing/0 Brassica napus/028.json +test/2-seed/Testing/0 Brassica napus/028.tiff +test/2-seed/Testing/0 Brassica napus/029.json +test/2-seed/Testing/0 Brassica napus/029.tiff +test/2-seed/Testing/0 Brassica napus/030.json +test/2-seed/Testing/0 Brassica napus/030.tiff +test/2-seed/Testing/0 Brassica napus/031.json +test/2-seed/Testing/0 Brassica napus/031.tiff +test/2-seed/Testing/0 Brassica napus/032.json +test/2-seed/Testing/0 Brassica napus/032.tiff +test/2-seed/Testing/0 Brassica napus/033.json +test/2-seed/Testing/0 Brassica napus/033.tiff +test/2-seed/Testing/0 Brassica napus/034.json +test/2-seed/Testing/0 Brassica napus/034.tiff +test/2-seed/Testing/0 Brassica napus/035.json +test/2-seed/Testing/0 Brassica napus/035.tiff +test/2-seed/Testing/0 Brassica napus/036.json +test/2-seed/Testing/0 Brassica napus/036.tiff +test/2-seed/Testing/0 Brassica napus/037.json +test/2-seed/Testing/0 Brassica napus/037.tiff +test/2-seed/Testing/0 Brassica napus/038.json +test/2-seed/Testing/0 Brassica napus/038.tiff +test/2-seed/Testing/0 Brassica napus/039.json +test/2-seed/Testing/0 Brassica napus/039.tiff +test/2-seed/Testing/0 Brassica napus/040.json +test/2-seed/Testing/0 Brassica napus/040.tiff +test/2-seed/Testing/0 Brassica napus/041.json +test/2-seed/Testing/0 Brassica napus/041.tiff +test/2-seed/Testing/0 Brassica napus/042.json +test/2-seed/Testing/0 Brassica napus/042.tiff +test/2-seed/Testing/0 Brassica napus/43.json +test/2-seed/Testing/0 Brassica napus/43.tiff +test/2-seed/Testing/0 Brassica napus/44.json +test/2-seed/Testing/0 Brassica napus/44.tiff +test/2-seed/Testing/0 Brassica napus/45.json +test/2-seed/Testing/0 Brassica napus/45.tiff +test/2-seed/Testing/0 Brassica napus/46.json +test/2-seed/Testing/0 Brassica napus/46.tiff +test/2-seed/Testing/0 Brassica napus/47.json +test/2-seed/Testing/0 Brassica napus/47.tiff +test/2-seed/Testing/0 Brassica napus/48.json +test/2-seed/Testing/0 Brassica napus/48.tiff +test/2-seed/Testing/0 Brassica napus/49.json +test/2-seed/Testing/0 Brassica napus/49.tiff +test/2-seed/Testing/0 Brassica napus/50.json +test/2-seed/Testing/0 Brassica napus/50.tiff +test/2-seed/Testing/0 Brassica napus/Brassica napus (2).json +test/2-seed/Testing/0 Brassica napus/Brassica napus (2).tiff +test/2-seed/Testing/0 Brassica napus/Brassica napus.json +test/2-seed/Testing/0 Brassica napus/Brassica napus.tiff +test/2-seed/Testing/0 Brassica napus/index.json +test/2-seed/Testing/1 Brassica junsea/201.json +test/2-seed/Testing/1 Brassica junsea/201.tiff +test/2-seed/Testing/1 Brassica junsea/202.json +test/2-seed/Testing/1 Brassica junsea/202.tiff +test/2-seed/Testing/1 Brassica junsea/203.json +test/2-seed/Testing/1 Brassica junsea/203.tiff +test/2-seed/Testing/1 Brassica junsea/204.json +test/2-seed/Testing/1 Brassica junsea/204.tiff +test/2-seed/Testing/1 Brassica junsea/205.json +test/2-seed/Testing/1 Brassica junsea/205.tiff +test/2-seed/Testing/1 Brassica junsea/206.json +test/2-seed/Testing/1 Brassica junsea/206.tiff +test/2-seed/Testing/1 Brassica junsea/207.json +test/2-seed/Testing/1 Brassica junsea/207.tiff +test/2-seed/Testing/1 Brassica junsea/208.json +test/2-seed/Testing/1 Brassica junsea/208.tiff +test/2-seed/Testing/1 Brassica junsea/209.json +test/2-seed/Testing/1 Brassica junsea/209.tiff +test/2-seed/Testing/1 Brassica junsea/210.json +test/2-seed/Testing/1 Brassica junsea/210.tiff +test/2-seed/Testing/1 Brassica junsea/211.json +test/2-seed/Testing/1 Brassica junsea/211.tiff +test/2-seed/Testing/1 Brassica junsea/212.json +test/2-seed/Testing/1 Brassica junsea/212.tiff +test/2-seed/Testing/1 Brassica junsea/213.json +test/2-seed/Testing/1 Brassica junsea/213.tiff +test/2-seed/Testing/1 Brassica junsea/214.json +test/2-seed/Testing/1 Brassica junsea/214.tiff +test/2-seed/Testing/1 Brassica junsea/215.json +test/2-seed/Testing/1 Brassica junsea/215.tiff +test/2-seed/Testing/1 Brassica junsea/216.json +test/2-seed/Testing/1 Brassica junsea/216.tiff +test/2-seed/Testing/1 Brassica junsea/217.json +test/2-seed/Testing/1 Brassica junsea/217.tiff +test/2-seed/Testing/1 Brassica junsea/218.json +test/2-seed/Testing/1 Brassica junsea/218.tiff +test/2-seed/Testing/1 Brassica junsea/219.json +test/2-seed/Testing/1 Brassica junsea/219.tiff +test/2-seed/Testing/1 Brassica junsea/220.json +test/2-seed/Testing/1 Brassica junsea/220.tiff +test/2-seed/Testing/1 Brassica junsea/221.json +test/2-seed/Testing/1 Brassica junsea/221.tiff +test/2-seed/Testing/1 Brassica junsea/222.json +test/2-seed/Testing/1 Brassica junsea/222.tiff +test/2-seed/Testing/1 Brassica junsea/223.json +test/2-seed/Testing/1 Brassica junsea/223.tiff +test/2-seed/Testing/1 Brassica junsea/224.json +test/2-seed/Testing/1 Brassica junsea/224.tiff +test/2-seed/Testing/1 Brassica junsea/225.json +test/2-seed/Testing/1 Brassica junsea/225.tiff +test/2-seed/Testing/1 Brassica junsea/226.json +test/2-seed/Testing/1 Brassica junsea/226.tiff +test/2-seed/Testing/1 Brassica junsea/227.json +test/2-seed/Testing/1 Brassica junsea/227.tiff +test/2-seed/Testing/1 Brassica junsea/228.json +test/2-seed/Testing/1 Brassica junsea/228.tiff +test/2-seed/Testing/1 Brassica junsea/229.json +test/2-seed/Testing/1 Brassica junsea/229.tiff +test/2-seed/Testing/1 Brassica junsea/230.json +test/2-seed/Testing/1 Brassica junsea/230.tiff +test/2-seed/Testing/1 Brassica junsea/231.json +test/2-seed/Testing/1 Brassica junsea/231.tiff +test/2-seed/Testing/1 Brassica junsea/232.json +test/2-seed/Testing/1 Brassica junsea/232.tiff +test/2-seed/Testing/1 Brassica junsea/233.json +test/2-seed/Testing/1 Brassica junsea/233.tiff +test/2-seed/Testing/1 Brassica junsea/234.json +test/2-seed/Testing/1 Brassica junsea/234.tiff +test/2-seed/Testing/1 Brassica junsea/235.json +test/2-seed/Testing/1 Brassica junsea/235.tiff +test/2-seed/Testing/1 Brassica junsea/236.json +test/2-seed/Testing/1 Brassica junsea/236.tiff +test/2-seed/Testing/1 Brassica junsea/237.json +test/2-seed/Testing/1 Brassica junsea/237.tiff +test/2-seed/Testing/1 Brassica junsea/238.json +test/2-seed/Testing/1 Brassica junsea/238.tiff +test/2-seed/Testing/1 Brassica junsea/239.json +test/2-seed/Testing/1 Brassica junsea/239.tiff +test/2-seed/Testing/1 Brassica junsea/241.json +test/2-seed/Testing/1 Brassica junsea/241.tiff +test/2-seed/Testing/1 Brassica junsea/242.json +test/2-seed/Testing/1 Brassica junsea/242.tiff +test/2-seed/Testing/1 Brassica junsea/243.json +test/2-seed/Testing/1 Brassica junsea/243.tiff +test/2-seed/Testing/1 Brassica junsea/244.json +test/2-seed/Testing/1 Brassica junsea/244.tiff +test/2-seed/Testing/1 Brassica junsea/245.json +test/2-seed/Testing/1 Brassica junsea/245.tiff +test/2-seed/Testing/1 Brassica junsea/246.json +test/2-seed/Testing/1 Brassica junsea/246.tiff +test/2-seed/Testing/1 Brassica junsea/247.json +test/2-seed/Testing/1 Brassica junsea/247.tiff +test/2-seed/Testing/1 Brassica junsea/248.json +test/2-seed/Testing/1 Brassica junsea/248.tiff +test/2-seed/Testing/1 Brassica junsea/249.json +test/2-seed/Testing/1 Brassica junsea/249.tiff +test/2-seed/Testing/1 Brassica junsea/250.json +test/2-seed/Testing/1 Brassica junsea/250.tiff +test/2-seed/Testing/1 Brassica junsea/Brassica junsea.json +test/2-seed/Testing/1 Brassica junsea/Brassica junsea.tiff +test/2-seed/Testing/1 Brassica junsea/index.json +test/2-seed/Testing/10 Solanum nigrum/1.tiff +test/2-seed/Testing/10 Solanum nigrum/2.tiff +test/2-seed/Testing/10 Solanum nigrum/3.tiff +test/2-seed/Testing/10 Solanum nigrum/4.tiff +test/2-seed/Testing/10 Solanum nigrum/5.tiff +test/2-seed/Testing/10 Solanum nigrum/6.tiff +test/2-seed/Testing/10 Solanum nigrum/7.tiff +test/2-seed/Testing/10 Solanum nigrum/8.tiff +test/2-seed/Testing/10 Solanum nigrum/9.tiff +test/2-seed/Testing/10 Solanum nigrum/10.tiff +test/2-seed/Testing/10 Solanum nigrum/11.tiff +test/2-seed/Testing/10 Solanum nigrum/12.tiff +test/2-seed/Testing/10 Solanum nigrum/13.tiff +test/2-seed/Testing/10 Solanum nigrum/14.tiff +test/2-seed/Testing/10 Solanum nigrum/15.tiff +test/2-seed/Testing/10 Solanum nigrum/17.tiff +test/2-seed/Testing/10 Solanum nigrum/18.tiff +test/2-seed/Testing/10 Solanum nigrum/19.tiff +test/2-seed/Testing/10 Solanum nigrum/20.tiff +test/2-seed/Testing/10 Solanum nigrum/21.tiff +test/2-seed/Testing/10 Solanum nigrum/22.tiff +test/2-seed/Testing/10 Solanum nigrum/23.tiff +test/2-seed/Testing/10 Solanum nigrum/24.tiff +test/2-seed/Testing/10 Solanum nigrum/25.tiff +test/2-seed/Testing/10 Solanum nigrum/Solanum nigrum.tiff +test/2-seed/Testing/11 Solanum rostratum/1.tiff +test/2-seed/Testing/11 Solanum rostratum/2.tiff +test/2-seed/Testing/11 Solanum rostratum/3.tiff +test/2-seed/Testing/11 Solanum rostratum/4.tiff +test/2-seed/Testing/11 Solanum rostratum/5.tiff +test/2-seed/Testing/11 Solanum rostratum/6.tiff +test/2-seed/Testing/11 Solanum rostratum/7.tiff +test/2-seed/Testing/11 Solanum rostratum/9.tiff +test/2-seed/Testing/11 Solanum rostratum/10.tiff +test/2-seed/Testing/11 Solanum rostratum/11.tiff +test/2-seed/Testing/11 Solanum rostratum/12.tiff +test/2-seed/Testing/11 Solanum rostratum/13.tiff +test/2-seed/Testing/11 Solanum rostratum/14.tiff +test/2-seed/Testing/11 Solanum rostratum/15.tiff +test/2-seed/Testing/11 Solanum rostratum/16.tiff +test/2-seed/Testing/11 Solanum rostratum/17.tiff +test/2-seed/Testing/11 Solanum rostratum/18.tiff +test/2-seed/Testing/11 Solanum rostratum/19.tiff +test/2-seed/Testing/11 Solanum rostratum/20.tiff +test/2-seed/Testing/11 Solanum rostratum/21.tiff +test/2-seed/Testing/11 Solanum rostratum/22.tiff +test/2-seed/Testing/11 Solanum rostratum/23.tiff +test/2-seed/Testing/11 Solanum rostratum/24.tiff +test/2-seed/Testing/11 Solanum rostratum/25.tiff +test/2-seed/Testing/11 Solanum rostratum/Solanum rostratum.tiff +test/2-seed/Testing/12 Ambrosia artemisiifolia/1.tiff +test/2-seed/Testing/12 Ambrosia artemisiifolia/2.tiff +test/2-seed/Testing/12 Ambrosia artemisiifolia/3.tiff +test/2-seed/Testing/12 Ambrosia artemisiifolia/4.tiff +test/2-seed/Testing/12 Ambrosia artemisiifolia/5.tiff +test/2-seed/Testing/12 Ambrosia artemisiifolia/6.tiff +test/2-seed/Testing/12 Ambrosia artemisiifolia/7.tiff +test/2-seed/Testing/12 Ambrosia artemisiifolia/9.tiff +test/2-seed/Testing/12 Ambrosia artemisiifolia/10.tiff +test/2-seed/Testing/12 Ambrosia artemisiifolia/11.tiff +test/2-seed/Testing/12 Ambrosia artemisiifolia/12.tiff +test/2-seed/Testing/12 Ambrosia artemisiifolia/13.tiff +test/2-seed/Testing/12 Ambrosia artemisiifolia/14.tiff +test/2-seed/Testing/12 Ambrosia artemisiifolia/15.tiff +test/2-seed/Testing/12 Ambrosia artemisiifolia/16.tiff +test/2-seed/Testing/12 Ambrosia artemisiifolia/17.tiff +test/2-seed/Testing/12 Ambrosia artemisiifolia/18.tiff +test/2-seed/Testing/12 Ambrosia artemisiifolia/19.tiff +test/2-seed/Testing/12 Ambrosia artemisiifolia/20.tiff +test/2-seed/Testing/12 Ambrosia artemisiifolia/21.tiff +test/2-seed/Testing/12 Ambrosia artemisiifolia/22.tiff +test/2-seed/Testing/12 Ambrosia artemisiifolia/23.tiff +test/2-seed/Testing/12 Ambrosia artemisiifolia/24.tiff +test/2-seed/Testing/12 Ambrosia artemisiifolia/25.tiff +test/2-seed/Testing/12 Ambrosia artemisiifolia/Ambrosia artemisiifolia.tiff +test/2-seed/Testing/13 Ambrosia trifida/1.tiff +test/2-seed/Testing/13 Ambrosia trifida/2.tiff +test/2-seed/Testing/13 Ambrosia trifida/3.tiff +test/2-seed/Testing/13 Ambrosia trifida/4.tiff +test/2-seed/Testing/13 Ambrosia trifida/5.tiff +test/2-seed/Testing/13 Ambrosia trifida/6.tiff +test/2-seed/Testing/13 Ambrosia trifida/7.tiff +test/2-seed/Testing/13 Ambrosia trifida/8.tiff +test/2-seed/Testing/13 Ambrosia trifida/9.tiff +test/2-seed/Testing/13 Ambrosia trifida/10.tiff +test/2-seed/Testing/13 Ambrosia trifida/11.tiff +test/2-seed/Testing/13 Ambrosia trifida/12.tiff +test/2-seed/Testing/13 Ambrosia trifida/13.tiff +test/2-seed/Testing/13 Ambrosia trifida/14.tiff +test/2-seed/Testing/13 Ambrosia trifida/15.tiff +test/2-seed/Testing/13 Ambrosia trifida/17.tiff +test/2-seed/Testing/13 Ambrosia trifida/18.tiff +test/2-seed/Testing/13 Ambrosia trifida/19.tiff +test/2-seed/Testing/13 Ambrosia trifida/20.tiff +test/2-seed/Testing/13 Ambrosia trifida/21.tiff +test/2-seed/Testing/13 Ambrosia trifida/22.tiff +test/2-seed/Testing/13 Ambrosia trifida/23.tiff +test/2-seed/Testing/13 Ambrosia trifida/24.tiff +test/2-seed/Testing/13 Ambrosia trifida/25.tiff +test/2-seed/Testing/13 Ambrosia trifida/Ambrosia trifida.tiff +test/2-seed/Testing/14 Ambrosia psilostachya/1.tiff +test/2-seed/Testing/14 Ambrosia psilostachya/2.tiff +test/2-seed/Testing/14 Ambrosia psilostachya/3.tiff +test/2-seed/Testing/14 Ambrosia psilostachya/4.tiff +test/2-seed/Testing/14 Ambrosia psilostachya/5.tiff +test/2-seed/Testing/14 Ambrosia psilostachya/7.tiff +test/2-seed/Testing/14 Ambrosia psilostachya/8.tiff +test/2-seed/Testing/14 Ambrosia psilostachya/9.tiff +test/2-seed/Testing/14 Ambrosia psilostachya/10.tiff +test/2-seed/Testing/14 Ambrosia psilostachya/11.tiff +test/2-seed/Testing/14 Ambrosia psilostachya/12.tiff +test/2-seed/Testing/14 Ambrosia psilostachya/13.tiff +test/2-seed/Testing/14 Ambrosia psilostachya/14.tiff +test/2-seed/Testing/14 Ambrosia psilostachya/15.tiff +test/2-seed/Testing/14 Ambrosia psilostachya/16.tiff +test/2-seed/Testing/14 Ambrosia psilostachya/17.tiff +test/2-seed/Testing/14 Ambrosia psilostachya/18.tiff +test/2-seed/Testing/14 Ambrosia psilostachya/19.tiff +test/2-seed/Testing/14 Ambrosia psilostachya/20.tiff +test/2-seed/Testing/14 Ambrosia psilostachya/21.tiff +test/2-seed/Testing/14 Ambrosia psilostachya/22.tiff +test/2-seed/Testing/14 Ambrosia psilostachya/23.tiff +test/2-seed/Testing/14 Ambrosia psilostachya/24.tiff +test/2-seed/Testing/14 Ambrosia psilostachya/25.tiff +test/2-seed/Testing/14 Ambrosia psilostachya/Ambrosia psilostachya.tiff +test/2-seed/Testing/2 Cirsium arvense/1.json +test/2-seed/Testing/2 Cirsium arvense/1.tiff +test/2-seed/Testing/2 Cirsium arvense/2.json +test/2-seed/Testing/2 Cirsium arvense/2.tiff +test/2-seed/Testing/2 Cirsium arvense/3.json +test/2-seed/Testing/2 Cirsium arvense/3.tiff +test/2-seed/Testing/2 Cirsium arvense/4.json +test/2-seed/Testing/2 Cirsium arvense/4.tiff +test/2-seed/Testing/2 Cirsium arvense/5.json +test/2-seed/Testing/2 Cirsium arvense/5.tiff +test/2-seed/Testing/2 Cirsium arvense/6.json +test/2-seed/Testing/2 Cirsium arvense/6.tiff +test/2-seed/Testing/2 Cirsium arvense/7.json +test/2-seed/Testing/2 Cirsium arvense/7.tiff +test/2-seed/Testing/2 Cirsium arvense/8.json +test/2-seed/Testing/2 Cirsium arvense/8.tiff +test/2-seed/Testing/2 Cirsium arvense/9.json +test/2-seed/Testing/2 Cirsium arvense/9.tiff +test/2-seed/Testing/2 Cirsium arvense/10.json +test/2-seed/Testing/2 Cirsium arvense/10.tiff +test/2-seed/Testing/2 Cirsium arvense/11.json +test/2-seed/Testing/2 Cirsium arvense/11.tiff +test/2-seed/Testing/2 Cirsium arvense/12.json +test/2-seed/Testing/2 Cirsium arvense/12.tiff +test/2-seed/Testing/2 Cirsium arvense/13.json +test/2-seed/Testing/2 Cirsium arvense/13.tiff +test/2-seed/Testing/2 Cirsium arvense/14.json +test/2-seed/Testing/2 Cirsium arvense/14.tiff +test/2-seed/Testing/2 Cirsium arvense/15.json +test/2-seed/Testing/2 Cirsium arvense/15.tiff +test/2-seed/Testing/2 Cirsium arvense/16.json +test/2-seed/Testing/2 Cirsium arvense/16.tiff +test/2-seed/Testing/2 Cirsium arvense/17.json +test/2-seed/Testing/2 Cirsium arvense/17.tiff +test/2-seed/Testing/2 Cirsium arvense/18.json +test/2-seed/Testing/2 Cirsium arvense/18.tiff +test/2-seed/Testing/2 Cirsium arvense/19.json +test/2-seed/Testing/2 Cirsium arvense/19.tiff +test/2-seed/Testing/2 Cirsium arvense/20.json +test/2-seed/Testing/2 Cirsium arvense/20.tiff +test/2-seed/Testing/2 Cirsium arvense/21.json +test/2-seed/Testing/2 Cirsium arvense/21.tiff +test/2-seed/Testing/2 Cirsium arvense/23.json +test/2-seed/Testing/2 Cirsium arvense/23.tiff +test/2-seed/Testing/2 Cirsium arvense/24.json +test/2-seed/Testing/2 Cirsium arvense/24.tiff +test/2-seed/Testing/2 Cirsium arvense/25.json +test/2-seed/Testing/2 Cirsium arvense/25.tiff +test/2-seed/Testing/2 Cirsium arvense/Cirsium arvense.json +test/2-seed/Testing/2 Cirsium arvense/Cirsium arvense.tiff +test/2-seed/Testing/2 Cirsium arvense/index.json +test/2-seed/Testing/3 Cirsium vulgare/1.json +test/2-seed/Testing/3 Cirsium vulgare/1.tiff +test/2-seed/Testing/3 Cirsium vulgare/2.json +test/2-seed/Testing/3 Cirsium vulgare/2.tiff +test/2-seed/Testing/3 Cirsium vulgare/3.json +test/2-seed/Testing/3 Cirsium vulgare/3.tiff +test/2-seed/Testing/3 Cirsium vulgare/4.json +test/2-seed/Testing/3 Cirsium vulgare/4.tiff +test/2-seed/Testing/3 Cirsium vulgare/5.json +test/2-seed/Testing/3 Cirsium vulgare/5.tiff +test/2-seed/Testing/3 Cirsium vulgare/6.json +test/2-seed/Testing/3 Cirsium vulgare/6.tiff +test/2-seed/Testing/3 Cirsium vulgare/7.json +test/2-seed/Testing/3 Cirsium vulgare/7.tiff +test/2-seed/Testing/3 Cirsium vulgare/8.json +test/2-seed/Testing/3 Cirsium vulgare/8.tiff +test/2-seed/Testing/3 Cirsium vulgare/9.json +test/2-seed/Testing/3 Cirsium vulgare/9.tiff +test/2-seed/Testing/3 Cirsium vulgare/10.json +test/2-seed/Testing/3 Cirsium vulgare/10.tiff +test/2-seed/Testing/3 Cirsium vulgare/11.json +test/2-seed/Testing/3 Cirsium vulgare/11.tiff +test/2-seed/Testing/3 Cirsium vulgare/12.json +test/2-seed/Testing/3 Cirsium vulgare/12.tiff +test/2-seed/Testing/3 Cirsium vulgare/13.json +test/2-seed/Testing/3 Cirsium vulgare/13.tiff +test/2-seed/Testing/3 Cirsium vulgare/14.json +test/2-seed/Testing/3 Cirsium vulgare/14.tiff +test/2-seed/Testing/3 Cirsium vulgare/15.json +test/2-seed/Testing/3 Cirsium vulgare/15.tiff +test/2-seed/Testing/3 Cirsium vulgare/17.json +test/2-seed/Testing/3 Cirsium vulgare/17.tiff +test/2-seed/Testing/3 Cirsium vulgare/18.json +test/2-seed/Testing/3 Cirsium vulgare/18.tiff +test/2-seed/Testing/3 Cirsium vulgare/19.json +test/2-seed/Testing/3 Cirsium vulgare/19.tiff +test/2-seed/Testing/3 Cirsium vulgare/20.json +test/2-seed/Testing/3 Cirsium vulgare/20.tiff +test/2-seed/Testing/3 Cirsium vulgare/21.json +test/2-seed/Testing/3 Cirsium vulgare/21.tiff +test/2-seed/Testing/3 Cirsium vulgare/22.json +test/2-seed/Testing/3 Cirsium vulgare/22.tiff +test/2-seed/Testing/3 Cirsium vulgare/23.json +test/2-seed/Testing/3 Cirsium vulgare/23.tiff +test/2-seed/Testing/3 Cirsium vulgare/24.json +test/2-seed/Testing/3 Cirsium vulgare/24.tiff +test/2-seed/Testing/3 Cirsium vulgare/25.json +test/2-seed/Testing/3 Cirsium vulgare/25.tiff +test/2-seed/Testing/3 Cirsium vulgare/Cirsium vulgare.json +test/2-seed/Testing/3 Cirsium vulgare/Cirsium vulgare.tiff +test/2-seed/Testing/3 Cirsium vulgare/index.json +test/2-seed/Testing/4 Carduus nutans/1.tiff +test/2-seed/Testing/4 Carduus nutans/2.tiff +test/2-seed/Testing/4 Carduus nutans/3.tiff +test/2-seed/Testing/4 Carduus nutans/4.tiff +test/2-seed/Testing/4 Carduus nutans/5.tiff +test/2-seed/Testing/4 Carduus nutans/6.tiff +test/2-seed/Testing/4 Carduus nutans/7.tiff +test/2-seed/Testing/4 Carduus nutans/8.tiff +test/2-seed/Testing/4 Carduus nutans/9.tiff +test/2-seed/Testing/4 Carduus nutans/10.tiff +test/2-seed/Testing/4 Carduus nutans/11.tiff +test/2-seed/Testing/4 Carduus nutans/12.tiff +test/2-seed/Testing/4 Carduus nutans/13.tiff +test/2-seed/Testing/4 Carduus nutans/14.tiff +test/2-seed/Testing/4 Carduus nutans/15.tiff +test/2-seed/Testing/4 Carduus nutans/16.tiff +test/2-seed/Testing/4 Carduus nutans/17.tiff +test/2-seed/Testing/4 Carduus nutans/18.tiff +test/2-seed/Testing/4 Carduus nutans/19.tiff +test/2-seed/Testing/4 Carduus nutans/20.tiff +test/2-seed/Testing/4 Carduus nutans/21.tiff +test/2-seed/Testing/4 Carduus nutans/23.tiff +test/2-seed/Testing/4 Carduus nutans/24.tiff +test/2-seed/Testing/4 Carduus nutans/25.tiff +test/2-seed/Testing/4 Carduus nutans/Carduus nutans.tiff +test/2-seed/Testing/5 Bromus secalinus/001 (2).tiff +test/2-seed/Testing/5 Bromus secalinus/001.tiff +test/2-seed/Testing/5 Bromus secalinus/002 (2).tiff +test/2-seed/Testing/5 Bromus secalinus/002.tiff +test/2-seed/Testing/5 Bromus secalinus/003 (2).tiff +test/2-seed/Testing/5 Bromus secalinus/003.tiff +test/2-seed/Testing/5 Bromus secalinus/004.tiff +test/2-seed/Testing/5 Bromus secalinus/005 (2).tiff +test/2-seed/Testing/5 Bromus secalinus/005.tiff +test/2-seed/Testing/5 Bromus secalinus/006 (2).tiff +test/2-seed/Testing/5 Bromus secalinus/007 (2).tiff +test/2-seed/Testing/5 Bromus secalinus/007.tiff +test/2-seed/Testing/5 Bromus secalinus/008 (2).tiff +test/2-seed/Testing/5 Bromus secalinus/008.tiff +test/2-seed/Testing/5 Bromus secalinus/009 (2).tiff +test/2-seed/Testing/5 Bromus secalinus/009.tiff +test/2-seed/Testing/5 Bromus secalinus/010 (2).tiff +test/2-seed/Testing/5 Bromus secalinus/010.tiff +test/2-seed/Testing/5 Bromus secalinus/011.tiff +test/2-seed/Testing/5 Bromus secalinus/012 (2).tiff +test/2-seed/Testing/5 Bromus secalinus/012.tiff +test/2-seed/Testing/5 Bromus secalinus/013 (2).tiff +test/2-seed/Testing/5 Bromus secalinus/013.tiff +test/2-seed/Testing/5 Bromus secalinus/014 (2).tiff +test/2-seed/Testing/5 Bromus secalinus/014.tiff +test/2-seed/Testing/5 Bromus secalinus/015 (2).tiff +test/2-seed/Testing/5 Bromus secalinus/015.tiff +test/2-seed/Testing/5 Bromus secalinus/016 (2).tiff +test/2-seed/Testing/5 Bromus secalinus/016.tiff +test/2-seed/Testing/5 Bromus secalinus/017 (2).tiff +test/2-seed/Testing/5 Bromus secalinus/017.tiff +test/2-seed/Testing/5 Bromus secalinus/018 (2).tiff +test/2-seed/Testing/5 Bromus secalinus/018.tiff +test/2-seed/Testing/5 Bromus secalinus/019.tiff +test/2-seed/Testing/5 Bromus secalinus/020.tiff +test/2-seed/Testing/5 Bromus secalinus/021.tiff +test/2-seed/Testing/5 Bromus secalinus/022.tiff +test/2-seed/Testing/5 Bromus secalinus/023.tiff +test/2-seed/Testing/5 Bromus secalinus/024.tiff +test/2-seed/Testing/5 Bromus secalinus/025.tiff +test/2-seed/Testing/5 Bromus secalinus/026.tiff +test/2-seed/Testing/5 Bromus secalinus/251.tiff +test/2-seed/Testing/5 Bromus secalinus/252.tiff +test/2-seed/Testing/5 Bromus secalinus/253.tiff +test/2-seed/Testing/5 Bromus secalinus/254.tiff +test/2-seed/Testing/5 Bromus secalinus/255.tiff +test/2-seed/Testing/5 Bromus secalinus/256.tiff +test/2-seed/Testing/5 Bromus secalinus/257.tiff +test/2-seed/Testing/5 Bromus secalinus/258.tiff +test/2-seed/Testing/5 Bromus secalinus/259.tiff +test/2-seed/Testing/5 Bromus secalinus/260.tiff +test/2-seed/Testing/5 Bromus secalinus/501.tiff +test/2-seed/Testing/5 Bromus secalinus/502.tiff +test/2-seed/Testing/5 Bromus secalinus/503.tiff +test/2-seed/Testing/5 Bromus secalinus/504.tiff +test/2-seed/Testing/5 Bromus secalinus/505.tiff +test/2-seed/Testing/5 Bromus secalinus/506.tiff +test/2-seed/Testing/5 Bromus secalinus/507.tiff +test/2-seed/Testing/5 Bromus secalinus/508.tiff +test/2-seed/Testing/6 Bromus hordeaceus/002.tiff +test/2-seed/Testing/6 Bromus hordeaceus/003.tiff +test/2-seed/Testing/6 Bromus hordeaceus/004.tiff +test/2-seed/Testing/6 Bromus hordeaceus/005.tiff +test/2-seed/Testing/6 Bromus hordeaceus/006.tiff +test/2-seed/Testing/6 Bromus hordeaceus/007.tiff +test/2-seed/Testing/6 Bromus hordeaceus/008.tiff +test/2-seed/Testing/6 Bromus hordeaceus/009.tiff +test/2-seed/Testing/6 Bromus hordeaceus/010.tiff +test/2-seed/Testing/6 Bromus hordeaceus/011.tiff +test/2-seed/Testing/6 Bromus hordeaceus/012.tiff +test/2-seed/Testing/6 Bromus hordeaceus/013.tiff +test/2-seed/Testing/6 Bromus hordeaceus/014.tiff +test/2-seed/Testing/6 Bromus hordeaceus/016.tiff +test/2-seed/Testing/6 Bromus hordeaceus/017.tiff +test/2-seed/Testing/6 Bromus hordeaceus/018.tiff +test/2-seed/Testing/6 Bromus hordeaceus/019.tiff +test/2-seed/Testing/6 Bromus hordeaceus/020.tiff +test/2-seed/Testing/6 Bromus hordeaceus/021.tiff +test/2-seed/Testing/6 Bromus hordeaceus/022.tiff +test/2-seed/Testing/6 Bromus hordeaceus/023.tiff +test/2-seed/Testing/6 Bromus hordeaceus/024.tiff +test/2-seed/Testing/6 Bromus hordeaceus/025.tiff +test/2-seed/Testing/6 Bromus hordeaceus/026.tiff +test/2-seed/Testing/6 Bromus hordeaceus/img_20220115_125559.tiff +test/2-seed/Testing/7 Bromus japonicus/001.tiff +test/2-seed/Testing/7 Bromus japonicus/002 (2).tiff +test/2-seed/Testing/7 Bromus japonicus/002.tiff +test/2-seed/Testing/7 Bromus japonicus/003 (2).tiff +test/2-seed/Testing/7 Bromus japonicus/004 (2).tiff +test/2-seed/Testing/7 Bromus japonicus/004.tiff +test/2-seed/Testing/7 Bromus japonicus/005 (2).tiff +test/2-seed/Testing/7 Bromus japonicus/005.tiff +test/2-seed/Testing/7 Bromus japonicus/007 (2).tiff +test/2-seed/Testing/7 Bromus japonicus/007.tiff +test/2-seed/Testing/7 Bromus japonicus/008 (2).tiff +test/2-seed/Testing/7 Bromus japonicus/008.tiff +test/2-seed/Testing/7 Bromus japonicus/009.tiff +test/2-seed/Testing/7 Bromus japonicus/010 (2).tiff +test/2-seed/Testing/7 Bromus japonicus/010.tiff +test/2-seed/Testing/7 Bromus japonicus/011.tiff +test/2-seed/Testing/7 Bromus japonicus/012.tiff +test/2-seed/Testing/7 Bromus japonicus/013 (2).tiff +test/2-seed/Testing/7 Bromus japonicus/013.tiff +test/2-seed/Testing/7 Bromus japonicus/014 (2).tiff +test/2-seed/Testing/7 Bromus japonicus/014.tiff +test/2-seed/Testing/7 Bromus japonicus/015 (2).tiff +test/2-seed/Testing/7 Bromus japonicus/015.tiff +test/2-seed/Testing/7 Bromus japonicus/016.tiff +test/2-seed/Testing/7 Bromus japonicus/017 (2).tiff +test/2-seed/Testing/7 Bromus japonicus/017.tiff +test/2-seed/Testing/7 Bromus japonicus/018 (2).tiff +test/2-seed/Testing/7 Bromus japonicus/018.tiff +test/2-seed/Testing/7 Bromus japonicus/019.tiff +test/2-seed/Testing/7 Bromus japonicus/020.tiff +test/2-seed/Testing/7 Bromus japonicus/021 (2).tiff +test/2-seed/Testing/7 Bromus japonicus/021.tiff +test/2-seed/Testing/7 Bromus japonicus/022 (2).tiff +test/2-seed/Testing/7 Bromus japonicus/022.tiff +test/2-seed/Testing/7 Bromus japonicus/023.tiff +test/2-seed/Testing/7 Bromus japonicus/024 (2).tiff +test/2-seed/Testing/7 Bromus japonicus/025 (2).tiff +test/2-seed/Testing/7 Bromus japonicus/025.tiff +test/2-seed/Testing/7 Bromus japonicus/026 (2).tiff +test/2-seed/Testing/7 Bromus japonicus/026.tiff +test/2-seed/Testing/7 Bromus japonicus/027 (2).tiff +test/2-seed/Testing/7 Bromus japonicus/027.tiff +test/2-seed/Testing/7 Bromus japonicus/029.tiff +test/2-seed/Testing/7 Bromus japonicus/201.tiff +test/2-seed/Testing/7 Bromus japonicus/202.tiff +test/2-seed/Testing/7 Bromus japonicus/203.tiff +test/2-seed/Testing/7 Bromus japonicus/204.tiff +test/2-seed/Testing/7 Bromus japonicus/205.tiff +test/2-seed/Testing/7 Bromus japonicus/206.tiff +test/2-seed/Testing/7 Bromus japonicus/207.tiff +test/2-seed/Testing/8 Lolium temulentum/001.tiff +test/2-seed/Testing/8 Lolium temulentum/002 (2).tiff +test/2-seed/Testing/8 Lolium temulentum/002.tiff +test/2-seed/Testing/8 Lolium temulentum/003 (2).tiff +test/2-seed/Testing/8 Lolium temulentum/003.tiff +test/2-seed/Testing/8 Lolium temulentum/004 (2).tiff +test/2-seed/Testing/8 Lolium temulentum/004.tiff +test/2-seed/Testing/8 Lolium temulentum/005 (2).tiff +test/2-seed/Testing/8 Lolium temulentum/005.tiff +test/2-seed/Testing/8 Lolium temulentum/006.tiff +test/2-seed/Testing/8 Lolium temulentum/007 (2).tiff +test/2-seed/Testing/8 Lolium temulentum/007.tiff +test/2-seed/Testing/8 Lolium temulentum/008 (2).tiff +test/2-seed/Testing/8 Lolium temulentum/008.tiff +test/2-seed/Testing/8 Lolium temulentum/009 (2).tiff +test/2-seed/Testing/8 Lolium temulentum/009.tiff +test/2-seed/Testing/8 Lolium temulentum/010 (2).tiff +test/2-seed/Testing/8 Lolium temulentum/010.tiff +test/2-seed/Testing/8 Lolium temulentum/011.tiff +test/2-seed/Testing/8 Lolium temulentum/012.tiff +test/2-seed/Testing/8 Lolium temulentum/013 (2).tiff +test/2-seed/Testing/8 Lolium temulentum/013.tiff +test/2-seed/Testing/8 Lolium temulentum/014 (2).tiff +test/2-seed/Testing/8 Lolium temulentum/014.tiff +test/2-seed/Testing/8 Lolium temulentum/015 (2).tiff +test/2-seed/Testing/8 Lolium temulentum/015.tiff +test/2-seed/Testing/8 Lolium temulentum/016.tiff +test/2-seed/Testing/8 Lolium temulentum/017 (2).tiff +test/2-seed/Testing/8 Lolium temulentum/017.tiff +test/2-seed/Testing/8 Lolium temulentum/018 (2).tiff +test/2-seed/Testing/8 Lolium temulentum/018.tiff +test/2-seed/Testing/8 Lolium temulentum/019 (2).tiff +test/2-seed/Testing/8 Lolium temulentum/019.tiff +test/2-seed/Testing/8 Lolium temulentum/020 (2).tiff +test/2-seed/Testing/8 Lolium temulentum/020.tiff +test/2-seed/Testing/8 Lolium temulentum/021.tiff +test/2-seed/Testing/8 Lolium temulentum/022 (2).tiff +test/2-seed/Testing/8 Lolium temulentum/022.tiff +test/2-seed/Testing/8 Lolium temulentum/023 (2).tiff +test/2-seed/Testing/8 Lolium temulentum/023.tiff +test/2-seed/Testing/8 Lolium temulentum/024 (2).tiff +test/2-seed/Testing/8 Lolium temulentum/024.tiff +test/2-seed/Testing/8 Lolium temulentum/025.tiff +test/2-seed/Testing/8 Lolium temulentum/026.tiff +test/2-seed/Testing/8 Lolium temulentum/027.tiff +test/2-seed/Testing/8 Lolium temulentum/028.tiff +test/2-seed/Testing/8 Lolium temulentum/029.tiff +test/2-seed/Testing/8 Lolium temulentum/030.tiff +test/2-seed/Testing/8 Lolium temulentum/201.tiff +test/2-seed/Testing/8 Lolium temulentum/202.tiff +test/2-seed/Testing/9 Solanum carolinense/1.tiff +test/2-seed/Testing/9 Solanum carolinense/2.tiff +test/2-seed/Testing/9 Solanum carolinense/3.tiff +test/2-seed/Testing/9 Solanum carolinense/4.tiff +test/2-seed/Testing/9 Solanum carolinense/5.tiff +test/2-seed/Testing/9 Solanum carolinense/6.tiff +test/2-seed/Testing/9 Solanum carolinense/7.tiff +test/2-seed/Testing/9 Solanum carolinense/8.tiff +test/2-seed/Testing/9 Solanum carolinense/9.tiff +test/2-seed/Testing/9 Solanum carolinense/10.tiff +test/2-seed/Testing/9 Solanum carolinense/11.tiff +test/2-seed/Testing/9 Solanum carolinense/12.tiff +test/2-seed/Testing/9 Solanum carolinense/13.tiff +test/2-seed/Testing/9 Solanum carolinense/14.tiff +test/2-seed/Testing/9 Solanum carolinense/15.tiff +test/2-seed/Testing/9 Solanum carolinense/17.tiff +test/2-seed/Testing/9 Solanum carolinense/18.tiff +test/2-seed/Testing/9 Solanum carolinense/19.tiff +test/2-seed/Testing/9 Solanum carolinense/20.tiff +test/2-seed/Testing/9 Solanum carolinense/21.tiff +test/2-seed/Testing/9 Solanum carolinense/22.tiff +test/2-seed/Testing/9 Solanum carolinense/23.tiff +test/2-seed/Testing/9 Solanum carolinense/24.tiff +test/2-seed/Testing/9 Solanum carolinense/25.tiff +test/2-seed/Testing/9 Solanum carolinense/Solanum carolinense.tiff +test/2-seed/Training/0 Brassica napus/001 (2).json +test/2-seed/Training/0 Brassica napus/001 (2).tiff +test/2-seed/Training/0 Brassica napus/001 (3).json +test/2-seed/Training/0 Brassica napus/001 (3).tiff +test/2-seed/Training/0 Brassica napus/001 (4).json +test/2-seed/Training/0 Brassica napus/001 (4).tiff +test/2-seed/Training/0 Brassica napus/001 (5).json +test/2-seed/Training/0 Brassica napus/001 (5).tiff +test/2-seed/Training/0 Brassica napus/001 (6).json +test/2-seed/Training/0 Brassica napus/001 (6).tiff +test/2-seed/Training/0 Brassica napus/001.json +test/2-seed/Training/0 Brassica napus/001.tiff +test/2-seed/Training/0 Brassica napus/002 (2).json +test/2-seed/Training/0 Brassica napus/002 (2).tiff +test/2-seed/Training/0 Brassica napus/002 (3).json +test/2-seed/Training/0 Brassica napus/002 (3).tiff +test/2-seed/Training/0 Brassica napus/002 (4).json +test/2-seed/Training/0 Brassica napus/002 (4).tiff +test/2-seed/Training/0 Brassica napus/002 (5).json +test/2-seed/Training/0 Brassica napus/002 (5).tiff +test/2-seed/Training/0 Brassica napus/002 (6).json +test/2-seed/Training/0 Brassica napus/002 (6).tiff +test/2-seed/Training/0 Brassica napus/002.json +test/2-seed/Training/0 Brassica napus/002.tiff +test/2-seed/Training/0 Brassica napus/003 (2).json +test/2-seed/Training/0 Brassica napus/003 (2).tiff +test/2-seed/Training/0 Brassica napus/003 (3).json +test/2-seed/Training/0 Brassica napus/003 (3).tiff +test/2-seed/Training/0 Brassica napus/003 (4).json +test/2-seed/Training/0 Brassica napus/003 (4).tiff +test/2-seed/Training/0 Brassica napus/003 (5).json +test/2-seed/Training/0 Brassica napus/003 (5).tiff +test/2-seed/Training/0 Brassica napus/003 (6).json +test/2-seed/Training/0 Brassica napus/003 (6).tiff +test/2-seed/Training/0 Brassica napus/003.json +test/2-seed/Training/0 Brassica napus/003.tiff +test/2-seed/Training/0 Brassica napus/004 (2).json +test/2-seed/Training/0 Brassica napus/004 (2).tiff +test/2-seed/Training/0 Brassica napus/004 (3).json +test/2-seed/Training/0 Brassica napus/004 (3).tiff +test/2-seed/Training/0 Brassica napus/004 (4).json +test/2-seed/Training/0 Brassica napus/004 (4).tiff +test/2-seed/Training/0 Brassica napus/004 (5).json +test/2-seed/Training/0 Brassica napus/004 (5).tiff +test/2-seed/Training/0 Brassica napus/004 (6).json +test/2-seed/Training/0 Brassica napus/004 (6).tiff +test/2-seed/Training/0 Brassica napus/004.json +test/2-seed/Training/0 Brassica napus/004.tiff +test/2-seed/Training/0 Brassica napus/005 (2).json +test/2-seed/Training/0 Brassica napus/005 (2).tiff +test/2-seed/Training/0 Brassica napus/005 (3).json +test/2-seed/Training/0 Brassica napus/005 (3).tiff +test/2-seed/Training/0 Brassica napus/005 (4).json +test/2-seed/Training/0 Brassica napus/005 (4).tiff +test/2-seed/Training/0 Brassica napus/005 (5).json +test/2-seed/Training/0 Brassica napus/005 (5).tiff +test/2-seed/Training/0 Brassica napus/005 (6).json +test/2-seed/Training/0 Brassica napus/005 (6).tiff +test/2-seed/Training/0 Brassica napus/005.json +test/2-seed/Training/0 Brassica napus/005.tiff +test/2-seed/Training/0 Brassica napus/006 (2).json +test/2-seed/Training/0 Brassica napus/006 (2).tiff +test/2-seed/Training/0 Brassica napus/006 (3).json +test/2-seed/Training/0 Brassica napus/006 (3).tiff +test/2-seed/Training/0 Brassica napus/006 (4).json +test/2-seed/Training/0 Brassica napus/006 (4).tiff +test/2-seed/Training/0 Brassica napus/006 (5).json +test/2-seed/Training/0 Brassica napus/006 (5).tiff +test/2-seed/Training/0 Brassica napus/006 (6).json +test/2-seed/Training/0 Brassica napus/006 (6).tiff +test/2-seed/Training/0 Brassica napus/006.json +test/2-seed/Training/0 Brassica napus/006.tiff +test/2-seed/Training/0 Brassica napus/007 (2).json +test/2-seed/Training/0 Brassica napus/007 (2).tiff +test/2-seed/Training/0 Brassica napus/007 (3).json +test/2-seed/Training/0 Brassica napus/007 (3).tiff +test/2-seed/Training/0 Brassica napus/007 (4).json +test/2-seed/Training/0 Brassica napus/007 (4).tiff +test/2-seed/Training/0 Brassica napus/007 (5).json +test/2-seed/Training/0 Brassica napus/007 (5).tiff +test/2-seed/Training/0 Brassica napus/007 (6).json +test/2-seed/Training/0 Brassica napus/007 (6).tiff +test/2-seed/Training/0 Brassica napus/007.json +test/2-seed/Training/0 Brassica napus/007.tiff +test/2-seed/Training/0 Brassica napus/008 (2).json +test/2-seed/Training/0 Brassica napus/008 (2).tiff +test/2-seed/Training/0 Brassica napus/008 (3).json +test/2-seed/Training/0 Brassica napus/008 (3).tiff +test/2-seed/Training/0 Brassica napus/008 (4).json +test/2-seed/Training/0 Brassica napus/008 (4).tiff +test/2-seed/Training/0 Brassica napus/008 (5).json +test/2-seed/Training/0 Brassica napus/008 (5).tiff +test/2-seed/Training/0 Brassica napus/008 (6).json +test/2-seed/Training/0 Brassica napus/008 (6).tiff +test/2-seed/Training/0 Brassica napus/008.json +test/2-seed/Training/0 Brassica napus/008.tiff +test/2-seed/Training/0 Brassica napus/009 (2).json +test/2-seed/Training/0 Brassica napus/009 (2).tiff +test/2-seed/Training/0 Brassica napus/009 (3).json +test/2-seed/Training/0 Brassica napus/009 (3).tiff +test/2-seed/Training/0 Brassica napus/009 (4).json +test/2-seed/Training/0 Brassica napus/009 (4).tiff +test/2-seed/Training/0 Brassica napus/009 (5).json +test/2-seed/Training/0 Brassica napus/009 (5).tiff +test/2-seed/Training/0 Brassica napus/009 (6).json +test/2-seed/Training/0 Brassica napus/009 (6).tiff +test/2-seed/Training/0 Brassica napus/009.json +test/2-seed/Training/0 Brassica napus/009.tiff +test/2-seed/Training/0 Brassica napus/010 (2).json +test/2-seed/Training/0 Brassica napus/010 (2).tiff +test/2-seed/Training/0 Brassica napus/010 (3).json +test/2-seed/Training/0 Brassica napus/010 (3).tiff +test/2-seed/Training/0 Brassica napus/010 (4).json +test/2-seed/Training/0 Brassica napus/010 (4).tiff +test/2-seed/Training/0 Brassica napus/010 (5).json +test/2-seed/Training/0 Brassica napus/010 (5).tiff +test/2-seed/Training/0 Brassica napus/010 (6).json +test/2-seed/Training/0 Brassica napus/010 (6).tiff +test/2-seed/Training/0 Brassica napus/010.json +test/2-seed/Training/0 Brassica napus/010.tiff +test/2-seed/Training/0 Brassica napus/011 (2).json +test/2-seed/Training/0 Brassica napus/011 (2).tiff +test/2-seed/Training/0 Brassica napus/011 (3).json +test/2-seed/Training/0 Brassica napus/011 (3).tiff +test/2-seed/Training/0 Brassica napus/011 (4).json +test/2-seed/Training/0 Brassica napus/011 (4).tiff +test/2-seed/Training/0 Brassica napus/011 (5).json +test/2-seed/Training/0 Brassica napus/011 (5).tiff +test/2-seed/Training/0 Brassica napus/011 (6).json +test/2-seed/Training/0 Brassica napus/011 (6).tiff +test/2-seed/Training/0 Brassica napus/011.json +test/2-seed/Training/0 Brassica napus/011.tiff +test/2-seed/Training/0 Brassica napus/012 (2).json +test/2-seed/Training/0 Brassica napus/012 (2).tiff +test/2-seed/Training/0 Brassica napus/012 (3).json +test/2-seed/Training/0 Brassica napus/012 (3).tiff +test/2-seed/Training/0 Brassica napus/012 (4).json +test/2-seed/Training/0 Brassica napus/012 (4).tiff +test/2-seed/Training/0 Brassica napus/012 (5).json +test/2-seed/Training/0 Brassica napus/012 (5).tiff +test/2-seed/Training/0 Brassica napus/012 (6).json +test/2-seed/Training/0 Brassica napus/012 (6).tiff +test/2-seed/Training/0 Brassica napus/012.json +test/2-seed/Training/0 Brassica napus/012.tiff +test/2-seed/Training/0 Brassica napus/013 (2).json +test/2-seed/Training/0 Brassica napus/013 (2).tiff +test/2-seed/Training/0 Brassica napus/013 (3).json +test/2-seed/Training/0 Brassica napus/013 (3).tiff +test/2-seed/Training/0 Brassica napus/013 (4).json +test/2-seed/Training/0 Brassica napus/013 (4).tiff +test/2-seed/Training/0 Brassica napus/013 (5).json +test/2-seed/Training/0 Brassica napus/013 (5).tiff +test/2-seed/Training/0 Brassica napus/013 (6).json +test/2-seed/Training/0 Brassica napus/013 (6).tiff +test/2-seed/Training/0 Brassica napus/013.json +test/2-seed/Training/0 Brassica napus/013.tiff +test/2-seed/Training/0 Brassica napus/014 (2).json +test/2-seed/Training/0 Brassica napus/014 (2).tiff +test/2-seed/Training/0 Brassica napus/014 (3).json +test/2-seed/Training/0 Brassica napus/014 (3).tiff +test/2-seed/Training/0 Brassica napus/014 (4).json +test/2-seed/Training/0 Brassica napus/014 (4).tiff +test/2-seed/Training/0 Brassica napus/014 (5).json +test/2-seed/Training/0 Brassica napus/014 (5).tiff +test/2-seed/Training/0 Brassica napus/014 (6).json +test/2-seed/Training/0 Brassica napus/014 (6).tiff +test/2-seed/Training/0 Brassica napus/014.json +test/2-seed/Training/0 Brassica napus/014.tiff +test/2-seed/Training/0 Brassica napus/015 (2).json +test/2-seed/Training/0 Brassica napus/015 (2).tiff +test/2-seed/Training/0 Brassica napus/015 (3).json +test/2-seed/Training/0 Brassica napus/015 (3).tiff +test/2-seed/Training/0 Brassica napus/015 (4).json +test/2-seed/Training/0 Brassica napus/015 (4).tiff +test/2-seed/Training/0 Brassica napus/015 (5).json +test/2-seed/Training/0 Brassica napus/015 (5).tiff +test/2-seed/Training/0 Brassica napus/015 (6).json +test/2-seed/Training/0 Brassica napus/015 (6).tiff +test/2-seed/Training/0 Brassica napus/015.json +test/2-seed/Training/0 Brassica napus/015.tiff +test/2-seed/Training/0 Brassica napus/016 (2).json +test/2-seed/Training/0 Brassica napus/016 (2).tiff +test/2-seed/Training/0 Brassica napus/016 (3).json +test/2-seed/Training/0 Brassica napus/016 (3).tiff +test/2-seed/Training/0 Brassica napus/016 (4).json +test/2-seed/Training/0 Brassica napus/016 (4).tiff +test/2-seed/Training/0 Brassica napus/016 (5).json +test/2-seed/Training/0 Brassica napus/016 (5).tiff +test/2-seed/Training/0 Brassica napus/016 (6).json +test/2-seed/Training/0 Brassica napus/016 (6).tiff +test/2-seed/Training/0 Brassica napus/016.json +test/2-seed/Training/0 Brassica napus/016.tiff +test/2-seed/Training/0 Brassica napus/017 (2).json +test/2-seed/Training/0 Brassica napus/017 (2).tiff +test/2-seed/Training/0 Brassica napus/017 (3).json +test/2-seed/Training/0 Brassica napus/017 (3).tiff +test/2-seed/Training/0 Brassica napus/017 (4).json +test/2-seed/Training/0 Brassica napus/017 (4).tiff +test/2-seed/Training/0 Brassica napus/017 (5).json +test/2-seed/Training/0 Brassica napus/017 (5).tiff +test/2-seed/Training/0 Brassica napus/017 (6).json +test/2-seed/Training/0 Brassica napus/017 (6).tiff +test/2-seed/Training/0 Brassica napus/017.json +test/2-seed/Training/0 Brassica napus/017.tiff +test/2-seed/Training/0 Brassica napus/018 (2).json +test/2-seed/Training/0 Brassica napus/018 (2).tiff +test/2-seed/Training/0 Brassica napus/018 (3).json +test/2-seed/Training/0 Brassica napus/018 (3).tiff +test/2-seed/Training/0 Brassica napus/018 (4).json +test/2-seed/Training/0 Brassica napus/018 (4).tiff +test/2-seed/Training/0 Brassica napus/018 (5).json +test/2-seed/Training/0 Brassica napus/018 (5).tiff +test/2-seed/Training/0 Brassica napus/018 (6).json +test/2-seed/Training/0 Brassica napus/018 (6).tiff +test/2-seed/Training/0 Brassica napus/018.json +test/2-seed/Training/0 Brassica napus/018.tiff +test/2-seed/Training/0 Brassica napus/019 (2).json +test/2-seed/Training/0 Brassica napus/019 (2).tiff +test/2-seed/Training/0 Brassica napus/019 (3).json +test/2-seed/Training/0 Brassica napus/019 (3).tiff +test/2-seed/Training/0 Brassica napus/019 (4).json +test/2-seed/Training/0 Brassica napus/019 (4).tiff +test/2-seed/Training/0 Brassica napus/019 (5).json +test/2-seed/Training/0 Brassica napus/019 (5).tiff +test/2-seed/Training/0 Brassica napus/019 (6).json +test/2-seed/Training/0 Brassica napus/019 (6).tiff +test/2-seed/Training/0 Brassica napus/019.json +test/2-seed/Training/0 Brassica napus/019.tiff +test/2-seed/Training/0 Brassica napus/020 (2).json +test/2-seed/Training/0 Brassica napus/020 (2).tiff +test/2-seed/Training/0 Brassica napus/020 (3).json +test/2-seed/Training/0 Brassica napus/020 (3).tiff +test/2-seed/Training/0 Brassica napus/020 (4).json +test/2-seed/Training/0 Brassica napus/020 (4).tiff +test/2-seed/Training/0 Brassica napus/020 (5).json +test/2-seed/Training/0 Brassica napus/020 (5).tiff +test/2-seed/Training/0 Brassica napus/020 (6).json +test/2-seed/Training/0 Brassica napus/020 (6).tiff +test/2-seed/Training/0 Brassica napus/020.json +test/2-seed/Training/0 Brassica napus/020.tiff +test/2-seed/Training/0 Brassica napus/021 (2).json +test/2-seed/Training/0 Brassica napus/021 (2).tiff +test/2-seed/Training/0 Brassica napus/021 (3).json +test/2-seed/Training/0 Brassica napus/021 (3).tiff +test/2-seed/Training/0 Brassica napus/021 (4).json +test/2-seed/Training/0 Brassica napus/021 (4).tiff +test/2-seed/Training/0 Brassica napus/021 (5).json +test/2-seed/Training/0 Brassica napus/021 (5).tiff +test/2-seed/Training/0 Brassica napus/021 (6).json +test/2-seed/Training/0 Brassica napus/021 (6).tiff +test/2-seed/Training/0 Brassica napus/021.json +test/2-seed/Training/0 Brassica napus/021.tiff +test/2-seed/Training/0 Brassica napus/022 (2).json +test/2-seed/Training/0 Brassica napus/022 (2).tiff +test/2-seed/Training/0 Brassica napus/022 (3).json +test/2-seed/Training/0 Brassica napus/022 (3).tiff +test/2-seed/Training/0 Brassica napus/022 (4).json +test/2-seed/Training/0 Brassica napus/022 (4).tiff +test/2-seed/Training/0 Brassica napus/022 (5).json +test/2-seed/Training/0 Brassica napus/022 (5).tiff +test/2-seed/Training/0 Brassica napus/022 (6).json +test/2-seed/Training/0 Brassica napus/022 (6).tiff +test/2-seed/Training/0 Brassica napus/022.json +test/2-seed/Training/0 Brassica napus/022.tiff +test/2-seed/Training/0 Brassica napus/023 (2).json +test/2-seed/Training/0 Brassica napus/023 (2).tiff +test/2-seed/Training/0 Brassica napus/023 (3).json +test/2-seed/Training/0 Brassica napus/023 (3).tiff +test/2-seed/Training/0 Brassica napus/023 (4).json +test/2-seed/Training/0 Brassica napus/023 (4).tiff +test/2-seed/Training/0 Brassica napus/023 (5).json +test/2-seed/Training/0 Brassica napus/023 (5).tiff +test/2-seed/Training/0 Brassica napus/023 (6).json +test/2-seed/Training/0 Brassica napus/023 (6).tiff +test/2-seed/Training/0 Brassica napus/023.json +test/2-seed/Training/0 Brassica napus/023.tiff +test/2-seed/Training/0 Brassica napus/024 (2).json +test/2-seed/Training/0 Brassica napus/024 (2).tiff +test/2-seed/Training/0 Brassica napus/024 (3).json +test/2-seed/Training/0 Brassica napus/024 (3).tiff +test/2-seed/Training/0 Brassica napus/024 (4).json +test/2-seed/Training/0 Brassica napus/024 (4).tiff +test/2-seed/Training/0 Brassica napus/024 (5).json +test/2-seed/Training/0 Brassica napus/024 (5).tiff +test/2-seed/Training/0 Brassica napus/024 (6).json +test/2-seed/Training/0 Brassica napus/024 (6).tiff +test/2-seed/Training/0 Brassica napus/024.json +test/2-seed/Training/0 Brassica napus/024.tiff +test/2-seed/Training/0 Brassica napus/025 (2).json +test/2-seed/Training/0 Brassica napus/025 (2).tiff +test/2-seed/Training/0 Brassica napus/025 (3).json +test/2-seed/Training/0 Brassica napus/025 (3).tiff +test/2-seed/Training/0 Brassica napus/025 (4).json +test/2-seed/Training/0 Brassica napus/025 (4).tiff +test/2-seed/Training/0 Brassica napus/025 (5).json +test/2-seed/Training/0 Brassica napus/025 (5).tiff +test/2-seed/Training/0 Brassica napus/025 (6).json +test/2-seed/Training/0 Brassica napus/025 (6).tiff +test/2-seed/Training/0 Brassica napus/025.json +test/2-seed/Training/0 Brassica napus/025.tiff +test/2-seed/Training/0 Brassica napus/026 (2).json +test/2-seed/Training/0 Brassica napus/026 (2).tiff +test/2-seed/Training/0 Brassica napus/026 (3).json +test/2-seed/Training/0 Brassica napus/026 (3).tiff +test/2-seed/Training/0 Brassica napus/026 (4).json +test/2-seed/Training/0 Brassica napus/026 (4).tiff +test/2-seed/Training/0 Brassica napus/026 (5).json +test/2-seed/Training/0 Brassica napus/026 (5).tiff +test/2-seed/Training/0 Brassica napus/026 (6).json +test/2-seed/Training/0 Brassica napus/026 (6).tiff +test/2-seed/Training/0 Brassica napus/026.json +test/2-seed/Training/0 Brassica napus/026.tiff +test/2-seed/Training/0 Brassica napus/027 (2).json +test/2-seed/Training/0 Brassica napus/027 (2).tiff +test/2-seed/Training/0 Brassica napus/027 (3).json +test/2-seed/Training/0 Brassica napus/027 (3).tiff +test/2-seed/Training/0 Brassica napus/027 (4).json +test/2-seed/Training/0 Brassica napus/027 (4).tiff +test/2-seed/Training/0 Brassica napus/027 (5).json +test/2-seed/Training/0 Brassica napus/027 (5).tiff +test/2-seed/Training/0 Brassica napus/027.json +test/2-seed/Training/0 Brassica napus/027.tiff +test/2-seed/Training/0 Brassica napus/028 (2).json +test/2-seed/Training/0 Brassica napus/028 (2).tiff +test/2-seed/Training/0 Brassica napus/028 (3).json +test/2-seed/Training/0 Brassica napus/028 (3).tiff +test/2-seed/Training/0 Brassica napus/028 (4).json +test/2-seed/Training/0 Brassica napus/028 (4).tiff +test/2-seed/Training/0 Brassica napus/028 (5).json +test/2-seed/Training/0 Brassica napus/028 (5).tiff +test/2-seed/Training/0 Brassica napus/028 (6).json +test/2-seed/Training/0 Brassica napus/028 (6).tiff +test/2-seed/Training/0 Brassica napus/028.json +test/2-seed/Training/0 Brassica napus/028.tiff +test/2-seed/Training/0 Brassica napus/029 (2).json +test/2-seed/Training/0 Brassica napus/029 (2).tiff +test/2-seed/Training/0 Brassica napus/029 (3).json +test/2-seed/Training/0 Brassica napus/029 (3).tiff +test/2-seed/Training/0 Brassica napus/029 (4).json +test/2-seed/Training/0 Brassica napus/029 (4).tiff +test/2-seed/Training/0 Brassica napus/029 (5).json +test/2-seed/Training/0 Brassica napus/029 (5).tiff +test/2-seed/Training/0 Brassica napus/029 (6).json +test/2-seed/Training/0 Brassica napus/029 (6).tiff +test/2-seed/Training/0 Brassica napus/029.json +test/2-seed/Training/0 Brassica napus/029.tiff +test/2-seed/Training/0 Brassica napus/030 (2).json +test/2-seed/Training/0 Brassica napus/030 (2).tiff +test/2-seed/Training/0 Brassica napus/030 (3).json +test/2-seed/Training/0 Brassica napus/030 (3).tiff +test/2-seed/Training/0 Brassica napus/030 (4).json +test/2-seed/Training/0 Brassica napus/030 (4).tiff +test/2-seed/Training/0 Brassica napus/030 (5).json +test/2-seed/Training/0 Brassica napus/030 (5).tiff +test/2-seed/Training/0 Brassica napus/030 (6).json +test/2-seed/Training/0 Brassica napus/030 (6).tiff +test/2-seed/Training/0 Brassica napus/030.json +test/2-seed/Training/0 Brassica napus/030.tiff +test/2-seed/Training/0 Brassica napus/031 (2).json +test/2-seed/Training/0 Brassica napus/031 (2).tiff +test/2-seed/Training/0 Brassica napus/031 (3).json +test/2-seed/Training/0 Brassica napus/031 (3).tiff +test/2-seed/Training/0 Brassica napus/031 (4).json +test/2-seed/Training/0 Brassica napus/031 (4).tiff +test/2-seed/Training/0 Brassica napus/031 (5).json +test/2-seed/Training/0 Brassica napus/031 (5).tiff +test/2-seed/Training/0 Brassica napus/031 (6).json +test/2-seed/Training/0 Brassica napus/031 (6).tiff +test/2-seed/Training/0 Brassica napus/031.json +test/2-seed/Training/0 Brassica napus/031.tiff +test/2-seed/Training/0 Brassica napus/032 (2).json +test/2-seed/Training/0 Brassica napus/032 (2).tiff +test/2-seed/Training/0 Brassica napus/032 (3).json +test/2-seed/Training/0 Brassica napus/032 (3).tiff +test/2-seed/Training/0 Brassica napus/032 (4).json +test/2-seed/Training/0 Brassica napus/032 (4).tiff +test/2-seed/Training/0 Brassica napus/032 (5).json +test/2-seed/Training/0 Brassica napus/032 (5).tiff +test/2-seed/Training/0 Brassica napus/032 (6).json +test/2-seed/Training/0 Brassica napus/032 (6).tiff +test/2-seed/Training/0 Brassica napus/032.json +test/2-seed/Training/0 Brassica napus/032.tiff +test/2-seed/Training/0 Brassica napus/033 (2).json +test/2-seed/Training/0 Brassica napus/033 (2).tiff +test/2-seed/Training/0 Brassica napus/033 (3).json +test/2-seed/Training/0 Brassica napus/033 (3).tiff +test/2-seed/Training/0 Brassica napus/033 (4).json +test/2-seed/Training/0 Brassica napus/033 (4).tiff +test/2-seed/Training/0 Brassica napus/033 (5).json +test/2-seed/Training/0 Brassica napus/033 (5).tiff +test/2-seed/Training/0 Brassica napus/033 (6).json +test/2-seed/Training/0 Brassica napus/033 (6).tiff +test/2-seed/Training/0 Brassica napus/033.json +test/2-seed/Training/0 Brassica napus/033.tiff +test/2-seed/Training/0 Brassica napus/034 (2).json +test/2-seed/Training/0 Brassica napus/034 (2).tiff +test/2-seed/Training/0 Brassica napus/034 (3).json +test/2-seed/Training/0 Brassica napus/034 (3).tiff +test/2-seed/Training/0 Brassica napus/034 (4).json +test/2-seed/Training/0 Brassica napus/034 (4).tiff +test/2-seed/Training/0 Brassica napus/034 (5).json +test/2-seed/Training/0 Brassica napus/034 (5).tiff +test/2-seed/Training/0 Brassica napus/034 (6).json +test/2-seed/Training/0 Brassica napus/034 (6).tiff +test/2-seed/Training/0 Brassica napus/034.json +test/2-seed/Training/0 Brassica napus/034.tiff +test/2-seed/Training/0 Brassica napus/035 (2).json +test/2-seed/Training/0 Brassica napus/035 (2).tiff +test/2-seed/Training/0 Brassica napus/035 (3).json +test/2-seed/Training/0 Brassica napus/035 (3).tiff +test/2-seed/Training/0 Brassica napus/035 (4).json +test/2-seed/Training/0 Brassica napus/035 (4).tiff +test/2-seed/Training/0 Brassica napus/035 (5).json +test/2-seed/Training/0 Brassica napus/035 (5).tiff +test/2-seed/Training/0 Brassica napus/035 (6).json +test/2-seed/Training/0 Brassica napus/035 (6).tiff +test/2-seed/Training/0 Brassica napus/035.json +test/2-seed/Training/0 Brassica napus/035.tiff +test/2-seed/Training/0 Brassica napus/036 (2).json +test/2-seed/Training/0 Brassica napus/036 (2).tiff +test/2-seed/Training/0 Brassica napus/036 (3).json +test/2-seed/Training/0 Brassica napus/036 (3).tiff +test/2-seed/Training/0 Brassica napus/036 (4).json +test/2-seed/Training/0 Brassica napus/036 (4).tiff +test/2-seed/Training/0 Brassica napus/036 (5).json +test/2-seed/Training/0 Brassica napus/036 (5).tiff +test/2-seed/Training/0 Brassica napus/036 (6).json +test/2-seed/Training/0 Brassica napus/036 (6).tiff +test/2-seed/Training/0 Brassica napus/036.json +test/2-seed/Training/0 Brassica napus/036.tiff +test/2-seed/Training/0 Brassica napus/037 (2).json +test/2-seed/Training/0 Brassica napus/037 (2).tiff +test/2-seed/Training/0 Brassica napus/037 (3).json +test/2-seed/Training/0 Brassica napus/037 (3).tiff +test/2-seed/Training/0 Brassica napus/037 (4).json +test/2-seed/Training/0 Brassica napus/037 (4).tiff +test/2-seed/Training/0 Brassica napus/037 (5).json +test/2-seed/Training/0 Brassica napus/037 (5).tiff +test/2-seed/Training/0 Brassica napus/037 (6).json +test/2-seed/Training/0 Brassica napus/037 (6).tiff +test/2-seed/Training/0 Brassica napus/037.json +test/2-seed/Training/0 Brassica napus/037.tiff +test/2-seed/Training/0 Brassica napus/038 (2).json +test/2-seed/Training/0 Brassica napus/038 (2).tiff +test/2-seed/Training/0 Brassica napus/038 (3).json +test/2-seed/Training/0 Brassica napus/038 (3).tiff +test/2-seed/Training/0 Brassica napus/038 (4).json +test/2-seed/Training/0 Brassica napus/038 (4).tiff +test/2-seed/Training/0 Brassica napus/038 (5).json +test/2-seed/Training/0 Brassica napus/038 (5).tiff +test/2-seed/Training/0 Brassica napus/038 (6).json +test/2-seed/Training/0 Brassica napus/038 (6).tiff +test/2-seed/Training/0 Brassica napus/038.json +test/2-seed/Training/0 Brassica napus/038.tiff +test/2-seed/Training/0 Brassica napus/039 (2).json +test/2-seed/Training/0 Brassica napus/039 (2).tiff +test/2-seed/Training/0 Brassica napus/039 (3).json +test/2-seed/Training/0 Brassica napus/039 (3).tiff +test/2-seed/Training/0 Brassica napus/039 (4).json +test/2-seed/Training/0 Brassica napus/039 (4).tiff +test/2-seed/Training/0 Brassica napus/039 (5).json +test/2-seed/Training/0 Brassica napus/039 (5).tiff +test/2-seed/Training/0 Brassica napus/039 (6).json +test/2-seed/Training/0 Brassica napus/039 (6).tiff +test/2-seed/Training/0 Brassica napus/039.json +test/2-seed/Training/0 Brassica napus/039.tiff +test/2-seed/Training/0 Brassica napus/040 (2).json +test/2-seed/Training/0 Brassica napus/040 (2).tiff +test/2-seed/Training/0 Brassica napus/040 (3).json +test/2-seed/Training/0 Brassica napus/040 (3).tiff +test/2-seed/Training/0 Brassica napus/040 (4).json +test/2-seed/Training/0 Brassica napus/040 (4).tiff +test/2-seed/Training/0 Brassica napus/040 (5).json +test/2-seed/Training/0 Brassica napus/040 (5).tiff +test/2-seed/Training/0 Brassica napus/040 (6).json +test/2-seed/Training/0 Brassica napus/040 (6).tiff +test/2-seed/Training/0 Brassica napus/040.json +test/2-seed/Training/0 Brassica napus/040.tiff +test/2-seed/Training/0 Brassica napus/041 (2).json +test/2-seed/Training/0 Brassica napus/041 (2).tiff +test/2-seed/Training/0 Brassica napus/041 (3).json +test/2-seed/Training/0 Brassica napus/041 (3).tiff +test/2-seed/Training/0 Brassica napus/041 (4).json +test/2-seed/Training/0 Brassica napus/041 (4).tiff +test/2-seed/Training/0 Brassica napus/041 (5).json +test/2-seed/Training/0 Brassica napus/041 (5).tiff +test/2-seed/Training/0 Brassica napus/041 (6).json +test/2-seed/Training/0 Brassica napus/041 (6).tiff +test/2-seed/Training/0 Brassica napus/041.json +test/2-seed/Training/0 Brassica napus/041.tiff +test/2-seed/Training/0 Brassica napus/042 (2).json +test/2-seed/Training/0 Brassica napus/042 (2).tiff +test/2-seed/Training/0 Brassica napus/042 (3).json +test/2-seed/Training/0 Brassica napus/042 (3).tiff +test/2-seed/Training/0 Brassica napus/042 (4).json +test/2-seed/Training/0 Brassica napus/042 (4).tiff +test/2-seed/Training/0 Brassica napus/042 (5).json +test/2-seed/Training/0 Brassica napus/042 (5).tiff +test/2-seed/Training/0 Brassica napus/042 (6).json +test/2-seed/Training/0 Brassica napus/042 (6).tiff +test/2-seed/Training/0 Brassica napus/042.json +test/2-seed/Training/0 Brassica napus/042.tiff +test/2-seed/Training/0 Brassica napus/043 (2).json +test/2-seed/Training/0 Brassica napus/043 (2).tiff +test/2-seed/Training/0 Brassica napus/043 (3).json +test/2-seed/Training/0 Brassica napus/043 (3).tiff +test/2-seed/Training/0 Brassica napus/043 (4).json +test/2-seed/Training/0 Brassica napus/043 (4).tiff +test/2-seed/Training/0 Brassica napus/043 (5).json +test/2-seed/Training/0 Brassica napus/043 (5).tiff +test/2-seed/Training/0 Brassica napus/043 (6).json +test/2-seed/Training/0 Brassica napus/043 (6).tiff +test/2-seed/Training/0 Brassica napus/043.json +test/2-seed/Training/0 Brassica napus/043.tiff +test/2-seed/Training/0 Brassica napus/044 (2).json +test/2-seed/Training/0 Brassica napus/044 (2).tiff +test/2-seed/Training/0 Brassica napus/044 (3).json +test/2-seed/Training/0 Brassica napus/044 (3).tiff +test/2-seed/Training/0 Brassica napus/044 (4).json +test/2-seed/Training/0 Brassica napus/044 (4).tiff +test/2-seed/Training/0 Brassica napus/044 (5).json +test/2-seed/Training/0 Brassica napus/044 (5).tiff +test/2-seed/Training/0 Brassica napus/044 (6).json +test/2-seed/Training/0 Brassica napus/044 (6).tiff +test/2-seed/Training/0 Brassica napus/044.json +test/2-seed/Training/0 Brassica napus/044.tiff +test/2-seed/Training/0 Brassica napus/045 (2).json +test/2-seed/Training/0 Brassica napus/045 (2).tiff +test/2-seed/Training/0 Brassica napus/045 (3).json +test/2-seed/Training/0 Brassica napus/045 (3).tiff +test/2-seed/Training/0 Brassica napus/045 (4).json +test/2-seed/Training/0 Brassica napus/045 (4).tiff +test/2-seed/Training/0 Brassica napus/045 (5).json +test/2-seed/Training/0 Brassica napus/045 (5).tiff +test/2-seed/Training/0 Brassica napus/045.json +test/2-seed/Training/0 Brassica napus/045.tiff +test/2-seed/Training/0 Brassica napus/046 (2).json +test/2-seed/Training/0 Brassica napus/046 (2).tiff +test/2-seed/Training/0 Brassica napus/046 (3).json +test/2-seed/Training/0 Brassica napus/046 (3).tiff +test/2-seed/Training/0 Brassica napus/046 (4).json +test/2-seed/Training/0 Brassica napus/046 (4).tiff +test/2-seed/Training/0 Brassica napus/046 (5).json +test/2-seed/Training/0 Brassica napus/046 (5).tiff +test/2-seed/Training/0 Brassica napus/046.json +test/2-seed/Training/0 Brassica napus/046.tiff +test/2-seed/Training/0 Brassica napus/047 (2).json +test/2-seed/Training/0 Brassica napus/047 (2).tiff +test/2-seed/Training/0 Brassica napus/047 (3).json +test/2-seed/Training/0 Brassica napus/047 (3).tiff +test/2-seed/Training/0 Brassica napus/047 (4).json +test/2-seed/Training/0 Brassica napus/047 (4).tiff +test/2-seed/Training/0 Brassica napus/047 (5).json +test/2-seed/Training/0 Brassica napus/047 (5).tiff +test/2-seed/Training/0 Brassica napus/047.json +test/2-seed/Training/0 Brassica napus/047.tiff +test/2-seed/Training/0 Brassica napus/048 (2).json +test/2-seed/Training/0 Brassica napus/048 (2).tiff +test/2-seed/Training/0 Brassica napus/048 (3).json +test/2-seed/Training/0 Brassica napus/048 (3).tiff +test/2-seed/Training/0 Brassica napus/048 (4).json +test/2-seed/Training/0 Brassica napus/048 (4).tiff +test/2-seed/Training/0 Brassica napus/048 (5).json +test/2-seed/Training/0 Brassica napus/048 (5).tiff +test/2-seed/Training/0 Brassica napus/048.json +test/2-seed/Training/0 Brassica napus/048.tiff +test/2-seed/Training/0 Brassica napus/049 (2).json +test/2-seed/Training/0 Brassica napus/049 (2).tiff +test/2-seed/Training/0 Brassica napus/049 (3).json +test/2-seed/Training/0 Brassica napus/049 (3).tiff +test/2-seed/Training/0 Brassica napus/049 (4).json +test/2-seed/Training/0 Brassica napus/049 (4).tiff +test/2-seed/Training/0 Brassica napus/049 (5).json +test/2-seed/Training/0 Brassica napus/049 (5).tiff +test/2-seed/Training/0 Brassica napus/049.json +test/2-seed/Training/0 Brassica napus/049.tiff +test/2-seed/Training/0 Brassica napus/050 (2).json +test/2-seed/Training/0 Brassica napus/050 (2).tiff +test/2-seed/Training/0 Brassica napus/050 (3).json +test/2-seed/Training/0 Brassica napus/050 (3).tiff +test/2-seed/Training/0 Brassica napus/050 (4).json +test/2-seed/Training/0 Brassica napus/050 (4).tiff +test/2-seed/Training/0 Brassica napus/050.json +test/2-seed/Training/0 Brassica napus/050.tiff +test/2-seed/Training/0 Brassica napus/051 (2).json +test/2-seed/Training/0 Brassica napus/051 (2).tiff +test/2-seed/Training/0 Brassica napus/051 (3).json +test/2-seed/Training/0 Brassica napus/051 (3).tiff +test/2-seed/Training/0 Brassica napus/051.json +test/2-seed/Training/0 Brassica napus/051.tiff +test/2-seed/Training/0 Brassica napus/052 (2).json +test/2-seed/Training/0 Brassica napus/052 (2).tiff +test/2-seed/Training/0 Brassica napus/052 (3).json +test/2-seed/Training/0 Brassica napus/052 (3).tiff +test/2-seed/Training/0 Brassica napus/052.json +test/2-seed/Training/0 Brassica napus/052.tiff +test/2-seed/Training/0 Brassica napus/053 (2).json +test/2-seed/Training/0 Brassica napus/053 (2).tiff +test/2-seed/Training/0 Brassica napus/053 (3).json +test/2-seed/Training/0 Brassica napus/053 (3).tiff +test/2-seed/Training/0 Brassica napus/053.json +test/2-seed/Training/0 Brassica napus/053.tiff +test/2-seed/Training/0 Brassica napus/054 (2).json +test/2-seed/Training/0 Brassica napus/054 (2).tiff +test/2-seed/Training/0 Brassica napus/054 (3).json +test/2-seed/Training/0 Brassica napus/054 (3).tiff +test/2-seed/Training/0 Brassica napus/054.json +test/2-seed/Training/0 Brassica napus/054.tiff +test/2-seed/Training/0 Brassica napus/055 (2).json +test/2-seed/Training/0 Brassica napus/055 (2).tiff +test/2-seed/Training/0 Brassica napus/055 (3).json +test/2-seed/Training/0 Brassica napus/055 (3).tiff +test/2-seed/Training/0 Brassica napus/055.json +test/2-seed/Training/0 Brassica napus/055.tiff +test/2-seed/Training/0 Brassica napus/056 (2).json +test/2-seed/Training/0 Brassica napus/056 (2).tiff +test/2-seed/Training/0 Brassica napus/056 (3).json +test/2-seed/Training/0 Brassica napus/056 (3).tiff +test/2-seed/Training/0 Brassica napus/056.json +test/2-seed/Training/0 Brassica napus/056.tiff +test/2-seed/Training/0 Brassica napus/057 (2).json +test/2-seed/Training/0 Brassica napus/057 (2).tiff +test/2-seed/Training/0 Brassica napus/057 (3).json +test/2-seed/Training/0 Brassica napus/057 (3).tiff +test/2-seed/Training/0 Brassica napus/057.json +test/2-seed/Training/0 Brassica napus/057.tiff +test/2-seed/Training/0 Brassica napus/058 (2).json +test/2-seed/Training/0 Brassica napus/058 (2).tiff +test/2-seed/Training/0 Brassica napus/058 (3).json +test/2-seed/Training/0 Brassica napus/058 (3).tiff +test/2-seed/Training/0 Brassica napus/058.json +test/2-seed/Training/0 Brassica napus/058.tiff +test/2-seed/Training/0 Brassica napus/059 (2).json +test/2-seed/Training/0 Brassica napus/059 (2).tiff +test/2-seed/Training/0 Brassica napus/059.json +test/2-seed/Training/0 Brassica napus/059.tiff +test/2-seed/Training/0 Brassica napus/060 (2).json +test/2-seed/Training/0 Brassica napus/060 (2).tiff +test/2-seed/Training/0 Brassica napus/060.json +test/2-seed/Training/0 Brassica napus/060.tiff +test/2-seed/Training/0 Brassica napus/061 (2).json +test/2-seed/Training/0 Brassica napus/061 (2).tiff +test/2-seed/Training/0 Brassica napus/061.json +test/2-seed/Training/0 Brassica napus/061.tiff +test/2-seed/Training/0 Brassica napus/062 (2).json +test/2-seed/Training/0 Brassica napus/062 (2).tiff +test/2-seed/Training/0 Brassica napus/062.json +test/2-seed/Training/0 Brassica napus/062.tiff +test/2-seed/Training/0 Brassica napus/063 (2).json +test/2-seed/Training/0 Brassica napus/063 (2).tiff +test/2-seed/Training/0 Brassica napus/063.json +test/2-seed/Training/0 Brassica napus/063.tiff +test/2-seed/Training/0 Brassica napus/064 (2).json +test/2-seed/Training/0 Brassica napus/064 (2).tiff +test/2-seed/Training/0 Brassica napus/064.json +test/2-seed/Training/0 Brassica napus/064.tiff +test/2-seed/Training/0 Brassica napus/065 (2).json +test/2-seed/Training/0 Brassica napus/065 (2).tiff +test/2-seed/Training/0 Brassica napus/065.json +test/2-seed/Training/0 Brassica napus/065.tiff +test/2-seed/Training/0 Brassica napus/066 (2).json +test/2-seed/Training/0 Brassica napus/066 (2).tiff +test/2-seed/Training/0 Brassica napus/066.json +test/2-seed/Training/0 Brassica napus/066.tiff +test/2-seed/Training/0 Brassica napus/067 (2).json +test/2-seed/Training/0 Brassica napus/067 (2).tiff +test/2-seed/Training/0 Brassica napus/067.json +test/2-seed/Training/0 Brassica napus/067.tiff +test/2-seed/Training/0 Brassica napus/068 (2).json +test/2-seed/Training/0 Brassica napus/068 (2).tiff +test/2-seed/Training/0 Brassica napus/068.json +test/2-seed/Training/0 Brassica napus/068.tiff +test/2-seed/Training/0 Brassica napus/069 (2).json +test/2-seed/Training/0 Brassica napus/069 (2).tiff +test/2-seed/Training/0 Brassica napus/069.json +test/2-seed/Training/0 Brassica napus/069.tiff +test/2-seed/Training/0 Brassica napus/070 (2).json +test/2-seed/Training/0 Brassica napus/070 (2).tiff +test/2-seed/Training/0 Brassica napus/070.json +test/2-seed/Training/0 Brassica napus/070.tiff +test/2-seed/Training/0 Brassica napus/071 (2).json +test/2-seed/Training/0 Brassica napus/071 (2).tiff +test/2-seed/Training/0 Brassica napus/071.json +test/2-seed/Training/0 Brassica napus/071.tiff +test/2-seed/Training/0 Brassica napus/072 (2).json +test/2-seed/Training/0 Brassica napus/072 (2).tiff +test/2-seed/Training/0 Brassica napus/072.json +test/2-seed/Training/0 Brassica napus/072.tiff +test/2-seed/Training/0 Brassica napus/073 (2).json +test/2-seed/Training/0 Brassica napus/073 (2).tiff +test/2-seed/Training/0 Brassica napus/073.json +test/2-seed/Training/0 Brassica napus/073.tiff +test/2-seed/Training/0 Brassica napus/074 (2).json +test/2-seed/Training/0 Brassica napus/074 (2).tiff +test/2-seed/Training/0 Brassica napus/074.json +test/2-seed/Training/0 Brassica napus/074.tiff +test/2-seed/Training/0 Brassica napus/075 (2).json +test/2-seed/Training/0 Brassica napus/075 (2).tiff +test/2-seed/Training/0 Brassica napus/075.json +test/2-seed/Training/0 Brassica napus/075.tiff +test/2-seed/Training/0 Brassica napus/076 (2).json +test/2-seed/Training/0 Brassica napus/076 (2).tiff +test/2-seed/Training/0 Brassica napus/076.json +test/2-seed/Training/0 Brassica napus/076.tiff +test/2-seed/Training/0 Brassica napus/077 (2).json +test/2-seed/Training/0 Brassica napus/077 (2).tiff +test/2-seed/Training/0 Brassica napus/077.json +test/2-seed/Training/0 Brassica napus/077.tiff +test/2-seed/Training/0 Brassica napus/078 (2).json +test/2-seed/Training/0 Brassica napus/078 (2).tiff +test/2-seed/Training/0 Brassica napus/078.json +test/2-seed/Training/0 Brassica napus/078.tiff +test/2-seed/Training/0 Brassica napus/079 (2).json +test/2-seed/Training/0 Brassica napus/079 (2).tiff +test/2-seed/Training/0 Brassica napus/079.json +test/2-seed/Training/0 Brassica napus/079.tiff +test/2-seed/Training/0 Brassica napus/080 (2).json +test/2-seed/Training/0 Brassica napus/080 (2).tiff +test/2-seed/Training/0 Brassica napus/080.json +test/2-seed/Training/0 Brassica napus/080.tiff +test/2-seed/Training/0 Brassica napus/081 (2).json +test/2-seed/Training/0 Brassica napus/081 (2).tiff +test/2-seed/Training/0 Brassica napus/081.json +test/2-seed/Training/0 Brassica napus/081.tiff +test/2-seed/Training/0 Brassica napus/082 (2).json +test/2-seed/Training/0 Brassica napus/082 (2).tiff +test/2-seed/Training/0 Brassica napus/082.json +test/2-seed/Training/0 Brassica napus/082.tiff +test/2-seed/Training/0 Brassica napus/083 (2).json +test/2-seed/Training/0 Brassica napus/083 (2).tiff +test/2-seed/Training/0 Brassica napus/083.json +test/2-seed/Training/0 Brassica napus/083.tiff +test/2-seed/Training/0 Brassica napus/084 (2).json +test/2-seed/Training/0 Brassica napus/084 (2).tiff +test/2-seed/Training/0 Brassica napus/084.json +test/2-seed/Training/0 Brassica napus/084.tiff +test/2-seed/Training/0 Brassica napus/085 (2).json +test/2-seed/Training/0 Brassica napus/085 (2).tiff +test/2-seed/Training/0 Brassica napus/085.json +test/2-seed/Training/0 Brassica napus/085.tiff +test/2-seed/Training/0 Brassica napus/086 (2).json +test/2-seed/Training/0 Brassica napus/086 (2).tiff +test/2-seed/Training/0 Brassica napus/086.json +test/2-seed/Training/0 Brassica napus/086.tiff +test/2-seed/Training/0 Brassica napus/087 (2).json +test/2-seed/Training/0 Brassica napus/087 (2).tiff +test/2-seed/Training/0 Brassica napus/087.json +test/2-seed/Training/0 Brassica napus/087.tiff +test/2-seed/Training/0 Brassica napus/088 (2).json +test/2-seed/Training/0 Brassica napus/088 (2).tiff +test/2-seed/Training/0 Brassica napus/088.json +test/2-seed/Training/0 Brassica napus/088.tiff +test/2-seed/Training/0 Brassica napus/089 (2).json +test/2-seed/Training/0 Brassica napus/089 (2).tiff +test/2-seed/Training/0 Brassica napus/089.json +test/2-seed/Training/0 Brassica napus/089.tiff +test/2-seed/Training/0 Brassica napus/090 (2).json +test/2-seed/Training/0 Brassica napus/090 (2).tiff +test/2-seed/Training/0 Brassica napus/090.json +test/2-seed/Training/0 Brassica napus/090.tiff +test/2-seed/Training/0 Brassica napus/091 (2).json +test/2-seed/Training/0 Brassica napus/091 (2).tiff +test/2-seed/Training/0 Brassica napus/091.json +test/2-seed/Training/0 Brassica napus/091.tiff +test/2-seed/Training/0 Brassica napus/092 (2).json +test/2-seed/Training/0 Brassica napus/092 (2).tiff +test/2-seed/Training/0 Brassica napus/092.json +test/2-seed/Training/0 Brassica napus/092.tiff +test/2-seed/Training/0 Brassica napus/093 (2).json +test/2-seed/Training/0 Brassica napus/093 (2).tiff +test/2-seed/Training/0 Brassica napus/093.json +test/2-seed/Training/0 Brassica napus/093.tiff +test/2-seed/Training/0 Brassica napus/094 (2).json +test/2-seed/Training/0 Brassica napus/094 (2).tiff +test/2-seed/Training/0 Brassica napus/094.json +test/2-seed/Training/0 Brassica napus/094.tiff +test/2-seed/Training/0 Brassica napus/095 (2).json +test/2-seed/Training/0 Brassica napus/095 (2).tiff +test/2-seed/Training/0 Brassica napus/095.json +test/2-seed/Training/0 Brassica napus/095.tiff +test/2-seed/Training/0 Brassica napus/096 (2).json +test/2-seed/Training/0 Brassica napus/096 (2).tiff +test/2-seed/Training/0 Brassica napus/096.json +test/2-seed/Training/0 Brassica napus/096.tiff +test/2-seed/Training/0 Brassica napus/097 (2).json +test/2-seed/Training/0 Brassica napus/097 (2).tiff +test/2-seed/Training/0 Brassica napus/097.json +test/2-seed/Training/0 Brassica napus/097.tiff +test/2-seed/Training/0 Brassica napus/098 (2).json +test/2-seed/Training/0 Brassica napus/098 (2).tiff +test/2-seed/Training/0 Brassica napus/098.json +test/2-seed/Training/0 Brassica napus/098.tiff +test/2-seed/Training/0 Brassica napus/099 (2).json +test/2-seed/Training/0 Brassica napus/099 (2).tiff +test/2-seed/Training/0 Brassica napus/099.json +test/2-seed/Training/0 Brassica napus/099.tiff +test/2-seed/Training/0 Brassica napus/100 (2).json +test/2-seed/Training/0 Brassica napus/100 (2).tiff +test/2-seed/Training/0 Brassica napus/100.json +test/2-seed/Training/0 Brassica napus/100.tiff +test/2-seed/Training/0 Brassica napus/index.json +test/2-seed/Training/1 Brassica junsea/001.json +test/2-seed/Training/1 Brassica junsea/001.tiff +test/2-seed/Training/1 Brassica junsea/002.json +test/2-seed/Training/1 Brassica junsea/002.tiff +test/2-seed/Training/1 Brassica junsea/003.json +test/2-seed/Training/1 Brassica junsea/003.tiff +test/2-seed/Training/1 Brassica junsea/004.json +test/2-seed/Training/1 Brassica junsea/004.tiff +test/2-seed/Training/1 Brassica junsea/005.json +test/2-seed/Training/1 Brassica junsea/005.tiff +test/2-seed/Training/1 Brassica junsea/006.json +test/2-seed/Training/1 Brassica junsea/006.tiff +test/2-seed/Training/1 Brassica junsea/007.json +test/2-seed/Training/1 Brassica junsea/007.tiff +test/2-seed/Training/1 Brassica junsea/008.json +test/2-seed/Training/1 Brassica junsea/008.tiff +test/2-seed/Training/1 Brassica junsea/009.json +test/2-seed/Training/1 Brassica junsea/009.tiff +test/2-seed/Training/1 Brassica junsea/010.json +test/2-seed/Training/1 Brassica junsea/010.tiff +test/2-seed/Training/1 Brassica junsea/011.json +test/2-seed/Training/1 Brassica junsea/011.tiff +test/2-seed/Training/1 Brassica junsea/012.json +test/2-seed/Training/1 Brassica junsea/012.tiff +test/2-seed/Training/1 Brassica junsea/013.json +test/2-seed/Training/1 Brassica junsea/013.tiff +test/2-seed/Training/1 Brassica junsea/014.json +test/2-seed/Training/1 Brassica junsea/014.tiff +test/2-seed/Training/1 Brassica junsea/015.json +test/2-seed/Training/1 Brassica junsea/015.tiff +test/2-seed/Training/1 Brassica junsea/016.json +test/2-seed/Training/1 Brassica junsea/016.tiff +test/2-seed/Training/1 Brassica junsea/017.json +test/2-seed/Training/1 Brassica junsea/017.tiff +test/2-seed/Training/1 Brassica junsea/018.json +test/2-seed/Training/1 Brassica junsea/018.tiff +test/2-seed/Training/1 Brassica junsea/019.json +test/2-seed/Training/1 Brassica junsea/019.tiff +test/2-seed/Training/1 Brassica junsea/020.json +test/2-seed/Training/1 Brassica junsea/020.tiff +test/2-seed/Training/1 Brassica junsea/021.json +test/2-seed/Training/1 Brassica junsea/021.tiff +test/2-seed/Training/1 Brassica junsea/022.json +test/2-seed/Training/1 Brassica junsea/022.tiff +test/2-seed/Training/1 Brassica junsea/023.json +test/2-seed/Training/1 Brassica junsea/023.tiff +test/2-seed/Training/1 Brassica junsea/024.json +test/2-seed/Training/1 Brassica junsea/024.tiff +test/2-seed/Training/1 Brassica junsea/025.json +test/2-seed/Training/1 Brassica junsea/025.tiff +test/2-seed/Training/1 Brassica junsea/026.json +test/2-seed/Training/1 Brassica junsea/026.tiff +test/2-seed/Training/1 Brassica junsea/027.json +test/2-seed/Training/1 Brassica junsea/027.tiff +test/2-seed/Training/1 Brassica junsea/28.json +test/2-seed/Training/1 Brassica junsea/28.tiff +test/2-seed/Training/1 Brassica junsea/29.json +test/2-seed/Training/1 Brassica junsea/29.tiff +test/2-seed/Training/1 Brassica junsea/30.json +test/2-seed/Training/1 Brassica junsea/30.tiff +test/2-seed/Training/1 Brassica junsea/31.json +test/2-seed/Training/1 Brassica junsea/31.tiff +test/2-seed/Training/1 Brassica junsea/32.json +test/2-seed/Training/1 Brassica junsea/32.tiff +test/2-seed/Training/1 Brassica junsea/33.json +test/2-seed/Training/1 Brassica junsea/33.tiff +test/2-seed/Training/1 Brassica junsea/34.json +test/2-seed/Training/1 Brassica junsea/34.tiff +test/2-seed/Training/1 Brassica junsea/35.json +test/2-seed/Training/1 Brassica junsea/35.tiff +test/2-seed/Training/1 Brassica junsea/36.json +test/2-seed/Training/1 Brassica junsea/36.tiff +test/2-seed/Training/1 Brassica junsea/38.json +test/2-seed/Training/1 Brassica junsea/38.tiff +test/2-seed/Training/1 Brassica junsea/39.json +test/2-seed/Training/1 Brassica junsea/39.tiff +test/2-seed/Training/1 Brassica junsea/40.json +test/2-seed/Training/1 Brassica junsea/40.tiff +test/2-seed/Training/1 Brassica junsea/41.json +test/2-seed/Training/1 Brassica junsea/41.tiff +test/2-seed/Training/1 Brassica junsea/42.json +test/2-seed/Training/1 Brassica junsea/42.tiff +test/2-seed/Training/1 Brassica junsea/43.json +test/2-seed/Training/1 Brassica junsea/43.tiff +test/2-seed/Training/1 Brassica junsea/44.json +test/2-seed/Training/1 Brassica junsea/44.tiff +test/2-seed/Training/1 Brassica junsea/45.json +test/2-seed/Training/1 Brassica junsea/45.tiff +test/2-seed/Training/1 Brassica junsea/46.json +test/2-seed/Training/1 Brassica junsea/46.tiff +test/2-seed/Training/1 Brassica junsea/47.json +test/2-seed/Training/1 Brassica junsea/47.tiff +test/2-seed/Training/1 Brassica junsea/48.json +test/2-seed/Training/1 Brassica junsea/48.tiff +test/2-seed/Training/1 Brassica junsea/49.json +test/2-seed/Training/1 Brassica junsea/49.tiff +test/2-seed/Training/1 Brassica junsea/50.json +test/2-seed/Training/1 Brassica junsea/50.tiff +test/2-seed/Training/1 Brassica junsea/51.json +test/2-seed/Training/1 Brassica junsea/51.tiff +test/2-seed/Training/1 Brassica junsea/52.json +test/2-seed/Training/1 Brassica junsea/52.tiff +test/2-seed/Training/1 Brassica junsea/53.json +test/2-seed/Training/1 Brassica junsea/53.tiff +test/2-seed/Training/1 Brassica junsea/54.json +test/2-seed/Training/1 Brassica junsea/54.tiff +test/2-seed/Training/1 Brassica junsea/55.json +test/2-seed/Training/1 Brassica junsea/55.tiff +test/2-seed/Training/1 Brassica junsea/56 (2).json +test/2-seed/Training/1 Brassica junsea/56 (2).tiff +test/2-seed/Training/1 Brassica junsea/56.json +test/2-seed/Training/1 Brassica junsea/56.tiff +test/2-seed/Training/1 Brassica junsea/57.json +test/2-seed/Training/1 Brassica junsea/57.tiff +test/2-seed/Training/1 Brassica junsea/58.json +test/2-seed/Training/1 Brassica junsea/58.tiff +test/2-seed/Training/1 Brassica junsea/59.json +test/2-seed/Training/1 Brassica junsea/59.tiff +test/2-seed/Training/1 Brassica junsea/60.json +test/2-seed/Training/1 Brassica junsea/60.tiff +test/2-seed/Training/1 Brassica junsea/61.json +test/2-seed/Training/1 Brassica junsea/61.tiff +test/2-seed/Training/1 Brassica junsea/62.json +test/2-seed/Training/1 Brassica junsea/62.tiff +test/2-seed/Training/1 Brassica junsea/63.json +test/2-seed/Training/1 Brassica junsea/63.tiff +test/2-seed/Training/1 Brassica junsea/64.json +test/2-seed/Training/1 Brassica junsea/64.tiff +test/2-seed/Training/1 Brassica junsea/65.json +test/2-seed/Training/1 Brassica junsea/65.tiff +test/2-seed/Training/1 Brassica junsea/66.json +test/2-seed/Training/1 Brassica junsea/66.tiff +test/2-seed/Training/1 Brassica junsea/67.json +test/2-seed/Training/1 Brassica junsea/67.tiff +test/2-seed/Training/1 Brassica junsea/68.json +test/2-seed/Training/1 Brassica junsea/68.tiff +test/2-seed/Training/1 Brassica junsea/69.json +test/2-seed/Training/1 Brassica junsea/69.tiff +test/2-seed/Training/1 Brassica junsea/70.json +test/2-seed/Training/1 Brassica junsea/70.tiff +test/2-seed/Training/1 Brassica junsea/71.json +test/2-seed/Training/1 Brassica junsea/71.tiff +test/2-seed/Training/1 Brassica junsea/72.json +test/2-seed/Training/1 Brassica junsea/72.tiff +test/2-seed/Training/1 Brassica junsea/73.json +test/2-seed/Training/1 Brassica junsea/73.tiff +test/2-seed/Training/1 Brassica junsea/74.json +test/2-seed/Training/1 Brassica junsea/74.tiff +test/2-seed/Training/1 Brassica junsea/75.json +test/2-seed/Training/1 Brassica junsea/75.tiff +test/2-seed/Training/1 Brassica junsea/76.json +test/2-seed/Training/1 Brassica junsea/76.tiff +test/2-seed/Training/1 Brassica junsea/77.json +test/2-seed/Training/1 Brassica junsea/77.tiff +test/2-seed/Training/1 Brassica junsea/78.json +test/2-seed/Training/1 Brassica junsea/78.tiff +test/2-seed/Training/1 Brassica junsea/79.json +test/2-seed/Training/1 Brassica junsea/79.tiff +test/2-seed/Training/1 Brassica junsea/80.json +test/2-seed/Training/1 Brassica junsea/80.tiff +test/2-seed/Training/1 Brassica junsea/81.json +test/2-seed/Training/1 Brassica junsea/81.tiff +test/2-seed/Training/1 Brassica junsea/82.json +test/2-seed/Training/1 Brassica junsea/82.tiff +test/2-seed/Training/1 Brassica junsea/83.json +test/2-seed/Training/1 Brassica junsea/83.tiff +test/2-seed/Training/1 Brassica junsea/84.json +test/2-seed/Training/1 Brassica junsea/84.tiff +test/2-seed/Training/1 Brassica junsea/85.json +test/2-seed/Training/1 Brassica junsea/85.tiff +test/2-seed/Training/1 Brassica junsea/86.json +test/2-seed/Training/1 Brassica junsea/86.tiff +test/2-seed/Training/1 Brassica junsea/87.json +test/2-seed/Training/1 Brassica junsea/87.tiff +test/2-seed/Training/1 Brassica junsea/88.json +test/2-seed/Training/1 Brassica junsea/88.tiff +test/2-seed/Training/1 Brassica junsea/89.json +test/2-seed/Training/1 Brassica junsea/89.tiff +test/2-seed/Training/1 Brassica junsea/90.json +test/2-seed/Training/1 Brassica junsea/90.tiff +test/2-seed/Training/1 Brassica junsea/91.json +test/2-seed/Training/1 Brassica junsea/91.tiff +test/2-seed/Training/1 Brassica junsea/92.json +test/2-seed/Training/1 Brassica junsea/92.tiff +test/2-seed/Training/1 Brassica junsea/93.json +test/2-seed/Training/1 Brassica junsea/93.tiff +test/2-seed/Training/1 Brassica junsea/94.json +test/2-seed/Training/1 Brassica junsea/94.tiff +test/2-seed/Training/1 Brassica junsea/95.json +test/2-seed/Training/1 Brassica junsea/95.tiff +test/2-seed/Training/1 Brassica junsea/96.json +test/2-seed/Training/1 Brassica junsea/96.tiff +test/2-seed/Training/1 Brassica junsea/97.json +test/2-seed/Training/1 Brassica junsea/97.tiff +test/2-seed/Training/1 Brassica junsea/98.json +test/2-seed/Training/1 Brassica junsea/98.tiff +test/2-seed/Training/1 Brassica junsea/99.json +test/2-seed/Training/1 Brassica junsea/99.tiff +test/2-seed/Training/1 Brassica junsea/100.json +test/2-seed/Training/1 Brassica junsea/100.tiff +test/2-seed/Training/1 Brassica junsea/101.json +test/2-seed/Training/1 Brassica junsea/101.tiff +test/2-seed/Training/1 Brassica junsea/102.json +test/2-seed/Training/1 Brassica junsea/102.tiff +test/2-seed/Training/1 Brassica junsea/103.json +test/2-seed/Training/1 Brassica junsea/103.tiff +test/2-seed/Training/1 Brassica junsea/104.json +test/2-seed/Training/1 Brassica junsea/104.tiff +test/2-seed/Training/1 Brassica junsea/105.json +test/2-seed/Training/1 Brassica junsea/105.tiff +test/2-seed/Training/1 Brassica junsea/106.json +test/2-seed/Training/1 Brassica junsea/106.tiff +test/2-seed/Training/1 Brassica junsea/107.json +test/2-seed/Training/1 Brassica junsea/107.tiff +test/2-seed/Training/1 Brassica junsea/108.json +test/2-seed/Training/1 Brassica junsea/108.tiff +test/2-seed/Training/1 Brassica junsea/109.json +test/2-seed/Training/1 Brassica junsea/109.tiff +test/2-seed/Training/1 Brassica junsea/110.json +test/2-seed/Training/1 Brassica junsea/110.tiff +test/2-seed/Training/1 Brassica junsea/111.json +test/2-seed/Training/1 Brassica junsea/111.tiff +test/2-seed/Training/1 Brassica junsea/112.json +test/2-seed/Training/1 Brassica junsea/112.tiff +test/2-seed/Training/1 Brassica junsea/113.json +test/2-seed/Training/1 Brassica junsea/113.tiff +test/2-seed/Training/1 Brassica junsea/114.json +test/2-seed/Training/1 Brassica junsea/114.tiff +test/2-seed/Training/1 Brassica junsea/115.json +test/2-seed/Training/1 Brassica junsea/115.tiff +test/2-seed/Training/1 Brassica junsea/116.json +test/2-seed/Training/1 Brassica junsea/116.tiff +test/2-seed/Training/1 Brassica junsea/117.json +test/2-seed/Training/1 Brassica junsea/117.tiff +test/2-seed/Training/1 Brassica junsea/118.json +test/2-seed/Training/1 Brassica junsea/118.tiff +test/2-seed/Training/1 Brassica junsea/119.json +test/2-seed/Training/1 Brassica junsea/119.tiff +test/2-seed/Training/1 Brassica junsea/120.json +test/2-seed/Training/1 Brassica junsea/120.tiff +test/2-seed/Training/1 Brassica junsea/121.json +test/2-seed/Training/1 Brassica junsea/121.tiff +test/2-seed/Training/1 Brassica junsea/122.json +test/2-seed/Training/1 Brassica junsea/122.tiff +test/2-seed/Training/1 Brassica junsea/123.json +test/2-seed/Training/1 Brassica junsea/123.tiff +test/2-seed/Training/1 Brassica junsea/124.json +test/2-seed/Training/1 Brassica junsea/124.tiff +test/2-seed/Training/1 Brassica junsea/125.json +test/2-seed/Training/1 Brassica junsea/125.tiff +test/2-seed/Training/1 Brassica junsea/126.json +test/2-seed/Training/1 Brassica junsea/126.tiff +test/2-seed/Training/1 Brassica junsea/127.json +test/2-seed/Training/1 Brassica junsea/127.tiff +test/2-seed/Training/1 Brassica junsea/128.json +test/2-seed/Training/1 Brassica junsea/128.tiff +test/2-seed/Training/1 Brassica junsea/129.json +test/2-seed/Training/1 Brassica junsea/129.tiff +test/2-seed/Training/1 Brassica junsea/130.json +test/2-seed/Training/1 Brassica junsea/130.tiff +test/2-seed/Training/1 Brassica junsea/131.json +test/2-seed/Training/1 Brassica junsea/131.tiff +test/2-seed/Training/1 Brassica junsea/132.json +test/2-seed/Training/1 Brassica junsea/132.tiff +test/2-seed/Training/1 Brassica junsea/133.json +test/2-seed/Training/1 Brassica junsea/133.tiff +test/2-seed/Training/1 Brassica junsea/134.json +test/2-seed/Training/1 Brassica junsea/134.tiff +test/2-seed/Training/1 Brassica junsea/135.json +test/2-seed/Training/1 Brassica junsea/135.tiff +test/2-seed/Training/1 Brassica junsea/136.json +test/2-seed/Training/1 Brassica junsea/136.tiff +test/2-seed/Training/1 Brassica junsea/137.json +test/2-seed/Training/1 Brassica junsea/137.tiff +test/2-seed/Training/1 Brassica junsea/138.json +test/2-seed/Training/1 Brassica junsea/138.tiff +test/2-seed/Training/1 Brassica junsea/139.json +test/2-seed/Training/1 Brassica junsea/139.tiff +test/2-seed/Training/1 Brassica junsea/140.json +test/2-seed/Training/1 Brassica junsea/140.tiff +test/2-seed/Training/1 Brassica junsea/141.json +test/2-seed/Training/1 Brassica junsea/141.tiff +test/2-seed/Training/1 Brassica junsea/142.json +test/2-seed/Training/1 Brassica junsea/142.tiff +test/2-seed/Training/1 Brassica junsea/143.json +test/2-seed/Training/1 Brassica junsea/143.tiff +test/2-seed/Training/1 Brassica junsea/144.json +test/2-seed/Training/1 Brassica junsea/144.tiff +test/2-seed/Training/1 Brassica junsea/145.json +test/2-seed/Training/1 Brassica junsea/145.tiff +test/2-seed/Training/1 Brassica junsea/146.json +test/2-seed/Training/1 Brassica junsea/146.tiff +test/2-seed/Training/1 Brassica junsea/147.json +test/2-seed/Training/1 Brassica junsea/147.tiff +test/2-seed/Training/1 Brassica junsea/148.json +test/2-seed/Training/1 Brassica junsea/148.tiff +test/2-seed/Training/1 Brassica junsea/149.json +test/2-seed/Training/1 Brassica junsea/149.tiff +test/2-seed/Training/1 Brassica junsea/150.json +test/2-seed/Training/1 Brassica junsea/150.tiff +test/2-seed/Training/1 Brassica junsea/151.json +test/2-seed/Training/1 Brassica junsea/151.tiff +test/2-seed/Training/1 Brassica junsea/152.json +test/2-seed/Training/1 Brassica junsea/152.tiff +test/2-seed/Training/1 Brassica junsea/153.json +test/2-seed/Training/1 Brassica junsea/153.tiff +test/2-seed/Training/1 Brassica junsea/154.json +test/2-seed/Training/1 Brassica junsea/154.tiff +test/2-seed/Training/1 Brassica junsea/155.json +test/2-seed/Training/1 Brassica junsea/155.tiff +test/2-seed/Training/1 Brassica junsea/156.json +test/2-seed/Training/1 Brassica junsea/156.tiff +test/2-seed/Training/1 Brassica junsea/157.json +test/2-seed/Training/1 Brassica junsea/157.tiff +test/2-seed/Training/1 Brassica junsea/158.json +test/2-seed/Training/1 Brassica junsea/158.tiff +test/2-seed/Training/1 Brassica junsea/159.json +test/2-seed/Training/1 Brassica junsea/159.tiff +test/2-seed/Training/1 Brassica junsea/160.json +test/2-seed/Training/1 Brassica junsea/160.tiff +test/2-seed/Training/1 Brassica junsea/161.json +test/2-seed/Training/1 Brassica junsea/161.tiff +test/2-seed/Training/1 Brassica junsea/162.json +test/2-seed/Training/1 Brassica junsea/162.tiff +test/2-seed/Training/1 Brassica junsea/163.json +test/2-seed/Training/1 Brassica junsea/163.tiff +test/2-seed/Training/1 Brassica junsea/164.json +test/2-seed/Training/1 Brassica junsea/164.tiff +test/2-seed/Training/1 Brassica junsea/165.json +test/2-seed/Training/1 Brassica junsea/165.tiff +test/2-seed/Training/1 Brassica junsea/166.json +test/2-seed/Training/1 Brassica junsea/166.tiff +test/2-seed/Training/1 Brassica junsea/167.json +test/2-seed/Training/1 Brassica junsea/167.tiff +test/2-seed/Training/1 Brassica junsea/168.json +test/2-seed/Training/1 Brassica junsea/168.tiff +test/2-seed/Training/1 Brassica junsea/169.json +test/2-seed/Training/1 Brassica junsea/169.tiff +test/2-seed/Training/1 Brassica junsea/170.json +test/2-seed/Training/1 Brassica junsea/170.tiff +test/2-seed/Training/1 Brassica junsea/171.json +test/2-seed/Training/1 Brassica junsea/171.tiff +test/2-seed/Training/1 Brassica junsea/172.json +test/2-seed/Training/1 Brassica junsea/172.tiff +test/2-seed/Training/1 Brassica junsea/173.json +test/2-seed/Training/1 Brassica junsea/173.tiff +test/2-seed/Training/1 Brassica junsea/174.json +test/2-seed/Training/1 Brassica junsea/174.tiff +test/2-seed/Training/1 Brassica junsea/175.json +test/2-seed/Training/1 Brassica junsea/175.tiff +test/2-seed/Training/1 Brassica junsea/176.json +test/2-seed/Training/1 Brassica junsea/176.tiff +test/2-seed/Training/1 Brassica junsea/177.json +test/2-seed/Training/1 Brassica junsea/177.tiff +test/2-seed/Training/1 Brassica junsea/178.json +test/2-seed/Training/1 Brassica junsea/178.tiff +test/2-seed/Training/1 Brassica junsea/179.json +test/2-seed/Training/1 Brassica junsea/179.tiff +test/2-seed/Training/1 Brassica junsea/180.json +test/2-seed/Training/1 Brassica junsea/180.tiff +test/2-seed/Training/1 Brassica junsea/181.json +test/2-seed/Training/1 Brassica junsea/181.tiff +test/2-seed/Training/1 Brassica junsea/182.json +test/2-seed/Training/1 Brassica junsea/182.tiff +test/2-seed/Training/1 Brassica junsea/183.json +test/2-seed/Training/1 Brassica junsea/183.tiff +test/2-seed/Training/1 Brassica junsea/184.json +test/2-seed/Training/1 Brassica junsea/184.tiff +test/2-seed/Training/1 Brassica junsea/185.json +test/2-seed/Training/1 Brassica junsea/185.tiff +test/2-seed/Training/1 Brassica junsea/186.json +test/2-seed/Training/1 Brassica junsea/186.tiff +test/2-seed/Training/1 Brassica junsea/187.json +test/2-seed/Training/1 Brassica junsea/187.tiff +test/2-seed/Training/1 Brassica junsea/188.json +test/2-seed/Training/1 Brassica junsea/188.tiff +test/2-seed/Training/1 Brassica junsea/189.json +test/2-seed/Training/1 Brassica junsea/189.tiff +test/2-seed/Training/1 Brassica junsea/190.json +test/2-seed/Training/1 Brassica junsea/190.tiff +test/2-seed/Training/1 Brassica junsea/191.json +test/2-seed/Training/1 Brassica junsea/191.tiff +test/2-seed/Training/1 Brassica junsea/192.json +test/2-seed/Training/1 Brassica junsea/192.tiff +test/2-seed/Training/1 Brassica junsea/193.json +test/2-seed/Training/1 Brassica junsea/193.tiff +test/2-seed/Training/1 Brassica junsea/194.json +test/2-seed/Training/1 Brassica junsea/194.tiff +test/2-seed/Training/1 Brassica junsea/195.json +test/2-seed/Training/1 Brassica junsea/195.tiff +test/2-seed/Training/1 Brassica junsea/196.json +test/2-seed/Training/1 Brassica junsea/196.tiff +test/2-seed/Training/1 Brassica junsea/197.json +test/2-seed/Training/1 Brassica junsea/197.tiff +test/2-seed/Training/1 Brassica junsea/198.json +test/2-seed/Training/1 Brassica junsea/198.tiff +test/2-seed/Training/1 Brassica junsea/199.json +test/2-seed/Training/1 Brassica junsea/199.tiff +test/2-seed/Training/1 Brassica junsea/200.json +test/2-seed/Training/1 Brassica junsea/200.tiff +test/2-seed/Training/1 Brassica junsea/index.json +test/2-seed/Training/10 Solanum nigrum/1.tiff +test/2-seed/Training/10 Solanum nigrum/2.tiff +test/2-seed/Training/10 Solanum nigrum/3.tiff +test/2-seed/Training/10 Solanum nigrum/4.tiff +test/2-seed/Training/10 Solanum nigrum/5.tiff +test/2-seed/Training/10 Solanum nigrum/6.tiff +test/2-seed/Training/10 Solanum nigrum/7.tiff +test/2-seed/Training/10 Solanum nigrum/8.tiff +test/2-seed/Training/10 Solanum nigrum/9.tiff +test/2-seed/Training/10 Solanum nigrum/10.tiff +test/2-seed/Training/10 Solanum nigrum/11.tiff +test/2-seed/Training/10 Solanum nigrum/12.tiff +test/2-seed/Training/10 Solanum nigrum/13.tiff +test/2-seed/Training/10 Solanum nigrum/14.tiff +test/2-seed/Training/10 Solanum nigrum/15.tiff +test/2-seed/Training/10 Solanum nigrum/16.tiff +test/2-seed/Training/10 Solanum nigrum/17.tiff +test/2-seed/Training/10 Solanum nigrum/18.tiff +test/2-seed/Training/10 Solanum nigrum/19.tiff +test/2-seed/Training/10 Solanum nigrum/20.tiff +test/2-seed/Training/10 Solanum nigrum/21.tiff +test/2-seed/Training/10 Solanum nigrum/22.tiff +test/2-seed/Training/10 Solanum nigrum/23.tiff +test/2-seed/Training/10 Solanum nigrum/24.tiff +test/2-seed/Training/10 Solanum nigrum/25.tiff +test/2-seed/Training/10 Solanum nigrum/26.tiff +test/2-seed/Training/10 Solanum nigrum/27.tiff +test/2-seed/Training/10 Solanum nigrum/28.tiff +test/2-seed/Training/10 Solanum nigrum/29.tiff +test/2-seed/Training/10 Solanum nigrum/30.tiff +test/2-seed/Training/10 Solanum nigrum/31.tiff +test/2-seed/Training/10 Solanum nigrum/32.tiff +test/2-seed/Training/10 Solanum nigrum/33.tiff +test/2-seed/Training/10 Solanum nigrum/34.tiff +test/2-seed/Training/10 Solanum nigrum/35.tiff +test/2-seed/Training/10 Solanum nigrum/36.tiff +test/2-seed/Training/10 Solanum nigrum/37.tiff +test/2-seed/Training/10 Solanum nigrum/38.tiff +test/2-seed/Training/10 Solanum nigrum/39.tiff +test/2-seed/Training/10 Solanum nigrum/40.tiff +test/2-seed/Training/10 Solanum nigrum/41.tiff +test/2-seed/Training/10 Solanum nigrum/42.tiff +test/2-seed/Training/10 Solanum nigrum/43.tiff +test/2-seed/Training/10 Solanum nigrum/44.tiff +test/2-seed/Training/10 Solanum nigrum/45.tiff +test/2-seed/Training/10 Solanum nigrum/46.tiff +test/2-seed/Training/10 Solanum nigrum/47.tiff +test/2-seed/Training/10 Solanum nigrum/48.tiff +test/2-seed/Training/10 Solanum nigrum/49.tiff +test/2-seed/Training/10 Solanum nigrum/50.tiff +test/2-seed/Training/11 Solanum rostratum/1.tiff +test/2-seed/Training/11 Solanum rostratum/2.tiff +test/2-seed/Training/11 Solanum rostratum/3.tiff +test/2-seed/Training/11 Solanum rostratum/4.tiff +test/2-seed/Training/11 Solanum rostratum/5.tiff +test/2-seed/Training/11 Solanum rostratum/6.tiff +test/2-seed/Training/11 Solanum rostratum/7.tiff +test/2-seed/Training/11 Solanum rostratum/8.tiff +test/2-seed/Training/11 Solanum rostratum/9.tiff +test/2-seed/Training/11 Solanum rostratum/10.tiff +test/2-seed/Training/11 Solanum rostratum/11.tiff +test/2-seed/Training/11 Solanum rostratum/12.tiff +test/2-seed/Training/11 Solanum rostratum/13.tiff +test/2-seed/Training/11 Solanum rostratum/14.tiff +test/2-seed/Training/11 Solanum rostratum/15.tiff +test/2-seed/Training/11 Solanum rostratum/16.tiff +test/2-seed/Training/11 Solanum rostratum/17.tiff +test/2-seed/Training/11 Solanum rostratum/18.tiff +test/2-seed/Training/11 Solanum rostratum/19.tiff +test/2-seed/Training/11 Solanum rostratum/20.tiff +test/2-seed/Training/11 Solanum rostratum/21.tiff +test/2-seed/Training/11 Solanum rostratum/22.tiff +test/2-seed/Training/11 Solanum rostratum/23.tiff +test/2-seed/Training/11 Solanum rostratum/24.tiff +test/2-seed/Training/11 Solanum rostratum/25.tiff +test/2-seed/Training/11 Solanum rostratum/26.tiff +test/2-seed/Training/11 Solanum rostratum/27.tiff +test/2-seed/Training/11 Solanum rostratum/28.tiff +test/2-seed/Training/11 Solanum rostratum/29.tiff +test/2-seed/Training/11 Solanum rostratum/30.tiff +test/2-seed/Training/11 Solanum rostratum/31.tiff +test/2-seed/Training/11 Solanum rostratum/32.tiff +test/2-seed/Training/11 Solanum rostratum/33.tiff +test/2-seed/Training/11 Solanum rostratum/34.tiff +test/2-seed/Training/11 Solanum rostratum/35.tiff +test/2-seed/Training/11 Solanum rostratum/36.tiff +test/2-seed/Training/11 Solanum rostratum/37.tiff +test/2-seed/Training/11 Solanum rostratum/38.tiff +test/2-seed/Training/11 Solanum rostratum/39.tiff +test/2-seed/Training/11 Solanum rostratum/40.tiff +test/2-seed/Training/11 Solanum rostratum/41.tiff +test/2-seed/Training/11 Solanum rostratum/42.tiff +test/2-seed/Training/11 Solanum rostratum/43.tiff +test/2-seed/Training/11 Solanum rostratum/44.tiff +test/2-seed/Training/11 Solanum rostratum/45.tiff +test/2-seed/Training/11 Solanum rostratum/46.tiff +test/2-seed/Training/11 Solanum rostratum/47.tiff +test/2-seed/Training/11 Solanum rostratum/48.tiff +test/2-seed/Training/11 Solanum rostratum/49.tiff +test/2-seed/Training/11 Solanum rostratum/50.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/1.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/2.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/3.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/4.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/5.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/6.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/7.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/8.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/9.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/10.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/11.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/12.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/13.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/14.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/15.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/16.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/17.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/18.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/19.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/20.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/21.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/22.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/23.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/24.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/25.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/26.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/27.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/28.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/29.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/30.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/31.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/32.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/33.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/34.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/35.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/36.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/37.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/38.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/39.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/40.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/41.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/42.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/43.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/44.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/45.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/46.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/47.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/48.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/49.tiff +test/2-seed/Training/12 Ambrosia artemisiifolia/50.tiff +test/2-seed/Training/13 Ambrosia trifida/1.tiff +test/2-seed/Training/13 Ambrosia trifida/2.tiff +test/2-seed/Training/13 Ambrosia trifida/3.tiff +test/2-seed/Training/13 Ambrosia trifida/4.tiff +test/2-seed/Training/13 Ambrosia trifida/5.tiff +test/2-seed/Training/13 Ambrosia trifida/6.tiff +test/2-seed/Training/13 Ambrosia trifida/7.tiff +test/2-seed/Training/13 Ambrosia trifida/8.tiff +test/2-seed/Training/13 Ambrosia trifida/9.tiff +test/2-seed/Training/13 Ambrosia trifida/10.tiff +test/2-seed/Training/13 Ambrosia trifida/11.tiff +test/2-seed/Training/13 Ambrosia trifida/12.tiff +test/2-seed/Training/13 Ambrosia trifida/13.tiff +test/2-seed/Training/13 Ambrosia trifida/14.tiff +test/2-seed/Training/13 Ambrosia trifida/15.tiff +test/2-seed/Training/13 Ambrosia trifida/16.tiff +test/2-seed/Training/13 Ambrosia trifida/17.tiff +test/2-seed/Training/13 Ambrosia trifida/18.tiff +test/2-seed/Training/13 Ambrosia trifida/19.tiff +test/2-seed/Training/13 Ambrosia trifida/20.tiff +test/2-seed/Training/13 Ambrosia trifida/21.tiff +test/2-seed/Training/13 Ambrosia trifida/22.tiff +test/2-seed/Training/13 Ambrosia trifida/23.tiff +test/2-seed/Training/13 Ambrosia trifida/24.tiff +test/2-seed/Training/13 Ambrosia trifida/25.tiff +test/2-seed/Training/13 Ambrosia trifida/26.tiff +test/2-seed/Training/13 Ambrosia trifida/27.tiff +test/2-seed/Training/13 Ambrosia trifida/28.tiff +test/2-seed/Training/13 Ambrosia trifida/29.tiff +test/2-seed/Training/13 Ambrosia trifida/30.tiff +test/2-seed/Training/13 Ambrosia trifida/31.tiff +test/2-seed/Training/13 Ambrosia trifida/32.tiff +test/2-seed/Training/13 Ambrosia trifida/33.tiff +test/2-seed/Training/13 Ambrosia trifida/34.tiff +test/2-seed/Training/13 Ambrosia trifida/35.tiff +test/2-seed/Training/13 Ambrosia trifida/36.tiff +test/2-seed/Training/13 Ambrosia trifida/37.tiff +test/2-seed/Training/13 Ambrosia trifida/38.tiff +test/2-seed/Training/13 Ambrosia trifida/39.tiff +test/2-seed/Training/13 Ambrosia trifida/40.tiff +test/2-seed/Training/13 Ambrosia trifida/41.tiff +test/2-seed/Training/13 Ambrosia trifida/42.tiff +test/2-seed/Training/13 Ambrosia trifida/43.tiff +test/2-seed/Training/13 Ambrosia trifida/44.tiff +test/2-seed/Training/13 Ambrosia trifida/45.tiff +test/2-seed/Training/13 Ambrosia trifida/46.tiff +test/2-seed/Training/13 Ambrosia trifida/47.tiff +test/2-seed/Training/13 Ambrosia trifida/48.tiff +test/2-seed/Training/13 Ambrosia trifida/49.tiff +test/2-seed/Training/13 Ambrosia trifida/50.tiff +test/2-seed/Training/14 Ambrosia psilostachya/1.tiff +test/2-seed/Training/14 Ambrosia psilostachya/2.tiff +test/2-seed/Training/14 Ambrosia psilostachya/3.tiff +test/2-seed/Training/14 Ambrosia psilostachya/4.tiff +test/2-seed/Training/14 Ambrosia psilostachya/5.tiff +test/2-seed/Training/14 Ambrosia psilostachya/6.tiff +test/2-seed/Training/14 Ambrosia psilostachya/7.tiff +test/2-seed/Training/14 Ambrosia psilostachya/8.tiff +test/2-seed/Training/14 Ambrosia psilostachya/9.tiff +test/2-seed/Training/14 Ambrosia psilostachya/10.tiff +test/2-seed/Training/14 Ambrosia psilostachya/11.tiff +test/2-seed/Training/14 Ambrosia psilostachya/12.tiff +test/2-seed/Training/14 Ambrosia psilostachya/13.tiff +test/2-seed/Training/14 Ambrosia psilostachya/14.tiff +test/2-seed/Training/14 Ambrosia psilostachya/15.tiff +test/2-seed/Training/14 Ambrosia psilostachya/16.tiff +test/2-seed/Training/14 Ambrosia psilostachya/17.tiff +test/2-seed/Training/14 Ambrosia psilostachya/18.tiff +test/2-seed/Training/14 Ambrosia psilostachya/19.tiff +test/2-seed/Training/14 Ambrosia psilostachya/20.tiff +test/2-seed/Training/14 Ambrosia psilostachya/21.tiff +test/2-seed/Training/14 Ambrosia psilostachya/22.tiff +test/2-seed/Training/14 Ambrosia psilostachya/23.1.tiff +test/2-seed/Training/14 Ambrosia psilostachya/24.tiff +test/2-seed/Training/14 Ambrosia psilostachya/25.tiff +test/2-seed/Training/14 Ambrosia psilostachya/26.tiff +test/2-seed/Training/14 Ambrosia psilostachya/27.tiff +test/2-seed/Training/14 Ambrosia psilostachya/28.tiff +test/2-seed/Training/14 Ambrosia psilostachya/29.tiff +test/2-seed/Training/14 Ambrosia psilostachya/30.tiff +test/2-seed/Training/14 Ambrosia psilostachya/31.tiff +test/2-seed/Training/14 Ambrosia psilostachya/32.tiff +test/2-seed/Training/14 Ambrosia psilostachya/33.tiff +test/2-seed/Training/14 Ambrosia psilostachya/34.tiff +test/2-seed/Training/14 Ambrosia psilostachya/35.tiff +test/2-seed/Training/14 Ambrosia psilostachya/36.tiff +test/2-seed/Training/14 Ambrosia psilostachya/37.tiff +test/2-seed/Training/14 Ambrosia psilostachya/38.tiff +test/2-seed/Training/14 Ambrosia psilostachya/39.tiff +test/2-seed/Training/14 Ambrosia psilostachya/40.tiff +test/2-seed/Training/14 Ambrosia psilostachya/41.tiff +test/2-seed/Training/14 Ambrosia psilostachya/42.tiff +test/2-seed/Training/14 Ambrosia psilostachya/43.tiff +test/2-seed/Training/14 Ambrosia psilostachya/44.tiff +test/2-seed/Training/14 Ambrosia psilostachya/45.tiff +test/2-seed/Training/14 Ambrosia psilostachya/46.tiff +test/2-seed/Training/14 Ambrosia psilostachya/47.tiff +test/2-seed/Training/14 Ambrosia psilostachya/48.tiff +test/2-seed/Training/14 Ambrosia psilostachya/49.tiff +test/2-seed/Training/14 Ambrosia psilostachya/50.tiff +test/2-seed/Training/2 Cirsium arvense/1.json +test/2-seed/Training/2 Cirsium arvense/1.tiff +test/2-seed/Training/2 Cirsium arvense/2.json +test/2-seed/Training/2 Cirsium arvense/2.tiff +test/2-seed/Training/2 Cirsium arvense/3.json +test/2-seed/Training/2 Cirsium arvense/3.tiff +test/2-seed/Training/2 Cirsium arvense/4.json +test/2-seed/Training/2 Cirsium arvense/4.tiff +test/2-seed/Training/2 Cirsium arvense/5.json +test/2-seed/Training/2 Cirsium arvense/5.tiff +test/2-seed/Training/2 Cirsium arvense/6.json +test/2-seed/Training/2 Cirsium arvense/6.tiff +test/2-seed/Training/2 Cirsium arvense/7.json +test/2-seed/Training/2 Cirsium arvense/7.tiff +test/2-seed/Training/2 Cirsium arvense/8.json +test/2-seed/Training/2 Cirsium arvense/8.tiff +test/2-seed/Training/2 Cirsium arvense/9.json +test/2-seed/Training/2 Cirsium arvense/9.tiff +test/2-seed/Training/2 Cirsium arvense/10.json +test/2-seed/Training/2 Cirsium arvense/10.tiff +test/2-seed/Training/2 Cirsium arvense/11.json +test/2-seed/Training/2 Cirsium arvense/11.tiff +test/2-seed/Training/2 Cirsium arvense/12.json +test/2-seed/Training/2 Cirsium arvense/12.tiff +test/2-seed/Training/2 Cirsium arvense/13.json +test/2-seed/Training/2 Cirsium arvense/13.tiff +test/2-seed/Training/2 Cirsium arvense/14.json +test/2-seed/Training/2 Cirsium arvense/14.tiff +test/2-seed/Training/2 Cirsium arvense/15.json +test/2-seed/Training/2 Cirsium arvense/15.tiff +test/2-seed/Training/2 Cirsium arvense/16.json +test/2-seed/Training/2 Cirsium arvense/16.tiff +test/2-seed/Training/2 Cirsium arvense/17.json +test/2-seed/Training/2 Cirsium arvense/17.tiff +test/2-seed/Training/2 Cirsium arvense/18.json +test/2-seed/Training/2 Cirsium arvense/18.tiff +test/2-seed/Training/2 Cirsium arvense/19.json +test/2-seed/Training/2 Cirsium arvense/19.tiff +test/2-seed/Training/2 Cirsium arvense/20.json +test/2-seed/Training/2 Cirsium arvense/20.tiff +test/2-seed/Training/2 Cirsium arvense/21.json +test/2-seed/Training/2 Cirsium arvense/21.tiff +test/2-seed/Training/2 Cirsium arvense/22.json +test/2-seed/Training/2 Cirsium arvense/22.tiff +test/2-seed/Training/2 Cirsium arvense/23.json +test/2-seed/Training/2 Cirsium arvense/23.tiff +test/2-seed/Training/2 Cirsium arvense/24.json +test/2-seed/Training/2 Cirsium arvense/24.tiff +test/2-seed/Training/2 Cirsium arvense/25.json +test/2-seed/Training/2 Cirsium arvense/25.tiff +test/2-seed/Training/2 Cirsium arvense/26.json +test/2-seed/Training/2 Cirsium arvense/26.tiff +test/2-seed/Training/2 Cirsium arvense/27.json +test/2-seed/Training/2 Cirsium arvense/27.tiff +test/2-seed/Training/2 Cirsium arvense/28.json +test/2-seed/Training/2 Cirsium arvense/28.tiff +test/2-seed/Training/2 Cirsium arvense/29.json +test/2-seed/Training/2 Cirsium arvense/29.tiff +test/2-seed/Training/2 Cirsium arvense/30.json +test/2-seed/Training/2 Cirsium arvense/30.tiff +test/2-seed/Training/2 Cirsium arvense/31.json +test/2-seed/Training/2 Cirsium arvense/31.tiff +test/2-seed/Training/2 Cirsium arvense/32.json +test/2-seed/Training/2 Cirsium arvense/32.tiff +test/2-seed/Training/2 Cirsium arvense/33.json +test/2-seed/Training/2 Cirsium arvense/33.tiff +test/2-seed/Training/2 Cirsium arvense/34.json +test/2-seed/Training/2 Cirsium arvense/34.tiff +test/2-seed/Training/2 Cirsium arvense/35.json +test/2-seed/Training/2 Cirsium arvense/35.tiff +test/2-seed/Training/2 Cirsium arvense/36.json +test/2-seed/Training/2 Cirsium arvense/36.tiff +test/2-seed/Training/2 Cirsium arvense/37.json +test/2-seed/Training/2 Cirsium arvense/37.tiff +test/2-seed/Training/2 Cirsium arvense/38.json +test/2-seed/Training/2 Cirsium arvense/38.tiff +test/2-seed/Training/2 Cirsium arvense/39.json +test/2-seed/Training/2 Cirsium arvense/39.tiff +test/2-seed/Training/2 Cirsium arvense/40.json +test/2-seed/Training/2 Cirsium arvense/40.tiff +test/2-seed/Training/2 Cirsium arvense/41.json +test/2-seed/Training/2 Cirsium arvense/41.tiff +test/2-seed/Training/2 Cirsium arvense/42.json +test/2-seed/Training/2 Cirsium arvense/42.tiff +test/2-seed/Training/2 Cirsium arvense/43.json +test/2-seed/Training/2 Cirsium arvense/43.tiff +test/2-seed/Training/2 Cirsium arvense/44.json +test/2-seed/Training/2 Cirsium arvense/44.tiff +test/2-seed/Training/2 Cirsium arvense/45.json +test/2-seed/Training/2 Cirsium arvense/45.tiff +test/2-seed/Training/2 Cirsium arvense/46.json +test/2-seed/Training/2 Cirsium arvense/46.tiff +test/2-seed/Training/2 Cirsium arvense/47.json +test/2-seed/Training/2 Cirsium arvense/47.tiff +test/2-seed/Training/2 Cirsium arvense/48.json +test/2-seed/Training/2 Cirsium arvense/48.tiff +test/2-seed/Training/2 Cirsium arvense/49.json +test/2-seed/Training/2 Cirsium arvense/49.tiff +test/2-seed/Training/2 Cirsium arvense/50.json +test/2-seed/Training/2 Cirsium arvense/50.tiff +test/2-seed/Training/2 Cirsium arvense/index.json +test/2-seed/Training/3 Cirsium vulgare/1.json +test/2-seed/Training/3 Cirsium vulgare/1.tiff +test/2-seed/Training/3 Cirsium vulgare/2.json +test/2-seed/Training/3 Cirsium vulgare/2.tiff +test/2-seed/Training/3 Cirsium vulgare/3.json +test/2-seed/Training/3 Cirsium vulgare/3.tiff +test/2-seed/Training/3 Cirsium vulgare/4.json +test/2-seed/Training/3 Cirsium vulgare/4.tiff +test/2-seed/Training/3 Cirsium vulgare/5.json +test/2-seed/Training/3 Cirsium vulgare/5.tiff +test/2-seed/Training/3 Cirsium vulgare/6.json +test/2-seed/Training/3 Cirsium vulgare/6.tiff +test/2-seed/Training/3 Cirsium vulgare/7.json +test/2-seed/Training/3 Cirsium vulgare/7.tiff +test/2-seed/Training/3 Cirsium vulgare/8.json +test/2-seed/Training/3 Cirsium vulgare/8.tiff +test/2-seed/Training/3 Cirsium vulgare/9.json +test/2-seed/Training/3 Cirsium vulgare/9.tiff +test/2-seed/Training/3 Cirsium vulgare/10.json +test/2-seed/Training/3 Cirsium vulgare/10.tiff +test/2-seed/Training/3 Cirsium vulgare/11.json +test/2-seed/Training/3 Cirsium vulgare/11.tiff +test/2-seed/Training/3 Cirsium vulgare/12.json +test/2-seed/Training/3 Cirsium vulgare/12.tiff +test/2-seed/Training/3 Cirsium vulgare/13.json +test/2-seed/Training/3 Cirsium vulgare/13.tiff +test/2-seed/Training/3 Cirsium vulgare/14.json +test/2-seed/Training/3 Cirsium vulgare/14.tiff +test/2-seed/Training/3 Cirsium vulgare/15.json +test/2-seed/Training/3 Cirsium vulgare/15.tiff +test/2-seed/Training/3 Cirsium vulgare/16.json +test/2-seed/Training/3 Cirsium vulgare/16.tiff +test/2-seed/Training/3 Cirsium vulgare/17.json +test/2-seed/Training/3 Cirsium vulgare/17.tiff +test/2-seed/Training/3 Cirsium vulgare/18.json +test/2-seed/Training/3 Cirsium vulgare/18.tiff +test/2-seed/Training/3 Cirsium vulgare/19.json +test/2-seed/Training/3 Cirsium vulgare/19.tiff +test/2-seed/Training/3 Cirsium vulgare/20.json +test/2-seed/Training/3 Cirsium vulgare/20.tiff +test/2-seed/Training/3 Cirsium vulgare/21.json +test/2-seed/Training/3 Cirsium vulgare/21.tiff +test/2-seed/Training/3 Cirsium vulgare/22.json +test/2-seed/Training/3 Cirsium vulgare/22.tiff +test/2-seed/Training/3 Cirsium vulgare/23.json +test/2-seed/Training/3 Cirsium vulgare/23.tiff +test/2-seed/Training/3 Cirsium vulgare/24.json +test/2-seed/Training/3 Cirsium vulgare/24.tiff +test/2-seed/Training/3 Cirsium vulgare/25.json +test/2-seed/Training/3 Cirsium vulgare/25.tiff +test/2-seed/Training/3 Cirsium vulgare/26.json +test/2-seed/Training/3 Cirsium vulgare/26.tiff +test/2-seed/Training/3 Cirsium vulgare/27.json +test/2-seed/Training/3 Cirsium vulgare/27.tiff +test/2-seed/Training/3 Cirsium vulgare/28.json +test/2-seed/Training/3 Cirsium vulgare/28.tiff +test/2-seed/Training/3 Cirsium vulgare/29.json +test/2-seed/Training/3 Cirsium vulgare/29.tiff +test/2-seed/Training/3 Cirsium vulgare/30.json +test/2-seed/Training/3 Cirsium vulgare/30.tiff +test/2-seed/Training/3 Cirsium vulgare/31.json +test/2-seed/Training/3 Cirsium vulgare/31.tiff +test/2-seed/Training/3 Cirsium vulgare/32.json +test/2-seed/Training/3 Cirsium vulgare/32.tiff +test/2-seed/Training/3 Cirsium vulgare/33.json +test/2-seed/Training/3 Cirsium vulgare/33.tiff +test/2-seed/Training/3 Cirsium vulgare/34.json +test/2-seed/Training/3 Cirsium vulgare/34.tiff +test/2-seed/Training/3 Cirsium vulgare/35.json +test/2-seed/Training/3 Cirsium vulgare/35.tiff +test/2-seed/Training/3 Cirsium vulgare/36.json +test/2-seed/Training/3 Cirsium vulgare/36.tiff +test/2-seed/Training/3 Cirsium vulgare/37.json +test/2-seed/Training/3 Cirsium vulgare/37.tiff +test/2-seed/Training/3 Cirsium vulgare/38.json +test/2-seed/Training/3 Cirsium vulgare/38.tiff +test/2-seed/Training/3 Cirsium vulgare/39.json +test/2-seed/Training/3 Cirsium vulgare/39.tiff +test/2-seed/Training/3 Cirsium vulgare/40.json +test/2-seed/Training/3 Cirsium vulgare/40.tiff +test/2-seed/Training/3 Cirsium vulgare/41.json +test/2-seed/Training/3 Cirsium vulgare/41.tiff +test/2-seed/Training/3 Cirsium vulgare/42.json +test/2-seed/Training/3 Cirsium vulgare/42.tiff +test/2-seed/Training/3 Cirsium vulgare/43.json +test/2-seed/Training/3 Cirsium vulgare/43.tiff +test/2-seed/Training/3 Cirsium vulgare/44.json +test/2-seed/Training/3 Cirsium vulgare/44.tiff +test/2-seed/Training/3 Cirsium vulgare/45.json +test/2-seed/Training/3 Cirsium vulgare/45.tiff +test/2-seed/Training/3 Cirsium vulgare/46.json +test/2-seed/Training/3 Cirsium vulgare/46.tiff +test/2-seed/Training/3 Cirsium vulgare/47.json +test/2-seed/Training/3 Cirsium vulgare/47.tiff +test/2-seed/Training/3 Cirsium vulgare/48.json +test/2-seed/Training/3 Cirsium vulgare/48.tiff +test/2-seed/Training/3 Cirsium vulgare/49.json +test/2-seed/Training/3 Cirsium vulgare/49.tiff +test/2-seed/Training/3 Cirsium vulgare/50.json +test/2-seed/Training/3 Cirsium vulgare/50.tiff +test/2-seed/Training/3 Cirsium vulgare/index.json +test/2-seed/Training/4 Carduus nutans/1.tiff +test/2-seed/Training/4 Carduus nutans/2.tiff +test/2-seed/Training/4 Carduus nutans/3.tiff +test/2-seed/Training/4 Carduus nutans/4.tiff +test/2-seed/Training/4 Carduus nutans/5.tiff +test/2-seed/Training/4 Carduus nutans/6.tiff +test/2-seed/Training/4 Carduus nutans/7.tiff +test/2-seed/Training/4 Carduus nutans/8.tiff +test/2-seed/Training/4 Carduus nutans/9.tiff +test/2-seed/Training/4 Carduus nutans/10.tiff +test/2-seed/Training/4 Carduus nutans/11.tiff +test/2-seed/Training/4 Carduus nutans/12.tiff +test/2-seed/Training/4 Carduus nutans/13.tiff +test/2-seed/Training/4 Carduus nutans/14.tiff +test/2-seed/Training/4 Carduus nutans/15.tiff +test/2-seed/Training/4 Carduus nutans/16.tiff +test/2-seed/Training/4 Carduus nutans/17.tiff +test/2-seed/Training/4 Carduus nutans/18.tiff +test/2-seed/Training/4 Carduus nutans/19.tiff +test/2-seed/Training/4 Carduus nutans/20.tiff +test/2-seed/Training/4 Carduus nutans/21.tiff +test/2-seed/Training/4 Carduus nutans/22.tiff +test/2-seed/Training/4 Carduus nutans/23.tiff +test/2-seed/Training/4 Carduus nutans/24.tiff +test/2-seed/Training/4 Carduus nutans/25.tiff +test/2-seed/Training/4 Carduus nutans/26.tiff +test/2-seed/Training/4 Carduus nutans/27.tiff +test/2-seed/Training/4 Carduus nutans/28.tiff +test/2-seed/Training/4 Carduus nutans/29.tiff +test/2-seed/Training/4 Carduus nutans/30.tiff +test/2-seed/Training/4 Carduus nutans/31.tiff +test/2-seed/Training/4 Carduus nutans/32.tiff +test/2-seed/Training/4 Carduus nutans/33.tiff +test/2-seed/Training/4 Carduus nutans/34.tiff +test/2-seed/Training/4 Carduus nutans/35.tiff +test/2-seed/Training/4 Carduus nutans/36.tiff +test/2-seed/Training/4 Carduus nutans/37.tiff +test/2-seed/Training/4 Carduus nutans/38.tiff +test/2-seed/Training/4 Carduus nutans/39.tiff +test/2-seed/Training/4 Carduus nutans/40.tiff +test/2-seed/Training/4 Carduus nutans/41.tiff +test/2-seed/Training/4 Carduus nutans/42.tiff +test/2-seed/Training/4 Carduus nutans/43.tiff +test/2-seed/Training/4 Carduus nutans/44.tiff +test/2-seed/Training/4 Carduus nutans/45.tiff +test/2-seed/Training/4 Carduus nutans/46.tiff +test/2-seed/Training/4 Carduus nutans/47.tiff +test/2-seed/Training/4 Carduus nutans/48.tiff +test/2-seed/Training/4 Carduus nutans/49.tiff +test/2-seed/Training/4 Carduus nutans/50.tiff +test/2-seed/Training/5 Bromus secalinus/001.tiff +test/2-seed/Training/5 Bromus secalinus/002 (2).tiff +test/2-seed/Training/5 Bromus secalinus/002.tiff +test/2-seed/Training/5 Bromus secalinus/003 (2).tiff +test/2-seed/Training/5 Bromus secalinus/003.tiff +test/2-seed/Training/5 Bromus secalinus/004 (2).tiff +test/2-seed/Training/5 Bromus secalinus/004.tiff +test/2-seed/Training/5 Bromus secalinus/005 (2).tiff +test/2-seed/Training/5 Bromus secalinus/005.tiff +test/2-seed/Training/5 Bromus secalinus/006 (2).tiff +test/2-seed/Training/5 Bromus secalinus/006.tiff +test/2-seed/Training/5 Bromus secalinus/007 (2).tiff +test/2-seed/Training/5 Bromus secalinus/007.tiff +test/2-seed/Training/5 Bromus secalinus/008 (2).tiff +test/2-seed/Training/5 Bromus secalinus/008.tiff +test/2-seed/Training/5 Bromus secalinus/009 (2).tiff +test/2-seed/Training/5 Bromus secalinus/009.tiff +test/2-seed/Training/5 Bromus secalinus/010 (2).tiff +test/2-seed/Training/5 Bromus secalinus/010.tiff +test/2-seed/Training/5 Bromus secalinus/011 (2).tiff +test/2-seed/Training/5 Bromus secalinus/011.tiff +test/2-seed/Training/5 Bromus secalinus/012 (2).tiff +test/2-seed/Training/5 Bromus secalinus/012.tiff +test/2-seed/Training/5 Bromus secalinus/013 (2).tiff +test/2-seed/Training/5 Bromus secalinus/013.tiff +test/2-seed/Training/5 Bromus secalinus/014 (2).tiff +test/2-seed/Training/5 Bromus secalinus/015.tiff +test/2-seed/Training/5 Bromus secalinus/016 (2).tiff +test/2-seed/Training/5 Bromus secalinus/016.tiff +test/2-seed/Training/5 Bromus secalinus/017 (2).tiff +test/2-seed/Training/5 Bromus secalinus/018 (2).tiff +test/2-seed/Training/5 Bromus secalinus/018.tiff +test/2-seed/Training/5 Bromus secalinus/019 (2).tiff +test/2-seed/Training/5 Bromus secalinus/019.tiff +test/2-seed/Training/5 Bromus secalinus/020 (2).tiff +test/2-seed/Training/5 Bromus secalinus/021 (2).tiff +test/2-seed/Training/5 Bromus secalinus/021.tiff +test/2-seed/Training/5 Bromus secalinus/022.tiff +test/2-seed/Training/5 Bromus secalinus/023 (2).tiff +test/2-seed/Training/5 Bromus secalinus/023.tiff +test/2-seed/Training/5 Bromus secalinus/024 (2).tiff +test/2-seed/Training/5 Bromus secalinus/024.tiff +test/2-seed/Training/5 Bromus secalinus/025 (2).tiff +test/2-seed/Training/5 Bromus secalinus/025.tiff +test/2-seed/Training/5 Bromus secalinus/026.tiff +test/2-seed/Training/5 Bromus secalinus/027 (2).tiff +test/2-seed/Training/5 Bromus secalinus/027.tiff +test/2-seed/Training/5 Bromus secalinus/028 (2).tiff +test/2-seed/Training/5 Bromus secalinus/028.tiff +test/2-seed/Training/5 Bromus secalinus/029.tiff +test/2-seed/Training/5 Bromus secalinus/030 (2).tiff +test/2-seed/Training/5 Bromus secalinus/030.tiff +test/2-seed/Training/5 Bromus secalinus/031 (2).tiff +test/2-seed/Training/5 Bromus secalinus/031.tiff +test/2-seed/Training/5 Bromus secalinus/032 (2).tiff +test/2-seed/Training/5 Bromus secalinus/032.tiff +test/2-seed/Training/5 Bromus secalinus/033 (2).tiff +test/2-seed/Training/5 Bromus secalinus/033.tiff +test/2-seed/Training/5 Bromus secalinus/034 (2).tiff +test/2-seed/Training/5 Bromus secalinus/034.tiff +test/2-seed/Training/5 Bromus secalinus/035 (2).tiff +test/2-seed/Training/5 Bromus secalinus/035.tiff +test/2-seed/Training/5 Bromus secalinus/036.tiff +test/2-seed/Training/5 Bromus secalinus/037.tiff +test/2-seed/Training/5 Bromus secalinus/038.tiff +test/2-seed/Training/5 Bromus secalinus/039 (2).tiff +test/2-seed/Training/5 Bromus secalinus/039.tiff +test/2-seed/Training/5 Bromus secalinus/040 (2).tiff +test/2-seed/Training/5 Bromus secalinus/040.tiff +test/2-seed/Training/5 Bromus secalinus/041 (2).tiff +test/2-seed/Training/5 Bromus secalinus/041.tiff +test/2-seed/Training/5 Bromus secalinus/042 (2).tiff +test/2-seed/Training/5 Bromus secalinus/042.tiff +test/2-seed/Training/5 Bromus secalinus/043 (2).tiff +test/2-seed/Training/5 Bromus secalinus/043.tiff +test/2-seed/Training/5 Bromus secalinus/044 (2).tiff +test/2-seed/Training/5 Bromus secalinus/044.tiff +test/2-seed/Training/5 Bromus secalinus/045 (2).tiff +test/2-seed/Training/5 Bromus secalinus/046 (2).tiff +test/2-seed/Training/5 Bromus secalinus/046.tiff +test/2-seed/Training/5 Bromus secalinus/047 (2).tiff +test/2-seed/Training/5 Bromus secalinus/047.tiff +test/2-seed/Training/5 Bromus secalinus/048 (2).tiff +test/2-seed/Training/5 Bromus secalinus/048.tiff +test/2-seed/Training/5 Bromus secalinus/049 (2).tiff +test/2-seed/Training/5 Bromus secalinus/049.tiff +test/2-seed/Training/5 Bromus secalinus/050 (2).tiff +test/2-seed/Training/5 Bromus secalinus/050.tiff +test/2-seed/Training/5 Bromus secalinus/051 (2).tiff +test/2-seed/Training/5 Bromus secalinus/052.tiff +test/2-seed/Training/5 Bromus secalinus/201 (2).tiff +test/2-seed/Training/5 Bromus secalinus/202 (2).tiff +test/2-seed/Training/5 Bromus secalinus/203 (2).tiff +test/2-seed/Training/5 Bromus secalinus/204 (2).tiff +test/2-seed/Training/5 Bromus secalinus/205 (2).tiff +test/2-seed/Training/5 Bromus secalinus/206 (2).tiff +test/2-seed/Training/5 Bromus secalinus/207 (2).tiff +test/2-seed/Training/5 Bromus secalinus/208 (2).tiff +test/2-seed/Training/5 Bromus secalinus/209 (2).tiff +test/2-seed/Training/5 Bromus secalinus/210 (2).tiff +test/2-seed/Training/5 Bromus secalinus/211 (2).tiff +test/2-seed/Training/5 Bromus secalinus/212 (2).tiff +test/2-seed/Training/5 Bromus secalinus/213 (2).tiff +test/2-seed/Training/5 Bromus secalinus/214 (2).tiff +test/2-seed/Training/5 Bromus secalinus/Bromus secalinus 2.tiff +test/2-seed/Training/5 Bromus secalinus/Bromus secalinus.tiff +test/2-seed/Training/6 Bromus hordeaceus/001.tiff +test/2-seed/Training/6 Bromus hordeaceus/002.tiff +test/2-seed/Training/6 Bromus hordeaceus/003.tiff +test/2-seed/Training/6 Bromus hordeaceus/004.tiff +test/2-seed/Training/6 Bromus hordeaceus/005.tiff +test/2-seed/Training/6 Bromus hordeaceus/006.tiff +test/2-seed/Training/6 Bromus hordeaceus/007.tiff +test/2-seed/Training/6 Bromus hordeaceus/008.tiff +test/2-seed/Training/6 Bromus hordeaceus/009.tiff +test/2-seed/Training/6 Bromus hordeaceus/010.tiff +test/2-seed/Training/6 Bromus hordeaceus/011.tiff +test/2-seed/Training/6 Bromus hordeaceus/012.tiff +test/2-seed/Training/6 Bromus hordeaceus/013.tiff +test/2-seed/Training/6 Bromus hordeaceus/014.tiff +test/2-seed/Training/6 Bromus hordeaceus/015.tiff +test/2-seed/Training/6 Bromus hordeaceus/016.tiff +test/2-seed/Training/6 Bromus hordeaceus/017.tiff +test/2-seed/Training/6 Bromus hordeaceus/018.tiff +test/2-seed/Training/6 Bromus hordeaceus/019.tiff +test/2-seed/Training/6 Bromus hordeaceus/020.tiff +test/2-seed/Training/6 Bromus hordeaceus/21.tiff +test/2-seed/Training/6 Bromus hordeaceus/22.tiff +test/2-seed/Training/6 Bromus hordeaceus/23.tiff +test/2-seed/Training/6 Bromus hordeaceus/24.tiff +test/2-seed/Training/6 Bromus hordeaceus/025.tiff +test/2-seed/Training/6 Bromus hordeaceus/026.tiff +test/2-seed/Training/6 Bromus hordeaceus/027.tiff +test/2-seed/Training/6 Bromus hordeaceus/028.tiff +test/2-seed/Training/6 Bromus hordeaceus/029.tiff +test/2-seed/Training/6 Bromus hordeaceus/030.tiff +test/2-seed/Training/6 Bromus hordeaceus/031.tiff +test/2-seed/Training/6 Bromus hordeaceus/032.tiff +test/2-seed/Training/6 Bromus hordeaceus/033.tiff +test/2-seed/Training/6 Bromus hordeaceus/036.tiff +test/2-seed/Training/6 Bromus hordeaceus/037.tiff +test/2-seed/Training/6 Bromus hordeaceus/038.tiff +test/2-seed/Training/6 Bromus hordeaceus/039.tiff +test/2-seed/Training/6 Bromus hordeaceus/040.tiff +test/2-seed/Training/6 Bromus hordeaceus/041.tiff +test/2-seed/Training/6 Bromus hordeaceus/042.tiff +test/2-seed/Training/6 Bromus hordeaceus/043.tiff +test/2-seed/Training/6 Bromus hordeaceus/044.tiff +test/2-seed/Training/6 Bromus hordeaceus/045.tiff +test/2-seed/Training/6 Bromus hordeaceus/046.tiff +test/2-seed/Training/6 Bromus hordeaceus/047.tiff +test/2-seed/Training/6 Bromus hordeaceus/048.tiff +test/2-seed/Training/6 Bromus hordeaceus/050.tiff +test/2-seed/Training/6 Bromus hordeaceus/051.tiff +test/2-seed/Training/6 Bromus hordeaceus/052.tiff +test/2-seed/Training/6 Bromus hordeaceus/053.tiff +test/2-seed/Training/6 Bromus hordeaceus/054.tiff +test/2-seed/Training/6 Bromus hordeaceus/055.tiff +test/2-seed/Training/6 Bromus hordeaceus/056.tiff +test/2-seed/Training/6 Bromus hordeaceus/057.tiff +test/2-seed/Training/6 Bromus hordeaceus/058.tiff +test/2-seed/Training/6 Bromus hordeaceus/059.tiff +test/2-seed/Training/6 Bromus hordeaceus/060.tiff +test/2-seed/Training/6 Bromus hordeaceus/061.tiff +test/2-seed/Training/6 Bromus hordeaceus/062.tiff +test/2-seed/Training/6 Bromus hordeaceus/063.tiff +test/2-seed/Training/6 Bromus hordeaceus/064.tiff +test/2-seed/Training/6 Bromus hordeaceus/065.tiff +test/2-seed/Training/6 Bromus hordeaceus/066.tiff +test/2-seed/Training/6 Bromus hordeaceus/067.tiff +test/2-seed/Training/6 Bromus hordeaceus/068.tiff +test/2-seed/Training/6 Bromus hordeaceus/069.tiff +test/2-seed/Training/6 Bromus hordeaceus/070.tiff +test/2-seed/Training/6 Bromus hordeaceus/071.tiff +test/2-seed/Training/6 Bromus hordeaceus/072.tiff +test/2-seed/Training/6 Bromus hordeaceus/073.tiff +test/2-seed/Training/6 Bromus hordeaceus/074.tiff +test/2-seed/Training/6 Bromus hordeaceus/075.tiff +test/2-seed/Training/6 Bromus hordeaceus/076.tiff +test/2-seed/Training/6 Bromus hordeaceus/077.tiff +test/2-seed/Training/6 Bromus hordeaceus/078.tiff +test/2-seed/Training/6 Bromus hordeaceus/079.tiff +test/2-seed/Training/6 Bromus hordeaceus/080.tiff +test/2-seed/Training/6 Bromus hordeaceus/081.tiff +test/2-seed/Training/6 Bromus hordeaceus/082.tiff +test/2-seed/Training/6 Bromus hordeaceus/083.tiff +test/2-seed/Training/6 Bromus hordeaceus/084.tiff +test/2-seed/Training/6 Bromus hordeaceus/085-1.tiff +test/2-seed/Training/6 Bromus hordeaceus/085.tiff +test/2-seed/Training/6 Bromus hordeaceus/086-1.tiff +test/2-seed/Training/6 Bromus hordeaceus/086.tiff +test/2-seed/Training/6 Bromus hordeaceus/087-1.tiff +test/2-seed/Training/6 Bromus hordeaceus/089.tiff +test/2-seed/Training/6 Bromus hordeaceus/090.tiff +test/2-seed/Training/6 Bromus hordeaceus/093.tiff +test/2-seed/Training/6 Bromus hordeaceus/094.tiff +test/2-seed/Training/6 Bromus hordeaceus/095.tiff +test/2-seed/Training/6 Bromus hordeaceus/096.tiff +test/2-seed/Training/6 Bromus hordeaceus/097.tiff +test/2-seed/Training/6 Bromus hordeaceus/098.tiff +test/2-seed/Training/6 Bromus hordeaceus/099.tiff +test/2-seed/Training/6 Bromus hordeaceus/601.tiff +test/2-seed/Training/6 Bromus hordeaceus/602.tiff +test/2-seed/Training/6 Bromus hordeaceus/603.tiff +test/2-seed/Training/6 Bromus hordeaceus/Bromus hordeaceus (2).tiff +test/2-seed/Training/6 Bromus hordeaceus/Bromus hordeaceus.tiff +test/2-seed/Training/7 Bromus japonicus/001 (2).tiff +test/2-seed/Training/7 Bromus japonicus/001.tiff +test/2-seed/Training/7 Bromus japonicus/002 (2).tiff +test/2-seed/Training/7 Bromus japonicus/002.tiff +test/2-seed/Training/7 Bromus japonicus/003 (2).tiff +test/2-seed/Training/7 Bromus japonicus/003.tiff +test/2-seed/Training/7 Bromus japonicus/004 (2).tiff +test/2-seed/Training/7 Bromus japonicus/004.tiff +test/2-seed/Training/7 Bromus japonicus/005.tiff +test/2-seed/Training/7 Bromus japonicus/006 (2).tiff +test/2-seed/Training/7 Bromus japonicus/006.tiff +test/2-seed/Training/7 Bromus japonicus/007 (2).tiff +test/2-seed/Training/7 Bromus japonicus/007.tiff +test/2-seed/Training/7 Bromus japonicus/008 (2).tiff +test/2-seed/Training/7 Bromus japonicus/008.tiff +test/2-seed/Training/7 Bromus japonicus/009 (2).tiff +test/2-seed/Training/7 Bromus japonicus/009.tiff +test/2-seed/Training/7 Bromus japonicus/010 (2).tiff +test/2-seed/Training/7 Bromus japonicus/010.tiff +test/2-seed/Training/7 Bromus japonicus/011 (2).tiff +test/2-seed/Training/7 Bromus japonicus/011.tiff +test/2-seed/Training/7 Bromus japonicus/012 (2).tiff +test/2-seed/Training/7 Bromus japonicus/012.tiff +test/2-seed/Training/7 Bromus japonicus/013 (2).tiff +test/2-seed/Training/7 Bromus japonicus/013.tiff +test/2-seed/Training/7 Bromus japonicus/014 (2).tiff +test/2-seed/Training/7 Bromus japonicus/014.tiff +test/2-seed/Training/7 Bromus japonicus/015 (2).tiff +test/2-seed/Training/7 Bromus japonicus/015.tiff +test/2-seed/Training/7 Bromus japonicus/016.tiff +test/2-seed/Training/7 Bromus japonicus/017 (2).tiff +test/2-seed/Training/7 Bromus japonicus/017.tiff +test/2-seed/Training/7 Bromus japonicus/018 (2).tiff +test/2-seed/Training/7 Bromus japonicus/018.tiff +test/2-seed/Training/7 Bromus japonicus/019.tiff +test/2-seed/Training/7 Bromus japonicus/020.tiff +test/2-seed/Training/7 Bromus japonicus/021 (2).tiff +test/2-seed/Training/7 Bromus japonicus/021.tiff +test/2-seed/Training/7 Bromus japonicus/022 (2).tiff +test/2-seed/Training/7 Bromus japonicus/022.tiff +test/2-seed/Training/7 Bromus japonicus/023 (2).tiff +test/2-seed/Training/7 Bromus japonicus/023.tiff +test/2-seed/Training/7 Bromus japonicus/024 (2).tiff +test/2-seed/Training/7 Bromus japonicus/024.tiff +test/2-seed/Training/7 Bromus japonicus/025 (2).tiff +test/2-seed/Training/7 Bromus japonicus/025.tiff +test/2-seed/Training/7 Bromus japonicus/026 (2).tiff +test/2-seed/Training/7 Bromus japonicus/026.tiff +test/2-seed/Training/7 Bromus japonicus/027 (2).tiff +test/2-seed/Training/7 Bromus japonicus/027.tiff +test/2-seed/Training/7 Bromus japonicus/028 (2).tiff +test/2-seed/Training/7 Bromus japonicus/028.tiff +test/2-seed/Training/7 Bromus japonicus/029.tiff +test/2-seed/Training/7 Bromus japonicus/030 (2).tiff +test/2-seed/Training/7 Bromus japonicus/030.tiff +test/2-seed/Training/7 Bromus japonicus/031 (2).tiff +test/2-seed/Training/7 Bromus japonicus/031.tiff +test/2-seed/Training/7 Bromus japonicus/032.tiff +test/2-seed/Training/7 Bromus japonicus/033.tiff +test/2-seed/Training/7 Bromus japonicus/034.tiff +test/2-seed/Training/7 Bromus japonicus/035.tiff +test/2-seed/Training/7 Bromus japonicus/036.tiff +test/2-seed/Training/7 Bromus japonicus/037.tiff +test/2-seed/Training/7 Bromus japonicus/038.tiff +test/2-seed/Training/7 Bromus japonicus/039.tiff +test/2-seed/Training/7 Bromus japonicus/040.tiff +test/2-seed/Training/7 Bromus japonicus/042.tiff +test/2-seed/Training/7 Bromus japonicus/043 (2).tiff +test/2-seed/Training/7 Bromus japonicus/043.tiff +test/2-seed/Training/7 Bromus japonicus/044.tiff +test/2-seed/Training/7 Bromus japonicus/045 (2).tiff +test/2-seed/Training/7 Bromus japonicus/045.tiff +test/2-seed/Training/7 Bromus japonicus/046 (2).tiff +test/2-seed/Training/7 Bromus japonicus/046.tiff +test/2-seed/Training/7 Bromus japonicus/047 (2).tiff +test/2-seed/Training/7 Bromus japonicus/048.tiff +test/2-seed/Training/7 Bromus japonicus/049 (2).tiff +test/2-seed/Training/7 Bromus japonicus/049.tiff +test/2-seed/Training/7 Bromus japonicus/050 (2).tiff +test/2-seed/Training/7 Bromus japonicus/051 (2).tiff +test/2-seed/Training/7 Bromus japonicus/051.tiff +test/2-seed/Training/7 Bromus japonicus/052 (2).tiff +test/2-seed/Training/7 Bromus japonicus/052.tiff +test/2-seed/Training/7 Bromus japonicus/053 (2).tiff +test/2-seed/Training/7 Bromus japonicus/053.tiff +test/2-seed/Training/7 Bromus japonicus/054.tiff +test/2-seed/Training/7 Bromus japonicus/055.tiff +test/2-seed/Training/7 Bromus japonicus/056.tiff +test/2-seed/Training/7 Bromus japonicus/057.tiff +test/2-seed/Training/7 Bromus japonicus/058.tiff +test/2-seed/Training/7 Bromus japonicus/059.tiff +test/2-seed/Training/7 Bromus japonicus/201.tiff +test/2-seed/Training/7 Bromus japonicus/202.tiff +test/2-seed/Training/7 Bromus japonicus/203.tiff +test/2-seed/Training/7 Bromus japonicus/205.tiff +test/2-seed/Training/7 Bromus japonicus/206.tiff +test/2-seed/Training/7 Bromus japonicus/207.tiff +test/2-seed/Training/7 Bromus japonicus/208.tiff +test/2-seed/Training/7 Bromus japonicus/209.tiff +test/2-seed/Training/7 Bromus japonicus/210.tiff +test/2-seed/Training/7 Bromus japonicus/211.tiff +test/2-seed/Training/7 Bromus japonicus/212.tiff +test/2-seed/Training/7 Bromus japonicus/213.tiff +test/2-seed/Training/7 Bromus japonicus/214.tiff +test/2-seed/Training/7 Bromus japonicus/215.tiff +test/2-seed/Training/7 Bromus japonicus/216.tiff +test/2-seed/Training/7 Bromus japonicus/221.tiff +test/2-seed/Training/7 Bromus japonicus/222.tiff +test/2-seed/Training/7 Bromus japonicus/223.tiff +test/2-seed/Training/7 Bromus japonicus/224.tiff +test/2-seed/Training/7 Bromus japonicus/225.tiff +test/2-seed/Training/7 Bromus japonicus/226.tiff +test/2-seed/Training/7 Bromus japonicus/227.tiff +test/2-seed/Training/7 Bromus japonicus/228.tiff +test/2-seed/Training/7 Bromus japonicus/601.tiff +test/2-seed/Training/7 Bromus japonicus/Bromus japonicus (2).tiff +test/2-seed/Training/7 Bromus japonicus/Bromus japonicus.tiff +test/2-seed/Training/8 Lolium temulentum/001.tiff +test/2-seed/Training/8 Lolium temulentum/002.tiff +test/2-seed/Training/8 Lolium temulentum/003.tiff +test/2-seed/Training/8 Lolium temulentum/004.tiff +test/2-seed/Training/8 Lolium temulentum/005.tiff +test/2-seed/Training/8 Lolium temulentum/006.tiff +test/2-seed/Training/8 Lolium temulentum/007.tiff +test/2-seed/Training/8 Lolium temulentum/008.tiff +test/2-seed/Training/8 Lolium temulentum/009.tiff +test/2-seed/Training/8 Lolium temulentum/010.tiff +test/2-seed/Training/8 Lolium temulentum/011 (2).tiff +test/2-seed/Training/8 Lolium temulentum/011.tiff +test/2-seed/Training/8 Lolium temulentum/013 (2).tiff +test/2-seed/Training/8 Lolium temulentum/013.tiff +test/2-seed/Training/8 Lolium temulentum/014 (2).tiff +test/2-seed/Training/8 Lolium temulentum/014.tiff +test/2-seed/Training/8 Lolium temulentum/015 (2).tiff +test/2-seed/Training/8 Lolium temulentum/017 (2).tiff +test/2-seed/Training/8 Lolium temulentum/018 (2).tiff +test/2-seed/Training/8 Lolium temulentum/019 (2).tiff +test/2-seed/Training/8 Lolium temulentum/019.tiff +test/2-seed/Training/8 Lolium temulentum/020 (2).tiff +test/2-seed/Training/8 Lolium temulentum/020.tiff +test/2-seed/Training/8 Lolium temulentum/021 (2).tiff +test/2-seed/Training/8 Lolium temulentum/022.tiff +test/2-seed/Training/8 Lolium temulentum/023 (2).tiff +test/2-seed/Training/8 Lolium temulentum/023.tiff +test/2-seed/Training/8 Lolium temulentum/024.tiff +test/2-seed/Training/8 Lolium temulentum/025 (2).tiff +test/2-seed/Training/8 Lolium temulentum/026 (2).tiff +test/2-seed/Training/8 Lolium temulentum/026.tiff +test/2-seed/Training/8 Lolium temulentum/027.tiff +test/2-seed/Training/8 Lolium temulentum/028 (2).tiff +test/2-seed/Training/8 Lolium temulentum/028.tiff +test/2-seed/Training/8 Lolium temulentum/029 (2).tiff +test/2-seed/Training/8 Lolium temulentum/029.tiff +test/2-seed/Training/8 Lolium temulentum/030 (2).tiff +test/2-seed/Training/8 Lolium temulentum/031 (2).tiff +test/2-seed/Training/8 Lolium temulentum/031.tiff +test/2-seed/Training/8 Lolium temulentum/032 (2).tiff +test/2-seed/Training/8 Lolium temulentum/032.tiff +test/2-seed/Training/8 Lolium temulentum/033.tiff +test/2-seed/Training/8 Lolium temulentum/034 (2).tiff +test/2-seed/Training/8 Lolium temulentum/034.tiff +test/2-seed/Training/8 Lolium temulentum/035 (2).tiff +test/2-seed/Training/8 Lolium temulentum/035.tiff +test/2-seed/Training/8 Lolium temulentum/036 (2).tiff +test/2-seed/Training/8 Lolium temulentum/036.tiff +test/2-seed/Training/8 Lolium temulentum/037 (2).tiff +test/2-seed/Training/8 Lolium temulentum/037.tiff +test/2-seed/Training/8 Lolium temulentum/038 (2).tiff +test/2-seed/Training/8 Lolium temulentum/038.tiff +test/2-seed/Training/8 Lolium temulentum/040 (2).tiff +test/2-seed/Training/8 Lolium temulentum/040.tiff +test/2-seed/Training/8 Lolium temulentum/041 (2).tiff +test/2-seed/Training/8 Lolium temulentum/041.tiff +test/2-seed/Training/8 Lolium temulentum/042 (2).tiff +test/2-seed/Training/8 Lolium temulentum/042.tiff +test/2-seed/Training/8 Lolium temulentum/043 (2).tiff +test/2-seed/Training/8 Lolium temulentum/043.tiff +test/2-seed/Training/8 Lolium temulentum/044 (2).tiff +test/2-seed/Training/8 Lolium temulentum/044.tiff +test/2-seed/Training/8 Lolium temulentum/046 (2).tiff +test/2-seed/Training/8 Lolium temulentum/046.tiff +test/2-seed/Training/8 Lolium temulentum/047 (2).tiff +test/2-seed/Training/8 Lolium temulentum/047.tiff +test/2-seed/Training/8 Lolium temulentum/048 (2).tiff +test/2-seed/Training/8 Lolium temulentum/048.tiff +test/2-seed/Training/8 Lolium temulentum/049 (2).tiff +test/2-seed/Training/8 Lolium temulentum/049.tiff +test/2-seed/Training/8 Lolium temulentum/050 (2).tiff +test/2-seed/Training/8 Lolium temulentum/050.tiff +test/2-seed/Training/8 Lolium temulentum/051 (2).tiff +test/2-seed/Training/8 Lolium temulentum/051.tiff +test/2-seed/Training/8 Lolium temulentum/052.tiff +test/2-seed/Training/8 Lolium temulentum/053 (2).tiff +test/2-seed/Training/8 Lolium temulentum/053.tiff +test/2-seed/Training/8 Lolium temulentum/054 (2).tiff +test/2-seed/Training/8 Lolium temulentum/101.tiff +test/2-seed/Training/8 Lolium temulentum/102.tiff +test/2-seed/Training/8 Lolium temulentum/103.tiff +test/2-seed/Training/8 Lolium temulentum/104.tiff +test/2-seed/Training/8 Lolium temulentum/105.tiff +test/2-seed/Training/8 Lolium temulentum/107.tiff +test/2-seed/Training/8 Lolium temulentum/108.tiff +test/2-seed/Training/8 Lolium temulentum/109.tiff +test/2-seed/Training/8 Lolium temulentum/110.tiff +test/2-seed/Training/8 Lolium temulentum/201.tiff +test/2-seed/Training/8 Lolium temulentum/202.tiff +test/2-seed/Training/8 Lolium temulentum/203.tiff +test/2-seed/Training/8 Lolium temulentum/204.tiff +test/2-seed/Training/8 Lolium temulentum/205.tiff +test/2-seed/Training/8 Lolium temulentum/206.tiff +test/2-seed/Training/8 Lolium temulentum/207.tiff +test/2-seed/Training/8 Lolium temulentum/208.tiff +test/2-seed/Training/8 Lolium temulentum/209.tiff +test/2-seed/Training/8 Lolium temulentum/210.tiff +test/2-seed/Training/8 Lolium temulentum/211.tiff +test/2-seed/Training/8 Lolium temulentum/222.tiff +test/2-seed/Training/8 Lolium temulentum/223.tiff +test/2-seed/Training/8 Lolium temulentum/224.tiff +test/2-seed/Training/8 Lolium temulentum/225.tiff +test/2-seed/Training/8 Lolium temulentum/226.tiff +test/2-seed/Training/8 Lolium temulentum/227.tiff +test/2-seed/Training/8 Lolium temulentum/Lolium temulentum 1.tiff +test/2-seed/Training/8 Lolium temulentum/Lolium temulentum.tiff +test/2-seed/Training/9 Solanum carolinense/1.tiff +test/2-seed/Training/9 Solanum carolinense/2.tiff +test/2-seed/Training/9 Solanum carolinense/3.tiff +test/2-seed/Training/9 Solanum carolinense/4.tiff +test/2-seed/Training/9 Solanum carolinense/5.tiff +test/2-seed/Training/9 Solanum carolinense/6.tiff +test/2-seed/Training/9 Solanum carolinense/7.tiff +test/2-seed/Training/9 Solanum carolinense/8.tiff +test/2-seed/Training/9 Solanum carolinense/9.tiff +test/2-seed/Training/9 Solanum carolinense/10.tiff +test/2-seed/Training/9 Solanum carolinense/11.tiff +test/2-seed/Training/9 Solanum carolinense/12.tiff +test/2-seed/Training/9 Solanum carolinense/13.tiff +test/2-seed/Training/9 Solanum carolinense/14.tiff +test/2-seed/Training/9 Solanum carolinense/15.tiff +test/2-seed/Training/9 Solanum carolinense/16.tiff +test/2-seed/Training/9 Solanum carolinense/17.tiff +test/2-seed/Training/9 Solanum carolinense/18.tiff +test/2-seed/Training/9 Solanum carolinense/19.tiff +test/2-seed/Training/9 Solanum carolinense/20.tiff +test/2-seed/Training/9 Solanum carolinense/21.tiff +test/2-seed/Training/9 Solanum carolinense/22.tiff +test/2-seed/Training/9 Solanum carolinense/23.tiff +test/2-seed/Training/9 Solanum carolinense/24.tiff +test/2-seed/Training/9 Solanum carolinense/25.tiff +test/2-seed/Training/9 Solanum carolinense/26.tiff +test/2-seed/Training/9 Solanum carolinense/27.tiff +test/2-seed/Training/9 Solanum carolinense/28.tiff +test/2-seed/Training/9 Solanum carolinense/29.tiff +test/2-seed/Training/9 Solanum carolinense/30.tiff +test/2-seed/Training/9 Solanum carolinense/31.tiff +test/2-seed/Training/9 Solanum carolinense/32.tiff +test/2-seed/Training/9 Solanum carolinense/33.tiff +test/2-seed/Training/9 Solanum carolinense/34.tiff +test/2-seed/Training/9 Solanum carolinense/35.tiff +test/2-seed/Training/9 Solanum carolinense/36.tiff +test/2-seed/Training/9 Solanum carolinense/37.tiff +test/2-seed/Training/9 Solanum carolinense/38.tiff +test/2-seed/Training/9 Solanum carolinense/39.tiff +test/2-seed/Training/9 Solanum carolinense/40.tiff +test/2-seed/Training/9 Solanum carolinense/41.tiff +test/2-seed/Training/9 Solanum carolinense/42.tiff +test/2-seed/Training/9 Solanum carolinense/43.tiff +test/2-seed/Training/9 Solanum carolinense/44.tiff +test/2-seed/Training/9 Solanum carolinense/45.tiff +test/2-seed/Training/9 Solanum carolinense/46.tiff +test/2-seed/Training/9 Solanum carolinense/47.tiff +test/2-seed/Training/9 Solanum carolinense/48.tiff +test/2-seed/Training/9 Solanum carolinense/49.tiff +test/2-seed/Training/9 Solanum carolinense/50.tiff +test/6-seed/Testing/0 Brassica napus/801.json +test/6-seed/Testing/0 Brassica napus/801.tiff +test/6-seed/Testing/0 Brassica napus/802.json +test/6-seed/Testing/0 Brassica napus/802.tiff +test/6-seed/Testing/0 Brassica napus/803.json +test/6-seed/Testing/0 Brassica napus/803.tiff +test/6-seed/Testing/0 Brassica napus/804.json +test/6-seed/Testing/0 Brassica napus/804.tiff +test/6-seed/Testing/0 Brassica napus/805.json +test/6-seed/Testing/0 Brassica napus/805.tiff +test/6-seed/Testing/0 Brassica napus/806.json +test/6-seed/Testing/0 Brassica napus/806.tiff +test/6-seed/Testing/0 Brassica napus/807.json +test/6-seed/Testing/0 Brassica napus/807.tiff +test/6-seed/Testing/0 Brassica napus/808.json +test/6-seed/Testing/0 Brassica napus/808.tiff +test/6-seed/Testing/0 Brassica napus/809.json +test/6-seed/Testing/0 Brassica napus/809.tiff +test/6-seed/Testing/0 Brassica napus/810.json +test/6-seed/Testing/0 Brassica napus/810.tiff +test/6-seed/Testing/0 Brassica napus/811.json +test/6-seed/Testing/0 Brassica napus/811.tiff +test/6-seed/Testing/0 Brassica napus/813.json +test/6-seed/Testing/0 Brassica napus/813.tiff +test/6-seed/Testing/0 Brassica napus/Brassica napus.json +test/6-seed/Testing/0 Brassica napus/Brassica napus.tiff +test/6-seed/Testing/0 Brassica napus/index.json +test/6-seed/Testing/1 Brassica junsea/851.json +test/6-seed/Testing/1 Brassica junsea/851.tiff +test/6-seed/Testing/1 Brassica junsea/852.json +test/6-seed/Testing/1 Brassica junsea/852.tiff +test/6-seed/Testing/1 Brassica junsea/853.json +test/6-seed/Testing/1 Brassica junsea/853.tiff +test/6-seed/Testing/1 Brassica junsea/854.json +test/6-seed/Testing/1 Brassica junsea/854.tiff +test/6-seed/Testing/1 Brassica junsea/856.json +test/6-seed/Testing/1 Brassica junsea/856.tiff +test/6-seed/Testing/1 Brassica junsea/857.json +test/6-seed/Testing/1 Brassica junsea/857.tiff +test/6-seed/Testing/1 Brassica junsea/858.json +test/6-seed/Testing/1 Brassica junsea/858.tiff +test/6-seed/Testing/1 Brassica junsea/859.json +test/6-seed/Testing/1 Brassica junsea/859.tiff +test/6-seed/Testing/1 Brassica junsea/860.json +test/6-seed/Testing/1 Brassica junsea/860.tiff +test/6-seed/Testing/1 Brassica junsea/862.json +test/6-seed/Testing/1 Brassica junsea/862.tiff +test/6-seed/Testing/1 Brassica junsea/863.json +test/6-seed/Testing/1 Brassica junsea/863.tiff +test/6-seed/Testing/1 Brassica junsea/961.json +test/6-seed/Testing/1 Brassica junsea/961.tiff +test/6-seed/Testing/1 Brassica junsea/Brassica junsea.json +test/6-seed/Testing/1 Brassica junsea/Brassica junsea.tiff +test/6-seed/Testing/1 Brassica junsea/index.json +test/6-seed/Testing/10 Solanum nigrum/1.tiff +test/6-seed/Testing/10 Solanum nigrum/2.tiff +test/6-seed/Testing/10 Solanum nigrum/3.tiff +test/6-seed/Testing/10 Solanum nigrum/4.tiff +test/6-seed/Testing/10 Solanum nigrum/5.tiff +test/6-seed/Testing/10 Solanum nigrum/6.tiff +test/6-seed/Testing/10 Solanum nigrum/8.tiff +test/6-seed/Testing/10 Solanum nigrum/9.tiff +test/6-seed/Testing/10 Solanum nigrum/Solanum nigrum.tiff +test/6-seed/Testing/11 Solanum rostratum/1.tiff +test/6-seed/Testing/11 Solanum rostratum/2.tiff +test/6-seed/Testing/11 Solanum rostratum/3.tiff +test/6-seed/Testing/11 Solanum rostratum/4.tiff +test/6-seed/Testing/11 Solanum rostratum/6.tiff +test/6-seed/Testing/11 Solanum rostratum/7.tiff +test/6-seed/Testing/11 Solanum rostratum/8.tiff +test/6-seed/Testing/11 Solanum rostratum/9.tiff +test/6-seed/Testing/11 Solanum rostratum/Solanum rostratum.tiff +test/6-seed/Testing/12 Ambrosia artemisiifolia/1.tiff +test/6-seed/Testing/12 Ambrosia artemisiifolia/2.tiff +test/6-seed/Testing/12 Ambrosia artemisiifolia/3.tiff +test/6-seed/Testing/12 Ambrosia artemisiifolia/4.tiff +test/6-seed/Testing/12 Ambrosia artemisiifolia/6.tiff +test/6-seed/Testing/12 Ambrosia artemisiifolia/7.tiff +test/6-seed/Testing/12 Ambrosia artemisiifolia/8.tiff +test/6-seed/Testing/12 Ambrosia artemisiifolia/9.tiff +test/6-seed/Testing/12 Ambrosia artemisiifolia/Ambrosia artemisiifolia.tiff +test/6-seed/Testing/13 Ambrosia trifida/1.tiff +test/6-seed/Testing/13 Ambrosia trifida/3.tiff +test/6-seed/Testing/13 Ambrosia trifida/4.tiff +test/6-seed/Testing/13 Ambrosia trifida/5.tiff +test/6-seed/Testing/13 Ambrosia trifida/6.tiff +test/6-seed/Testing/13 Ambrosia trifida/7.tiff +test/6-seed/Testing/13 Ambrosia trifida/8.tiff +test/6-seed/Testing/13 Ambrosia trifida/9.tiff +test/6-seed/Testing/13 Ambrosia trifida/Ambrosia trifida.tiff +test/6-seed/Testing/14 Ambrosia psilostachya/1.tiff +test/6-seed/Testing/14 Ambrosia psilostachya/2.tiff +test/6-seed/Testing/14 Ambrosia psilostachya/4.tiff +test/6-seed/Testing/14 Ambrosia psilostachya/5.tiff +test/6-seed/Testing/14 Ambrosia psilostachya/6.tiff +test/6-seed/Testing/14 Ambrosia psilostachya/7.tiff +test/6-seed/Testing/14 Ambrosia psilostachya/8.tiff +test/6-seed/Testing/14 Ambrosia psilostachya/9.tiff +test/6-seed/Testing/14 Ambrosia psilostachya/Ambrosia psilostachya.tiff +test/6-seed/Testing/2 Cirsium arvense/1.json +test/6-seed/Testing/2 Cirsium arvense/1.tiff +test/6-seed/Testing/2 Cirsium arvense/2.json +test/6-seed/Testing/2 Cirsium arvense/2.tiff +test/6-seed/Testing/2 Cirsium arvense/3.json +test/6-seed/Testing/2 Cirsium arvense/3.tiff +test/6-seed/Testing/2 Cirsium arvense/4.json +test/6-seed/Testing/2 Cirsium arvense/4.tiff +test/6-seed/Testing/2 Cirsium arvense/5.json +test/6-seed/Testing/2 Cirsium arvense/5.tiff +test/6-seed/Testing/2 Cirsium arvense/6.json +test/6-seed/Testing/2 Cirsium arvense/6.tiff +test/6-seed/Testing/2 Cirsium arvense/7.json +test/6-seed/Testing/2 Cirsium arvense/7.tiff +test/6-seed/Testing/2 Cirsium arvense/9.json +test/6-seed/Testing/2 Cirsium arvense/9.tiff +test/6-seed/Testing/2 Cirsium arvense/Cirsium arvense.json +test/6-seed/Testing/2 Cirsium arvense/Cirsium arvense.tiff +test/6-seed/Testing/2 Cirsium arvense/index.json +test/6-seed/Testing/3 Cirsium vulgare/1.tiff +test/6-seed/Testing/3 Cirsium vulgare/2.tiff +test/6-seed/Testing/3 Cirsium vulgare/3.tiff +test/6-seed/Testing/3 Cirsium vulgare/4.tiff +test/6-seed/Testing/3 Cirsium vulgare/5.tiff +test/6-seed/Testing/3 Cirsium vulgare/6.tiff +test/6-seed/Testing/3 Cirsium vulgare/7.tiff +test/6-seed/Testing/3 Cirsium vulgare/9.tiff +test/6-seed/Testing/3 Cirsium vulgare/Cirsium vulgare.tiff +test/6-seed/Testing/4 Carduus nutans/1.tiff +test/6-seed/Testing/4 Carduus nutans/2.tiff +test/6-seed/Testing/4 Carduus nutans/3.tiff +test/6-seed/Testing/4 Carduus nutans/5.tiff +test/6-seed/Testing/4 Carduus nutans/6.tiff +test/6-seed/Testing/4 Carduus nutans/7.tiff +test/6-seed/Testing/4 Carduus nutans/8.tiff +test/6-seed/Testing/4 Carduus nutans/9.tiff +test/6-seed/Testing/4 Carduus nutans/Carduus nutans.tiff +test/6-seed/Testing/5 Bromus secalinus/651.tiff +test/6-seed/Testing/5 Bromus secalinus/652.tiff +test/6-seed/Testing/5 Bromus secalinus/653.tiff +test/6-seed/Testing/5 Bromus secalinus/654.tiff +test/6-seed/Testing/5 Bromus secalinus/655.tiff +test/6-seed/Testing/5 Bromus secalinus/656.tiff +test/6-seed/Testing/5 Bromus secalinus/657.tiff +test/6-seed/Testing/5 Bromus secalinus/658.tiff +test/6-seed/Testing/5 Bromus secalinus/659.tiff +test/6-seed/Testing/5 Bromus secalinus/661.tiff +test/6-seed/Testing/5 Bromus secalinus/662.tiff +test/6-seed/Testing/5 Bromus secalinus/663.tiff +test/6-seed/Testing/5 Bromus secalinus/664.tiff +test/6-seed/Testing/5 Bromus secalinus/665.tiff +test/6-seed/Testing/5 Bromus secalinus/666.tiff +test/6-seed/Testing/5 Bromus secalinus/667.tiff +test/6-seed/Testing/5 Bromus secalinus/668.tiff +test/6-seed/Testing/5 Bromus secalinus/669.tiff +test/6-seed/Testing/5 Bromus secalinus/926.tiff +test/6-seed/Testing/6 Bromus hordeaceus/001.tiff +test/6-seed/Testing/6 Bromus hordeaceus/002.tiff +test/6-seed/Testing/6 Bromus hordeaceus/003.tiff +test/6-seed/Testing/6 Bromus hordeaceus/0004.tiff +test/6-seed/Testing/6 Bromus hordeaceus/005.tiff +test/6-seed/Testing/6 Bromus hordeaceus/006.tiff +test/6-seed/Testing/6 Bromus hordeaceus/007.tiff +test/6-seed/Testing/6 Bromus hordeaceus/008.tiff +test/6-seed/Testing/6 Bromus hordeaceus/009.tiff +test/6-seed/Testing/6 Bromus hordeaceus/010.tiff +test/6-seed/Testing/6 Bromus hordeaceus/014.tiff +test/6-seed/Testing/6 Bromus hordeaceus/015.tiff +test/6-seed/Testing/6 Bromus hordeaceus/016.tiff +test/6-seed/Testing/6 Bromus hordeaceus/017.tiff +test/6-seed/Testing/6 Bromus hordeaceus/018.tiff +test/6-seed/Testing/6 Bromus hordeaceus/019.tiff +test/6-seed/Testing/6 Bromus hordeaceus/020.tiff +test/6-seed/Testing/6 Bromus hordeaceus/021.tiff +test/6-seed/Testing/6 Bromus hordeaceus/022.tiff +test/6-seed/Testing/7 Bromus japonicus/001.tiff +test/6-seed/Testing/7 Bromus japonicus/003.tiff +test/6-seed/Testing/7 Bromus japonicus/602.tiff +test/6-seed/Testing/7 Bromus japonicus/604.tiff +test/6-seed/Testing/7 Bromus japonicus/605.tiff +test/6-seed/Testing/7 Bromus japonicus/606.tiff +test/6-seed/Testing/7 Bromus japonicus/607.tiff +test/6-seed/Testing/7 Bromus japonicus/608.tiff +test/6-seed/Testing/7 Bromus japonicus/609.tiff +test/6-seed/Testing/7 Bromus japonicus/610.tiff +test/6-seed/Testing/7 Bromus japonicus/611.tiff +test/6-seed/Testing/7 Bromus japonicus/612.tiff +test/6-seed/Testing/7 Bromus japonicus/613.tiff +test/6-seed/Testing/7 Bromus japonicus/614.tiff +test/6-seed/Testing/7 Bromus japonicus/615.tiff +test/6-seed/Testing/7 Bromus japonicus/616.tiff +test/6-seed/Testing/7 Bromus japonicus/617.tiff +test/6-seed/Testing/7 Bromus japonicus/618.tiff +test/6-seed/Testing/8 Lolium temulentum/001.tiff +test/6-seed/Testing/8 Lolium temulentum/002.tiff +test/6-seed/Testing/8 Lolium temulentum/003.tiff +test/6-seed/Testing/8 Lolium temulentum/004.tiff +test/6-seed/Testing/8 Lolium temulentum/005.tiff +test/6-seed/Testing/8 Lolium temulentum/006.tiff +test/6-seed/Testing/8 Lolium temulentum/007.tiff +test/6-seed/Testing/8 Lolium temulentum/008.tiff +test/6-seed/Testing/8 Lolium temulentum/009.tiff +test/6-seed/Testing/8 Lolium temulentum/011.tiff +test/6-seed/Testing/8 Lolium temulentum/012.tiff +test/6-seed/Testing/8 Lolium temulentum/013.tiff +test/6-seed/Testing/8 Lolium temulentum/014.tiff +test/6-seed/Testing/8 Lolium temulentum/015.tiff +test/6-seed/Testing/8 Lolium temulentum/016.tiff +test/6-seed/Testing/8 Lolium temulentum/017.tiff +test/6-seed/Testing/8 Lolium temulentum/018.tiff +test/6-seed/Testing/8 Lolium temulentum/019.tiff +test/6-seed/Testing/9 Solanum carolinense/1.tiff +test/6-seed/Testing/9 Solanum carolinense/2.tiff +test/6-seed/Testing/9 Solanum carolinense/3.tiff +test/6-seed/Testing/9 Solanum carolinense/4.tiff +test/6-seed/Testing/9 Solanum carolinense/5.tiff +test/6-seed/Testing/9 Solanum carolinense/6.tiff +test/6-seed/Testing/9 Solanum carolinense/8.tiff +test/6-seed/Testing/9 Solanum carolinense/9.tiff +test/6-seed/Testing/9 Solanum carolinense/Solanum carolinense.tiff +test/6-seed/Training/0 Brassica napus/001.json +test/6-seed/Training/0 Brassica napus/001.tiff +test/6-seed/Training/0 Brassica napus/003.json +test/6-seed/Training/0 Brassica napus/003.tiff +test/6-seed/Training/0 Brassica napus/004.json +test/6-seed/Training/0 Brassica napus/004.tiff +test/6-seed/Training/0 Brassica napus/005.json +test/6-seed/Training/0 Brassica napus/005.tiff +test/6-seed/Training/0 Brassica napus/006.json +test/6-seed/Training/0 Brassica napus/006.tiff +test/6-seed/Training/0 Brassica napus/007.json +test/6-seed/Training/0 Brassica napus/007.tiff +test/6-seed/Training/0 Brassica napus/008.json +test/6-seed/Training/0 Brassica napus/008.tiff +test/6-seed/Training/0 Brassica napus/009.json +test/6-seed/Training/0 Brassica napus/009.tiff +test/6-seed/Training/0 Brassica napus/010.json +test/6-seed/Training/0 Brassica napus/010.tiff +test/6-seed/Training/0 Brassica napus/011.json +test/6-seed/Training/0 Brassica napus/011.tiff +test/6-seed/Training/0 Brassica napus/012.json +test/6-seed/Training/0 Brassica napus/012.tiff +test/6-seed/Training/0 Brassica napus/013.json +test/6-seed/Training/0 Brassica napus/013.tiff +test/6-seed/Training/0 Brassica napus/014.json +test/6-seed/Training/0 Brassica napus/014.tiff +test/6-seed/Training/0 Brassica napus/024.json +test/6-seed/Training/0 Brassica napus/024.tiff +test/6-seed/Training/0 Brassica napus/201 (3).json +test/6-seed/Training/0 Brassica napus/201 (3).tiff +test/6-seed/Training/0 Brassica napus/201 (4).json +test/6-seed/Training/0 Brassica napus/201 (4).tiff +test/6-seed/Training/0 Brassica napus/201.json +test/6-seed/Training/0 Brassica napus/201.tiff +test/6-seed/Training/0 Brassica napus/202 (3).json +test/6-seed/Training/0 Brassica napus/202 (3).tiff +test/6-seed/Training/0 Brassica napus/202 (4).json +test/6-seed/Training/0 Brassica napus/202 (4).tiff +test/6-seed/Training/0 Brassica napus/202 (5).json +test/6-seed/Training/0 Brassica napus/202 (5).tiff +test/6-seed/Training/0 Brassica napus/202.json +test/6-seed/Training/0 Brassica napus/202.tiff +test/6-seed/Training/0 Brassica napus/203 (2).json +test/6-seed/Training/0 Brassica napus/203 (2).tiff +test/6-seed/Training/0 Brassica napus/203 (3).json +test/6-seed/Training/0 Brassica napus/203 (3).tiff +test/6-seed/Training/0 Brassica napus/203 (4).json +test/6-seed/Training/0 Brassica napus/203 (4).tiff +test/6-seed/Training/0 Brassica napus/203 (5).json +test/6-seed/Training/0 Brassica napus/203 (5).tiff +test/6-seed/Training/0 Brassica napus/203.json +test/6-seed/Training/0 Brassica napus/203.tiff +test/6-seed/Training/0 Brassica napus/204 (2).json +test/6-seed/Training/0 Brassica napus/204 (2).tiff +test/6-seed/Training/0 Brassica napus/204 (3).json +test/6-seed/Training/0 Brassica napus/204 (3).tiff +test/6-seed/Training/0 Brassica napus/204 (4).json +test/6-seed/Training/0 Brassica napus/204 (4).tiff +test/6-seed/Training/0 Brassica napus/204 (5).json +test/6-seed/Training/0 Brassica napus/204 (5).tiff +test/6-seed/Training/0 Brassica napus/204.json +test/6-seed/Training/0 Brassica napus/204.tiff +test/6-seed/Training/0 Brassica napus/205 (2).json +test/6-seed/Training/0 Brassica napus/205 (2).tiff +test/6-seed/Training/0 Brassica napus/205 (3).json +test/6-seed/Training/0 Brassica napus/205 (3).tiff +test/6-seed/Training/0 Brassica napus/205 (4).json +test/6-seed/Training/0 Brassica napus/205 (4).tiff +test/6-seed/Training/0 Brassica napus/205 (5).json +test/6-seed/Training/0 Brassica napus/205 (5).tiff +test/6-seed/Training/0 Brassica napus/205.json +test/6-seed/Training/0 Brassica napus/205.tiff +test/6-seed/Training/0 Brassica napus/206 (3).json +test/6-seed/Training/0 Brassica napus/206 (3).tiff +test/6-seed/Training/0 Brassica napus/206 (4).json +test/6-seed/Training/0 Brassica napus/206 (4).tiff +test/6-seed/Training/0 Brassica napus/206 (5).json +test/6-seed/Training/0 Brassica napus/206 (5).tiff +test/6-seed/Training/0 Brassica napus/206.json +test/6-seed/Training/0 Brassica napus/206.tiff +test/6-seed/Training/0 Brassica napus/207 (3).json +test/6-seed/Training/0 Brassica napus/207 (3).tiff +test/6-seed/Training/0 Brassica napus/207 (4).json +test/6-seed/Training/0 Brassica napus/207 (4).tiff +test/6-seed/Training/0 Brassica napus/207 (5).json +test/6-seed/Training/0 Brassica napus/207 (5).tiff +test/6-seed/Training/0 Brassica napus/207.json +test/6-seed/Training/0 Brassica napus/207.tiff +test/6-seed/Training/0 Brassica napus/208 (2).json +test/6-seed/Training/0 Brassica napus/208 (2).tiff +test/6-seed/Training/0 Brassica napus/208 (3).json +test/6-seed/Training/0 Brassica napus/208 (3).tiff +test/6-seed/Training/0 Brassica napus/208 (4).json +test/6-seed/Training/0 Brassica napus/208 (4).tiff +test/6-seed/Training/0 Brassica napus/208 (5).json +test/6-seed/Training/0 Brassica napus/208 (5).tiff +test/6-seed/Training/0 Brassica napus/208.json +test/6-seed/Training/0 Brassica napus/208.tiff +test/6-seed/Training/0 Brassica napus/209 (2).json +test/6-seed/Training/0 Brassica napus/209 (2).tiff +test/6-seed/Training/0 Brassica napus/209 (3).json +test/6-seed/Training/0 Brassica napus/209 (3).tiff +test/6-seed/Training/0 Brassica napus/209 (4).json +test/6-seed/Training/0 Brassica napus/209 (4).tiff +test/6-seed/Training/0 Brassica napus/209 (5).json +test/6-seed/Training/0 Brassica napus/209 (5).tiff +test/6-seed/Training/0 Brassica napus/209.json +test/6-seed/Training/0 Brassica napus/209.tiff +test/6-seed/Training/0 Brassica napus/210 (2).json +test/6-seed/Training/0 Brassica napus/210 (2).tiff +test/6-seed/Training/0 Brassica napus/210 (3).json +test/6-seed/Training/0 Brassica napus/210 (3).tiff +test/6-seed/Training/0 Brassica napus/210 (4).json +test/6-seed/Training/0 Brassica napus/210 (4).tiff +test/6-seed/Training/0 Brassica napus/210 (5).json +test/6-seed/Training/0 Brassica napus/210 (5).tiff +test/6-seed/Training/0 Brassica napus/210.json +test/6-seed/Training/0 Brassica napus/210.tiff +test/6-seed/Training/0 Brassica napus/211 (3).json +test/6-seed/Training/0 Brassica napus/211 (3).tiff +test/6-seed/Training/0 Brassica napus/211.json +test/6-seed/Training/0 Brassica napus/211.tiff +test/6-seed/Training/0 Brassica napus/212 (2).json +test/6-seed/Training/0 Brassica napus/212 (2).tiff +test/6-seed/Training/0 Brassica napus/212.json +test/6-seed/Training/0 Brassica napus/212.tiff +test/6-seed/Training/0 Brassica napus/213 (2).json +test/6-seed/Training/0 Brassica napus/213 (2).tiff +test/6-seed/Training/0 Brassica napus/213 (3).json +test/6-seed/Training/0 Brassica napus/213 (3).tiff +test/6-seed/Training/0 Brassica napus/213.json +test/6-seed/Training/0 Brassica napus/213.tiff +test/6-seed/Training/0 Brassica napus/214 (2).json +test/6-seed/Training/0 Brassica napus/214 (2).tiff +test/6-seed/Training/0 Brassica napus/214 (3).json +test/6-seed/Training/0 Brassica napus/214 (3).tiff +test/6-seed/Training/0 Brassica napus/214.json +test/6-seed/Training/0 Brassica napus/214.tiff +test/6-seed/Training/0 Brassica napus/215 (2).json +test/6-seed/Training/0 Brassica napus/215 (2).tiff +test/6-seed/Training/0 Brassica napus/215 (3).json +test/6-seed/Training/0 Brassica napus/215 (3).tiff +test/6-seed/Training/0 Brassica napus/215.json +test/6-seed/Training/0 Brassica napus/215.tiff +test/6-seed/Training/0 Brassica napus/216 (2).json +test/6-seed/Training/0 Brassica napus/216 (2).tiff +test/6-seed/Training/0 Brassica napus/216 (3).json +test/6-seed/Training/0 Brassica napus/216 (3).tiff +test/6-seed/Training/0 Brassica napus/216.json +test/6-seed/Training/0 Brassica napus/216.tiff +test/6-seed/Training/0 Brassica napus/217 (2).json +test/6-seed/Training/0 Brassica napus/217 (2).tiff +test/6-seed/Training/0 Brassica napus/217.json +test/6-seed/Training/0 Brassica napus/217.tiff +test/6-seed/Training/0 Brassica napus/218 (2).json +test/6-seed/Training/0 Brassica napus/218 (2).tiff +test/6-seed/Training/0 Brassica napus/218.json +test/6-seed/Training/0 Brassica napus/218.tiff +test/6-seed/Training/0 Brassica napus/219 (2).json +test/6-seed/Training/0 Brassica napus/219 (2).tiff +test/6-seed/Training/0 Brassica napus/219.json +test/6-seed/Training/0 Brassica napus/219.tiff +test/6-seed/Training/0 Brassica napus/220.json +test/6-seed/Training/0 Brassica napus/220.tiff +test/6-seed/Training/0 Brassica napus/221.json +test/6-seed/Training/0 Brassica napus/221.tiff +test/6-seed/Training/0 Brassica napus/222.json +test/6-seed/Training/0 Brassica napus/222.tiff +test/6-seed/Training/0 Brassica napus/223.json +test/6-seed/Training/0 Brassica napus/223.tiff +test/6-seed/Training/0 Brassica napus/225.json +test/6-seed/Training/0 Brassica napus/225.tiff +test/6-seed/Training/0 Brassica napus/230.json +test/6-seed/Training/0 Brassica napus/230.tiff +test/6-seed/Training/0 Brassica napus/231.json +test/6-seed/Training/0 Brassica napus/231.tiff +test/6-seed/Training/0 Brassica napus/232.json +test/6-seed/Training/0 Brassica napus/232.tiff +test/6-seed/Training/0 Brassica napus/233.json +test/6-seed/Training/0 Brassica napus/233.tiff +test/6-seed/Training/0 Brassica napus/235.json +test/6-seed/Training/0 Brassica napus/235.tiff +test/6-seed/Training/0 Brassica napus/236.json +test/6-seed/Training/0 Brassica napus/236.tiff +test/6-seed/Training/0 Brassica napus/index.json +test/6-seed/Training/1 Brassica junsea/801.json +test/6-seed/Training/1 Brassica junsea/801.tiff +test/6-seed/Training/1 Brassica junsea/802.json +test/6-seed/Training/1 Brassica junsea/802.tiff +test/6-seed/Training/1 Brassica junsea/803.json +test/6-seed/Training/1 Brassica junsea/803.tiff +test/6-seed/Training/1 Brassica junsea/804.json +test/6-seed/Training/1 Brassica junsea/804.tiff +test/6-seed/Training/1 Brassica junsea/805.json +test/6-seed/Training/1 Brassica junsea/805.tiff +test/6-seed/Training/1 Brassica junsea/806.json +test/6-seed/Training/1 Brassica junsea/806.tiff +test/6-seed/Training/1 Brassica junsea/807.json +test/6-seed/Training/1 Brassica junsea/807.tiff +test/6-seed/Training/1 Brassica junsea/808.json +test/6-seed/Training/1 Brassica junsea/808.tiff +test/6-seed/Training/1 Brassica junsea/809.json +test/6-seed/Training/1 Brassica junsea/809.tiff +test/6-seed/Training/1 Brassica junsea/810.json +test/6-seed/Training/1 Brassica junsea/810.tiff +test/6-seed/Training/1 Brassica junsea/811.json +test/6-seed/Training/1 Brassica junsea/811.tiff +test/6-seed/Training/1 Brassica junsea/812.json +test/6-seed/Training/1 Brassica junsea/812.tiff +test/6-seed/Training/1 Brassica junsea/813.json +test/6-seed/Training/1 Brassica junsea/813.tiff +test/6-seed/Training/1 Brassica junsea/814.json +test/6-seed/Training/1 Brassica junsea/814.tiff +test/6-seed/Training/1 Brassica junsea/815.json +test/6-seed/Training/1 Brassica junsea/815.tiff +test/6-seed/Training/1 Brassica junsea/816.json +test/6-seed/Training/1 Brassica junsea/816.tiff +test/6-seed/Training/1 Brassica junsea/817.json +test/6-seed/Training/1 Brassica junsea/817.tiff +test/6-seed/Training/1 Brassica junsea/818.json +test/6-seed/Training/1 Brassica junsea/818.tiff +test/6-seed/Training/1 Brassica junsea/819.json +test/6-seed/Training/1 Brassica junsea/819.tiff +test/6-seed/Training/1 Brassica junsea/820.json +test/6-seed/Training/1 Brassica junsea/820.tiff +test/6-seed/Training/1 Brassica junsea/821.json +test/6-seed/Training/1 Brassica junsea/821.tiff +test/6-seed/Training/1 Brassica junsea/822.json +test/6-seed/Training/1 Brassica junsea/822.tiff +test/6-seed/Training/1 Brassica junsea/823.json +test/6-seed/Training/1 Brassica junsea/823.tiff +test/6-seed/Training/1 Brassica junsea/824.json +test/6-seed/Training/1 Brassica junsea/824.tiff +test/6-seed/Training/1 Brassica junsea/825.json +test/6-seed/Training/1 Brassica junsea/825.tiff +test/6-seed/Training/1 Brassica junsea/826.json +test/6-seed/Training/1 Brassica junsea/826.tiff +test/6-seed/Training/1 Brassica junsea/827.json +test/6-seed/Training/1 Brassica junsea/827.tiff +test/6-seed/Training/1 Brassica junsea/828.json +test/6-seed/Training/1 Brassica junsea/828.tiff +test/6-seed/Training/1 Brassica junsea/829.json +test/6-seed/Training/1 Brassica junsea/829.tiff +test/6-seed/Training/1 Brassica junsea/830.json +test/6-seed/Training/1 Brassica junsea/830.tiff +test/6-seed/Training/1 Brassica junsea/831.json +test/6-seed/Training/1 Brassica junsea/831.tiff +test/6-seed/Training/1 Brassica junsea/832.json +test/6-seed/Training/1 Brassica junsea/832.tiff +test/6-seed/Training/1 Brassica junsea/833.json +test/6-seed/Training/1 Brassica junsea/833.tiff +test/6-seed/Training/1 Brassica junsea/834.json +test/6-seed/Training/1 Brassica junsea/834.tiff +test/6-seed/Training/1 Brassica junsea/835.json +test/6-seed/Training/1 Brassica junsea/835.tiff +test/6-seed/Training/1 Brassica junsea/836.json +test/6-seed/Training/1 Brassica junsea/836.tiff +test/6-seed/Training/1 Brassica junsea/837.json +test/6-seed/Training/1 Brassica junsea/837.tiff +test/6-seed/Training/1 Brassica junsea/838.json +test/6-seed/Training/1 Brassica junsea/838.tiff +test/6-seed/Training/1 Brassica junsea/839.json +test/6-seed/Training/1 Brassica junsea/839.tiff +test/6-seed/Training/1 Brassica junsea/840.json +test/6-seed/Training/1 Brassica junsea/840.tiff +test/6-seed/Training/1 Brassica junsea/841.json +test/6-seed/Training/1 Brassica junsea/841.tiff +test/6-seed/Training/1 Brassica junsea/842.json +test/6-seed/Training/1 Brassica junsea/842.tiff +test/6-seed/Training/1 Brassica junsea/843.json +test/6-seed/Training/1 Brassica junsea/843.tiff +test/6-seed/Training/1 Brassica junsea/844.json +test/6-seed/Training/1 Brassica junsea/844.tiff +test/6-seed/Training/1 Brassica junsea/845.json +test/6-seed/Training/1 Brassica junsea/845.tiff +test/6-seed/Training/1 Brassica junsea/846.json +test/6-seed/Training/1 Brassica junsea/846.tiff +test/6-seed/Training/1 Brassica junsea/847.json +test/6-seed/Training/1 Brassica junsea/847.tiff +test/6-seed/Training/1 Brassica junsea/848.json +test/6-seed/Training/1 Brassica junsea/848.tiff +test/6-seed/Training/1 Brassica junsea/849.json +test/6-seed/Training/1 Brassica junsea/849.tiff +test/6-seed/Training/1 Brassica junsea/850.json +test/6-seed/Training/1 Brassica junsea/850.tiff +test/6-seed/Training/1 Brassica junsea/index.json +test/6-seed/Training/10 Solanum nigrum/1.tiff +test/6-seed/Training/10 Solanum nigrum/2.tiff +test/6-seed/Training/10 Solanum nigrum/3.tiff +test/6-seed/Training/10 Solanum nigrum/4.tiff +test/6-seed/Training/10 Solanum nigrum/5.tiff +test/6-seed/Training/10 Solanum nigrum/6.tiff +test/6-seed/Training/10 Solanum nigrum/7.tiff +test/6-seed/Training/10 Solanum nigrum/8.tiff +test/6-seed/Training/10 Solanum nigrum/9.tiff +test/6-seed/Training/10 Solanum nigrum/10.tiff +test/6-seed/Training/10 Solanum nigrum/11.tiff +test/6-seed/Training/10 Solanum nigrum/12.tiff +test/6-seed/Training/10 Solanum nigrum/13.tiff +test/6-seed/Training/10 Solanum nigrum/14.tiff +test/6-seed/Training/10 Solanum nigrum/15.tiff +test/6-seed/Training/10 Solanum nigrum/16.tiff +test/6-seed/Training/10 Solanum nigrum/17.tiff +test/6-seed/Training/11 Solanum rostratum/1.tiff +test/6-seed/Training/11 Solanum rostratum/2.tiff +test/6-seed/Training/11 Solanum rostratum/3.tiff +test/6-seed/Training/11 Solanum rostratum/4.tiff +test/6-seed/Training/11 Solanum rostratum/5.tiff +test/6-seed/Training/11 Solanum rostratum/6.tiff +test/6-seed/Training/11 Solanum rostratum/7.tiff +test/6-seed/Training/11 Solanum rostratum/8.tiff +test/6-seed/Training/11 Solanum rostratum/9.tiff +test/6-seed/Training/11 Solanum rostratum/10.tiff +test/6-seed/Training/11 Solanum rostratum/11.tiff +test/6-seed/Training/11 Solanum rostratum/12.tiff +test/6-seed/Training/11 Solanum rostratum/13.tiff +test/6-seed/Training/11 Solanum rostratum/14.tiff +test/6-seed/Training/11 Solanum rostratum/15.tiff +test/6-seed/Training/11 Solanum rostratum/16.tiff +test/6-seed/Training/11 Solanum rostratum/17.tiff +test/6-seed/Training/12 Ambrosia artemisiifolia/1.tiff +test/6-seed/Training/12 Ambrosia artemisiifolia/2.tiff +test/6-seed/Training/12 Ambrosia artemisiifolia/3.tiff +test/6-seed/Training/12 Ambrosia artemisiifolia/4.tiff +test/6-seed/Training/12 Ambrosia artemisiifolia/5.tiff +test/6-seed/Training/12 Ambrosia artemisiifolia/6.tiff +test/6-seed/Training/12 Ambrosia artemisiifolia/7.tiff +test/6-seed/Training/12 Ambrosia artemisiifolia/8.tiff +test/6-seed/Training/12 Ambrosia artemisiifolia/9.tiff +test/6-seed/Training/12 Ambrosia artemisiifolia/10.tiff +test/6-seed/Training/12 Ambrosia artemisiifolia/11.tiff +test/6-seed/Training/12 Ambrosia artemisiifolia/12.tiff +test/6-seed/Training/12 Ambrosia artemisiifolia/13.tiff +test/6-seed/Training/12 Ambrosia artemisiifolia/14.tiff +test/6-seed/Training/12 Ambrosia artemisiifolia/15.tiff +test/6-seed/Training/12 Ambrosia artemisiifolia/16.tiff +test/6-seed/Training/12 Ambrosia artemisiifolia/17.tiff +test/6-seed/Training/13 Ambrosia trifida/1.tiff +test/6-seed/Training/13 Ambrosia trifida/2.tiff +test/6-seed/Training/13 Ambrosia trifida/3.tiff +test/6-seed/Training/13 Ambrosia trifida/4.tiff +test/6-seed/Training/13 Ambrosia trifida/5.tiff +test/6-seed/Training/13 Ambrosia trifida/6.tiff +test/6-seed/Training/13 Ambrosia trifida/7.tiff +test/6-seed/Training/13 Ambrosia trifida/8.tiff +test/6-seed/Training/13 Ambrosia trifida/9.tiff +test/6-seed/Training/13 Ambrosia trifida/10.tiff +test/6-seed/Training/13 Ambrosia trifida/11.tiff +test/6-seed/Training/13 Ambrosia trifida/12.tiff +test/6-seed/Training/13 Ambrosia trifida/13.tiff +test/6-seed/Training/13 Ambrosia trifida/14.tiff +test/6-seed/Training/13 Ambrosia trifida/15.tiff +test/6-seed/Training/13 Ambrosia trifida/16.tiff +test/6-seed/Training/13 Ambrosia trifida/17.tiff +test/6-seed/Training/14 Ambrosia psilostachya/1.tiff +test/6-seed/Training/14 Ambrosia psilostachya/2.tiff +test/6-seed/Training/14 Ambrosia psilostachya/3.tiff +test/6-seed/Training/14 Ambrosia psilostachya/4.tiff +test/6-seed/Training/14 Ambrosia psilostachya/5.tiff +test/6-seed/Training/14 Ambrosia psilostachya/6.tiff +test/6-seed/Training/14 Ambrosia psilostachya/7.tiff +test/6-seed/Training/14 Ambrosia psilostachya/8.tiff +test/6-seed/Training/14 Ambrosia psilostachya/9.tiff +test/6-seed/Training/14 Ambrosia psilostachya/10.tiff +test/6-seed/Training/14 Ambrosia psilostachya/11.tiff +test/6-seed/Training/14 Ambrosia psilostachya/12.tiff +test/6-seed/Training/14 Ambrosia psilostachya/13.tiff +test/6-seed/Training/14 Ambrosia psilostachya/14.tiff +test/6-seed/Training/14 Ambrosia psilostachya/15.tiff +test/6-seed/Training/14 Ambrosia psilostachya/16.tiff +test/6-seed/Training/14 Ambrosia psilostachya/17.tiff +test/6-seed/Training/2 Cirsium arvense/1.json +test/6-seed/Training/2 Cirsium arvense/1.tiff +test/6-seed/Training/2 Cirsium arvense/2.json +test/6-seed/Training/2 Cirsium arvense/2.tiff +test/6-seed/Training/2 Cirsium arvense/3.json +test/6-seed/Training/2 Cirsium arvense/3.tiff +test/6-seed/Training/2 Cirsium arvense/4.json +test/6-seed/Training/2 Cirsium arvense/4.tiff +test/6-seed/Training/2 Cirsium arvense/5.json +test/6-seed/Training/2 Cirsium arvense/5.tiff +test/6-seed/Training/2 Cirsium arvense/6.json +test/6-seed/Training/2 Cirsium arvense/6.tiff +test/6-seed/Training/2 Cirsium arvense/7.json +test/6-seed/Training/2 Cirsium arvense/7.tiff +test/6-seed/Training/2 Cirsium arvense/8.json +test/6-seed/Training/2 Cirsium arvense/8.tiff +test/6-seed/Training/2 Cirsium arvense/9.json +test/6-seed/Training/2 Cirsium arvense/9.tiff +test/6-seed/Training/2 Cirsium arvense/10.json +test/6-seed/Training/2 Cirsium arvense/10.tiff +test/6-seed/Training/2 Cirsium arvense/11.json +test/6-seed/Training/2 Cirsium arvense/11.tiff +test/6-seed/Training/2 Cirsium arvense/12.json +test/6-seed/Training/2 Cirsium arvense/12.tiff +test/6-seed/Training/2 Cirsium arvense/13.json +test/6-seed/Training/2 Cirsium arvense/13.tiff +test/6-seed/Training/2 Cirsium arvense/14.json +test/6-seed/Training/2 Cirsium arvense/14.tiff +test/6-seed/Training/2 Cirsium arvense/15.json +test/6-seed/Training/2 Cirsium arvense/15.tiff +test/6-seed/Training/2 Cirsium arvense/16.json +test/6-seed/Training/2 Cirsium arvense/16.tiff +test/6-seed/Training/2 Cirsium arvense/17.json +test/6-seed/Training/2 Cirsium arvense/17.tiff +test/6-seed/Training/2 Cirsium arvense/index.json +test/6-seed/Training/3 Cirsium vulgare/1.tiff +test/6-seed/Training/3 Cirsium vulgare/2.tiff +test/6-seed/Training/3 Cirsium vulgare/3.tiff +test/6-seed/Training/3 Cirsium vulgare/4.tiff +test/6-seed/Training/3 Cirsium vulgare/5.tiff +test/6-seed/Training/3 Cirsium vulgare/6.tiff +test/6-seed/Training/3 Cirsium vulgare/7.tiff +test/6-seed/Training/3 Cirsium vulgare/8.tiff +test/6-seed/Training/3 Cirsium vulgare/9.tiff +test/6-seed/Training/3 Cirsium vulgare/10.tiff +test/6-seed/Training/3 Cirsium vulgare/11.tiff +test/6-seed/Training/3 Cirsium vulgare/12.tiff +test/6-seed/Training/3 Cirsium vulgare/13.tiff +test/6-seed/Training/3 Cirsium vulgare/14.tiff +test/6-seed/Training/3 Cirsium vulgare/15.tiff +test/6-seed/Training/3 Cirsium vulgare/16.tiff +test/6-seed/Training/3 Cirsium vulgare/17.tiff +test/6-seed/Training/4 Carduus nutans/1.tiff +test/6-seed/Training/4 Carduus nutans/2.tiff +test/6-seed/Training/4 Carduus nutans/3.tiff +test/6-seed/Training/4 Carduus nutans/4.tiff +test/6-seed/Training/4 Carduus nutans/5.tiff +test/6-seed/Training/4 Carduus nutans/6.tiff +test/6-seed/Training/4 Carduus nutans/7.tiff +test/6-seed/Training/4 Carduus nutans/8.tiff +test/6-seed/Training/4 Carduus nutans/9.tiff +test/6-seed/Training/4 Carduus nutans/10.tiff +test/6-seed/Training/4 Carduus nutans/11.tiff +test/6-seed/Training/4 Carduus nutans/12.tiff +test/6-seed/Training/4 Carduus nutans/13.tiff +test/6-seed/Training/4 Carduus nutans/14.tiff +test/6-seed/Training/4 Carduus nutans/15.tiff +test/6-seed/Training/4 Carduus nutans/16.tiff +test/6-seed/Training/4 Carduus nutans/17.tiff +test/6-seed/Training/5 Bromus secalinus/601.tiff +test/6-seed/Training/5 Bromus secalinus/602.tiff +test/6-seed/Training/5 Bromus secalinus/603.tiff +test/6-seed/Training/5 Bromus secalinus/604.tiff +test/6-seed/Training/5 Bromus secalinus/605.tiff +test/6-seed/Training/5 Bromus secalinus/606.tiff +test/6-seed/Training/5 Bromus secalinus/607.tiff +test/6-seed/Training/5 Bromus secalinus/608.tiff +test/6-seed/Training/5 Bromus secalinus/609.tiff +test/6-seed/Training/5 Bromus secalinus/610.tiff +test/6-seed/Training/5 Bromus secalinus/612.tiff +test/6-seed/Training/5 Bromus secalinus/613.tiff +test/6-seed/Training/5 Bromus secalinus/614.tiff +test/6-seed/Training/5 Bromus secalinus/616.tiff +test/6-seed/Training/5 Bromus secalinus/617.tiff +test/6-seed/Training/5 Bromus secalinus/618.tiff +test/6-seed/Training/5 Bromus secalinus/619.tiff +test/6-seed/Training/5 Bromus secalinus/621.tiff +test/6-seed/Training/5 Bromus secalinus/622.tiff +test/6-seed/Training/5 Bromus secalinus/623.tiff +test/6-seed/Training/5 Bromus secalinus/624.tiff +test/6-seed/Training/5 Bromus secalinus/625.tiff +test/6-seed/Training/5 Bromus secalinus/627.tiff +test/6-seed/Training/5 Bromus secalinus/629.tiff +test/6-seed/Training/5 Bromus secalinus/Bromus secalinus 2.tiff +test/6-seed/Training/5 Bromus secalinus/Bromus secalinus.tiff +test/6-seed/Training/6 Bromus hordeaceus/002.tiff +test/6-seed/Training/6 Bromus hordeaceus/003.tiff +test/6-seed/Training/6 Bromus hordeaceus/004.tiff +test/6-seed/Training/6 Bromus hordeaceus/005.tiff +test/6-seed/Training/6 Bromus hordeaceus/006.tiff +test/6-seed/Training/6 Bromus hordeaceus/007.tiff +test/6-seed/Training/6 Bromus hordeaceus/008.tiff +test/6-seed/Training/6 Bromus hordeaceus/009.tiff +test/6-seed/Training/6 Bromus hordeaceus/010.tiff +test/6-seed/Training/6 Bromus hordeaceus/012.tiff +test/6-seed/Training/6 Bromus hordeaceus/013.tiff +test/6-seed/Training/6 Bromus hordeaceus/014.tiff +test/6-seed/Training/6 Bromus hordeaceus/015.tiff +test/6-seed/Training/6 Bromus hordeaceus/016.tiff +test/6-seed/Training/6 Bromus hordeaceus/017.tiff +test/6-seed/Training/6 Bromus hordeaceus/018.tiff +test/6-seed/Training/6 Bromus hordeaceus/021.tiff +test/6-seed/Training/6 Bromus hordeaceus/022.tiff +test/6-seed/Training/6 Bromus hordeaceus/023.tiff +test/6-seed/Training/6 Bromus hordeaceus/024.tiff +test/6-seed/Training/6 Bromus hordeaceus/025.tiff +test/6-seed/Training/6 Bromus hordeaceus/026.tiff +test/6-seed/Training/6 Bromus hordeaceus/027.tiff +test/6-seed/Training/6 Bromus hordeaceus/028.tiff +test/6-seed/Training/6 Bromus hordeaceus/029.tiff +test/6-seed/Training/6 Bromus hordeaceus/030.tiff +test/6-seed/Training/6 Bromus hordeaceus/031.tiff +test/6-seed/Training/6 Bromus hordeaceus/032.tiff +test/6-seed/Training/6 Bromus hordeaceus/033.tiff +test/6-seed/Training/6 Bromus hordeaceus/034.tiff +test/6-seed/Training/6 Bromus hordeaceus/035.tiff +test/6-seed/Training/6 Bromus hordeaceus/036.tiff +test/6-seed/Training/6 Bromus hordeaceus/Bromus hordeaceus (2).tiff +test/6-seed/Training/6 Bromus hordeaceus/Bromus hordeaceus.tiff +test/6-seed/Training/7 Bromus japonicus/602.tiff +test/6-seed/Training/7 Bromus japonicus/603.tiff +test/6-seed/Training/7 Bromus japonicus/604.tiff +test/6-seed/Training/7 Bromus japonicus/605.tiff +test/6-seed/Training/7 Bromus japonicus/606.tiff +test/6-seed/Training/7 Bromus japonicus/607.tiff +test/6-seed/Training/7 Bromus japonicus/608.tiff +test/6-seed/Training/7 Bromus japonicus/609.tiff +test/6-seed/Training/7 Bromus japonicus/610.tiff +test/6-seed/Training/7 Bromus japonicus/611.tiff +test/6-seed/Training/7 Bromus japonicus/612.tiff +test/6-seed/Training/7 Bromus japonicus/613.tiff +test/6-seed/Training/7 Bromus japonicus/614.tiff +test/6-seed/Training/7 Bromus japonicus/615.tiff +test/6-seed/Training/7 Bromus japonicus/616.tiff +test/6-seed/Training/7 Bromus japonicus/617.tiff +test/6-seed/Training/7 Bromus japonicus/618.tiff +test/6-seed/Training/7 Bromus japonicus/621.tiff +test/6-seed/Training/7 Bromus japonicus/622.tiff +test/6-seed/Training/7 Bromus japonicus/623.tiff +test/6-seed/Training/7 Bromus japonicus/624.tiff +test/6-seed/Training/7 Bromus japonicus/625.tiff +test/6-seed/Training/7 Bromus japonicus/627.tiff +test/6-seed/Training/7 Bromus japonicus/628.tiff +test/6-seed/Training/7 Bromus japonicus/629.tiff +test/6-seed/Training/7 Bromus japonicus/632.tiff +test/6-seed/Training/7 Bromus japonicus/633.tiff +test/6-seed/Training/7 Bromus japonicus/634.tiff +test/6-seed/Training/7 Bromus japonicus/635.tiff +test/6-seed/Training/7 Bromus japonicus/636.tiff +test/6-seed/Training/7 Bromus japonicus/637.tiff +test/6-seed/Training/7 Bromus japonicus/638.tiff +test/6-seed/Training/7 Bromus japonicus/639.tiff +test/6-seed/Training/7 Bromus japonicus/Bromus japonicus (2).tiff +test/6-seed/Training/7 Bromus japonicus/Bromus japonicus.tiff +test/6-seed/Training/8 Lolium temulentum/2.1.tiff +test/6-seed/Training/8 Lolium temulentum/2.2.tiff +test/6-seed/Training/8 Lolium temulentum/3.1.tiff +test/6-seed/Training/8 Lolium temulentum/3.2.tiff +test/6-seed/Training/8 Lolium temulentum/4.1.tiff +test/6-seed/Training/8 Lolium temulentum/4.2.tiff +test/6-seed/Training/8 Lolium temulentum/5.1.tiff +test/6-seed/Training/8 Lolium temulentum/5.2.tiff +test/6-seed/Training/8 Lolium temulentum/6.1.tiff +test/6-seed/Training/8 Lolium temulentum/6.2.tiff +test/6-seed/Training/8 Lolium temulentum/7.1.tiff +test/6-seed/Training/8 Lolium temulentum/7.2.tiff +test/6-seed/Training/8 Lolium temulentum/8.1.tiff +test/6-seed/Training/8 Lolium temulentum/8.tiff +test/6-seed/Training/8 Lolium temulentum/9.1.tiff +test/6-seed/Training/8 Lolium temulentum/9.2.tiff +test/6-seed/Training/8 Lolium temulentum/10.1.tiff +test/6-seed/Training/8 Lolium temulentum/10.2.tiff +test/6-seed/Training/8 Lolium temulentum/11.1.tiff +test/6-seed/Training/8 Lolium temulentum/11.2.tiff +test/6-seed/Training/8 Lolium temulentum/011.tiff +test/6-seed/Training/8 Lolium temulentum/12.1.tiff +test/6-seed/Training/8 Lolium temulentum/12.2.tiff +test/6-seed/Training/8 Lolium temulentum/012.tiff +test/6-seed/Training/8 Lolium temulentum/13.1.tiff +test/6-seed/Training/8 Lolium temulentum/13.2.tiff +test/6-seed/Training/8 Lolium temulentum/013.tiff +test/6-seed/Training/8 Lolium temulentum/14.1.tiff +test/6-seed/Training/8 Lolium temulentum/14.2.tiff +test/6-seed/Training/8 Lolium temulentum/014.tiff +test/6-seed/Training/8 Lolium temulentum/15.1.tiff +test/6-seed/Training/8 Lolium temulentum/15.2.tiff +test/6-seed/Training/8 Lolium temulentum/015.tiff +test/6-seed/Training/8 Lolium temulentum/16.1.tiff +test/6-seed/Training/8 Lolium temulentum/16.2.tiff +test/6-seed/Training/8 Lolium temulentum/016.tiff +test/6-seed/Training/8 Lolium temulentum/17.1.tiff +test/6-seed/Training/8 Lolium temulentum/17.2.tiff +test/6-seed/Training/8 Lolium temulentum/017.tiff +test/6-seed/Training/8 Lolium temulentum/021.tiff +test/6-seed/Training/8 Lolium temulentum/022.tiff +test/6-seed/Training/8 Lolium temulentum/023.tiff +test/6-seed/Training/8 Lolium temulentum/024.tiff +test/6-seed/Training/8 Lolium temulentum/025.tiff +test/6-seed/Training/8 Lolium temulentum/026.tiff +test/6-seed/Training/8 Lolium temulentum/027.tiff +test/6-seed/Training/8 Lolium temulentum/028.tiff +test/6-seed/Training/8 Lolium temulentum/029.tiff +test/6-seed/Training/8 Lolium temulentum/030.tiff +test/6-seed/Training/8 Lolium temulentum/031.tiff +test/6-seed/Training/8 Lolium temulentum/032.tiff +test/6-seed/Training/8 Lolium temulentum/033.tiff +test/6-seed/Training/8 Lolium temulentum/034.tiff +test/6-seed/Training/8 Lolium temulentum/035.tiff +test/6-seed/Training/8 Lolium temulentum/036.tiff +test/6-seed/Training/8 Lolium temulentum/037.tiff +test/6-seed/Training/8 Lolium temulentum/Lolium temulentum.1.tiff +test/6-seed/Training/8 Lolium temulentum/Lolium temulentum.2.tiff +test/6-seed/Training/9 Solanum carolinense/1.tiff +test/6-seed/Training/9 Solanum carolinense/2.tiff +test/6-seed/Training/9 Solanum carolinense/3.tiff +test/6-seed/Training/9 Solanum carolinense/4.tiff +test/6-seed/Training/9 Solanum carolinense/5.tiff +test/6-seed/Training/9 Solanum carolinense/6.tiff +test/6-seed/Training/9 Solanum carolinense/7.tiff +test/6-seed/Training/9 Solanum carolinense/8.tiff +test/6-seed/Training/9 Solanum carolinense/9.tiff +test/6-seed/Training/9 Solanum carolinense/10.tiff +test/6-seed/Training/9 Solanum carolinense/11.tiff +test/6-seed/Training/9 Solanum carolinense/12.tiff +test/6-seed/Training/9 Solanum carolinense/13.tiff +test/6-seed/Training/9 Solanum carolinense/14.tiff +test/6-seed/Training/9 Solanum carolinense/15.tiff +test/6-seed/Training/9 Solanum carolinense/16.tiff +test/6-seed/Training/9 Solanum carolinense/17.tiff diff --git a/db/queries/queries.py b/db/queries/queries.py new file mode 100644 index 00000000..38e6de9f --- /dev/null +++ b/db/queries/queries.py @@ -0,0 +1,41 @@ +import psycopg +import os + +NACHET_DB_URL = os.getenv("NACHET_DB_URL") + +NACHET_SCHEMA = os.getenv("NACHET_SCHEMA") + +def createConnection(): + return psycopg.connect(NACHET_DB_URL) + +def createCursor(conn): + return conn.cursor() + +def createSearchPath(conn,cur): + cur.execute(f"""SET search_path TO "{NACHET_SCHEMA}";""") + conn.commit() + +def closeConnection(conn): + conn.close() + +def closeCursor(cur): + cur.close() + +def endQuery(conn,cursor): + conn.commit() + closeCursor(cursor) + closeConnection(conn) + +def queryDB(conn,cur,query): + cur.execute(query) + conn.commit() + return cur + +def queryParameterizedDB(conn,cur,query, params): + cur.execute(query, params) + conn.commit() + return cur + +def printResults(cur): + for record in cur: + print(record) \ No newline at end of file diff --git a/nachetDb-1.0.0/db-creation.py b/db/setup/db-creation.py similarity index 58% rename from nachetDb-1.0.0/db-creation.py rename to db/setup/db-creation.py index 670c3bd4..83b7cdf4 100644 --- a/nachetDb-1.0.0/db-creation.py +++ b/db/setup/db-creation.py @@ -1,14 +1,15 @@ -import psycopg -import os +import db.queries.queries as queries -print(os.getenv("NACHET_DB_URL")) # Connect to your PostgreSQL database with the DB URL -conn = psycopg.connect(os.getenv("NACHET_DB_URL")) +conn = queries.createConnection() # Create a cursor object -cur = conn.cursor() +cur = queries.createCursor(conn) # # Create Schema -# cur.execute("CREATE SCHEMA \"%s\"") % ("nachetdb_0.0.1")) +# cur.execute("""CREATE SCHEMA "nachetdb_0.0.1";""") + +# # Create Search Path +# cur.execute("""SET search_path TO "nachetdb_0.0.1";""") # #Create Users table # cur.execute(""" @@ -36,14 +37,27 @@ # ) # """ % ("nachetdb_0.0.1")) +# Create seed DB +cur.execute(""" + CREATE TABLE \"%s\".seeds ( + id uuid PRIMARY KEY, + info JSON, + name VARCHAR(255) + ) +""" % ("nachetdb_0.0.1")) + # # check if the schema exists -# cur.execute(""" -# SELECT EXISTS ( -# SELECT 1 -# FROM information_schema.schemata -# WHERE schema_name = 'nachetdb_0.0.1' -# ) -# """) +cur.execute(""" + SELECT EXISTS ( + SELECT * + FROM information_schema.schemata + WHERE schema_name = 'nachetdb_0.0.1' + ) +""") + +# # check if the search path exists +# cur.execute("Show search_path") + ## Commit the transaction conn.commit() diff --git a/db/setup/db-mock-seeds-population.py b/db/setup/db-mock-seeds-population.py new file mode 100644 index 00000000..ad481c62 --- /dev/null +++ b/db/setup/db-mock-seeds-population.py @@ -0,0 +1,66 @@ +import uuid +import db.queries.queries as queries + + +# Connect to your PostgreSQL database with the DB URL +conn = queries.createConnection() +# Create a cursor object +cur = queries.createCursor(conn) + +brassicaNapus = (str(uuid.uuid4()), "Brassica napus") +brassicaJunsea = (str(uuid.uuid4()), "Brassica juncea") +cirsiumArvense = (str(uuid.uuid4()), "Cirsium arvense") +cirsiumVulgare = (str(uuid.uuid4()), "Cirsium vulgare") +carduusNutans = (str(uuid.uuid4()), "Carduus nutans") +bromusSecalinus = (str(uuid.uuid4()), "Bromus secalinus") +bromusHordeaceus = (str(uuid.uuid4()), "Bromus hordeaceus") +bromusJaponicus = (str(uuid.uuid4()), "Bromus japonicus") +loliumTemulentum = (str(uuid.uuid4()), "Lolium temulentum") +solanumCarolinense = (str(uuid.uuid4()), "Solanum carolinense") +solanumNigrum = (str(uuid.uuid4()), "Solanum nigrum") +solanumRostratum = (str(uuid.uuid4()), "Solanum rostratum") +ambrosiaArtemisiifolia = (str(uuid.uuid4()), "Ambrosia artemisiifolia") +ambrosiaTrifida = (str(uuid.uuid4()), "Ambrosia trifida") +Ambrosiapsilostachya = (str(uuid.uuid4()), "Ambrosia psilostachya") + +queries.createSearchPath(conn, cur) + +# Query to insert a seed +query = "INSERT INTO seeds (id,name) VALUES (%s,%s),(%s,%s),(%s,%s),(%s,%s),(%s,%s),(%s,%s),(%s,%s),(%s,%s),(%s,%s),(%s,%s),(%s,%s),(%s,%s),(%s,%s),(%s,%s),(%s,%s)" +data = ( + brassicaNapus[0], + brassicaNapus[1], + brassicaJunsea[0], + brassicaJunsea[1], + cirsiumArvense[0], + cirsiumArvense[1], + cirsiumVulgare[0], + cirsiumVulgare[1], + carduusNutans[0], + carduusNutans[1], + bromusSecalinus[0], + bromusSecalinus[1], + bromusHordeaceus[0], + bromusHordeaceus[1], + bromusJaponicus[0], + bromusJaponicus[1], + loliumTemulentum[0], + loliumTemulentum[1], + solanumCarolinense[0], + solanumCarolinense[1], + solanumNigrum[0], + solanumNigrum[1], + solanumRostratum[0], + solanumRostratum[1], + ambrosiaArtemisiifolia[0], + ambrosiaArtemisiifolia[1], + ambrosiaTrifida[0], + ambrosiaTrifida[1], + Ambrosiapsilostachya[0], + Ambrosiapsilostachya[1], +) + +# queries.queryParameterizedDB(conn,cur,query,data) + +queries.queryDB(conn, cur, "select id,name from seeds") +queries.printResults(cur) diff --git a/db/setup/deployment-mass-import.py b/db/setup/deployment-mass-import.py new file mode 100644 index 00000000..1647375d --- /dev/null +++ b/db/setup/deployment-mass-import.py @@ -0,0 +1,528 @@ +from datetime import date +import uuid +import json +import os +import psycopg +from PIL import Image +import db.queries.queries as queries +import validator as validator + +# File: nachetDb-1.0.0/mass-import.py +# This script is used to import the missing metadata from an Azure container to the database + + +# Constants +container_URL = "" +seedID = "" +path = "" + + +def jsonDeletion(picturefolder): + """ + Function to delete all the .json files in the specified folder in case of a bad importation. + + Parameters: + - picturefolder (str): The path to the folder. + """ + # Get a list of files in the directory + files = [ + f + for f in os.listdir(picturefolder) + if os.path.isfile(os.path.join(picturefolder, f)) + ] + # Iterate over the list of filepaths & remove each file. + for file in files: + try: + os.remove(file) + except OSError as e: + print("Error: %s : %s" % (file, e.strerror)) + + +def manualMetaDataImport(picturefolder: str): + """ + Template function to do the importation process of the metadata from the Azure container to the database. + The user is prompted to input the client email, the zoom level and the number of seeds for the index. + The container needs to be downloaded locally before running this function. + + Parameters: + - picturefolder (str): Relative path to the folder we want to import. + """ + + # Manually define the picture folder + # picturefolder = "" # manually inputted before each import sequence + + # Input the client email to identify the user or register him if needed + # clientEmail = input("clientEmail: ") + clientEmail = "test@dev" + userID = getUserID(clientEmail) + + # #build index + nbPic = buildIndex(picturefolder, userID) + + # upload index to database + indexID = uploadIndexDB(f"{picturefolder}/index.json", userID=userID) + # indexID=1 + print("indexID : " + str(indexID)) + + # for each picture in field + # zoomlevel = input("Zoom level for this index: ") + zoomlevel = 0 + seedNumber = input("Number of seed for this index: ") + + # Get a list of files in the directory + files = [ + f + for f in os.listdir(picturefolder) + if os.path.isfile(os.path.join(picturefolder, f)) + ] + i = 0 + # Loop through each file in the folder + for filename in files: + if filename.endswith(".tiff") or filename.endswith(".tif"): + buildPicture( + picturefolder, seedNumber, zoomlevel, indexID, filename, userID + ) + picPath = f'{picturefolder}/{filename.removesuffix(".tiff")}.json' + # upload picture to database + uploadPictureDB(picPath, userID, indexID) + i = i + 1 + + if i != nbPic: + print("Error: number of pictures processed does not match the index") + print("Number of picture processed: " + str(i)) + print("Number of picture in index: " + str(nbPic)) + else: + print("importation of " + picturefolder + "/ complete") + print("Number of picture processed: " + str(i)) + + +# Function to retrieve the userID from the database based on the email +# Returns the userID if the email is already registered, else it registers the email and returns the userID +def getUserID(email: str): + """ + Function to retrieve the userID from the database based on the email. + If the email is not already registered, it registers the email and returns the userID. + + Parameters: + - email (str): The email of the user. + + Returns: + - The userID of the user. + """ + # Connect to your PostgreSQL database + conn = psycopg.connect(os.getenv("NACHET_DB_URL")) + # Create a cursor object + cur = conn.cursor() + queries.createSearchPath(conn, cur) + # Check if the email is already registered + cur.execute(f"SELECT Exists(SELECT 1 FROM users WHERE email='{email}')") + if cur.fetchone()[0]: # Already registered + cur.execute(f"SELECT id FROM users WHERE email='{email}'") + res = cur.fetchone()[0] + else: # Not registered -> Creates new user + cur.execute(f"INSERT INTO users (id,email) VALUES (,'{email}')") + cur.execute(f"SELECT id FROM users WHERE email='{email}'") + res = cur.fetchone()[0] + cur.close() + conn.close() + return res + + +# Function to build the index metadata file +def buildIndex(output: str, clientID): + """ + This function builds the index needed to represent each folder (with pictures in it) of a container. + It prompts the user to input the expertise of the client and the number of images in the folder. + + Parameters: + - output (str): The path to the folder. + - clientID (str): The UUID of the client. + + Returns: + - The number of pictures in the folder that are supposed to be processed. + """ + + # clientEmail = input("clientEmail: ") + clientEmail = "test@email" + + # clientExpertise = input("Expertise: ") + clientExpertise = "Developer" + + # Create the index metadata (sub part) normally filled by the user + clientData = validator.ClientData(clientEmail=clientEmail, clientExpertise=clientExpertise) + print(output) + nb = len([f for f in os.listdir(output) if os.path.isfile(os.path.join(output, f))]) + imageData = validator.ImageDataindex(numberOfImages=nb) + + # ATM===> I dont have access to the seedID db + # seedID = input("SeedID: ") + + # family = input("Family: ") + # genus = input("Genus: ") + # species = input("Species: ") + family = "" + genus = "" + species = "" + seedData = validator.SeedData( + seedID=seedID, seedFamily=family, seedGenus=genus, seedSpecies=species + ) + + # Create the Index object + index = validator.Index(clientData=clientData, imageData=imageData, seedData=seedData) + + print("File created, name: " + output + "/index.json") + indexJson = index.dict() + + # Creating index metadata collected from the system + sysData = IndexProcessing(openIndexSystem()) + + # Append the system data to the index + indexJson.update(sysData) + + # Create the index file + createJsonIndex(index=indexJson, name="index", output=output) + return nb + + +def buildPicture(output: str, number: int, zoom, indexID: int, name: str, userID: str): + """ + This function builds the picture metadata file (.json) for each picture in the folder. + + Parameters: + - output (str): The path to the folder. + - number (int): The number of seeds in the picture. + - zoom (float): The zoom level of the picture. + - indexID (uuid): The ID of the index. + - name (str): The name of the picture. + - userID (str): The UUID of the user. + + Returns: + None + """ + + desc = "This image was uploaded before the creation of the database and was apart of the first importation batch." + nb = number + userData = validator.UserData(description=desc, numberOfSeeds=nb, zoom=zoom) + + # Create the Picture object with the user data + pic = validator.Picture(userData=userData) + picJson = pic.dict() + print("File created, name: " + name) + picturePath = f"{output}/{name}" + + # Creating picture metadata collected from the system + picData = PictureProcessing(openPictureSystem(), userID, indexID, picturePath) + picJson.update(picData) + createJsonPicture(pic=picJson, name=name, output=output) + + +def IndexProcessing(jsonData: str): + """ + Function to create the system index metadata. + + Parameters: + - jsonData (str): The JSON data of the index. + + Returns: + - The system index object populated with the metadata. + """ + today = date.today() + editedBy = "Francois Werbrouck" # not permanent, will be changed once the mass import is over + editDate = date.today() + change = "" + access = "" + privacy = False + return IndexSystemPopulating( + jsonData, today, editedBy, editDate, change, access, privacy + ) + + +# Function to create the system picture metadata +# Returns the system picture object populated with the metadata +def PictureProcessing(jsonData: str, userID: str, indexID: int, picturePath: str): + """ + Function to create the system picture metadata. + + Parameters: + - jsonData (str): The JSON data of the picture. + - userID (str): The UUID of the user. + - indexID (str): The ID of the index. + - picturePath (str): The path to the picture. + + Returns: + - The system picture object populated with the metadata. + """ + today = date.today() + # userID + # indexID + info = getImageProperties(picturePath) + parent = "" + source = container_URL + picturePath.removesuffix("test") + format = info[2] + height = info[1] + width = info[0] + resolution = "" + return PictureSystemPopulating( + jsonData, + today, + str(userID), + str(indexID), + format, + height, + width, + resolution, + source, + parent, + ) + + +# Function to populate the index metadata file provided +# Returns the index metadata object populated with the metadata +def IndexSystemPopulating( + data, date: date, editedBy, editDate: date, changes: str, access, privacy +): + """ + Function to populate the index metadata file provided. + + Parameters: + - data (str): The JSON data template of the index. + - date (date): The date of the upload + - editedBy (str): The name of the person who edited the file + - editDate (date): The date of the last edit + - changes (str): The changes made to the file + - access (str): The access log + + Returns: + - The index metadata object populated with the metadata. + """ + # print(data) + data["auditTrail"]["uploadDate"] = date.strftime("%Y-%m-%d") + data["auditTrail"]["editedBy"] = editedBy + data["auditTrail"]["editDate"] = editDate.strftime("%Y-%m-%d") + data["auditTrail"]["changeLog"] = changes + data["auditTrail"]["accessLog"] = access + data["auditTrail"]["privacyFlag"] = privacy + return data + + +# Function to populate the picture metadata file provided +# Returns the picture metadata object populated with the metadata +def PictureSystemPopulating( + data, + date: date, + userID: str, + indexID: str, + format: str, + height: int, + width: int, + resolution: str, + source: str, + parent: str, +): + """ + Function to populate the Picture metadata file provided. + + Parameters: + - data (str): The JSON data template of the Picture. + - date (date): The date of the upload + - userID (str): The UUID of the user + - indexID (str): The ID of the index + - format (str): The format of the picture + - height (int): The height of the picture + - width (int): The width of the picture + - resolution (str): The resolution of the picture + - source (str): The source of the picture + - parent (str): The parent of the picture + + Returns: + - The Picture metadata object populated with the metadata. + """ + data["info"]["userID"] = userID + data["info"]["uploadDate"] = date.strftime("%Y-%m-%d") + data["info"]["indexID"] = indexID + data["imageData"]["height"] = height + data["imageData"]["width"] = width + data["imageData"]["format"] = format + data["imageData"]["source"] = source + data["imageData"]["parent"] = parent + data["imageData"]["resolution"] = resolution + + data["qualityCheck"]["imageChecksum"] = "" + data["qualityCheck"]["uploadCheck"] = True + data["qualityCheck"]["validData"] = True + data["qualityCheck"]["errorType"] = "" + data["qualityCheck"]["dataQualityScore"] = 1.0 + + return data + + +def getImageProperties(path: str): + """ + Function to retrieve an image's properties. + + Parameters: + - path (str): The path to the image. + + Returns: + - The image's width, height and format as a tuple. + """ + with Image.open(path) as img: + width, height = img.size + img_format = img.format + return width, height, img_format + + +# Function to open the system index metadata template file +# Returns an empty system index metadata object +def openIndexSystem(): + """ + Function to open the system index metadata template file. + + Returns: + - An empty system index metadata object. + """ + with open("index-template-system.json", "r") as file: + sysData = json.load(file) + # print(sysData) + return sysData + + +# Function to open the system picture metadata template file +# Returns an empty system picture metadata object +def openPictureSystem(): + """ + Function to open the system picture metadata template file. + + Returns: + - An empty system picture metadata object. + """ + with open("picture-template-system.json", "r") as file: + sysData = json.load(file) + # print(sysData) + return sysData + + +# Create .json from Index data model +def createJsonIndex(index, name: str, output: str): + """ + Create .json from index data model to the specified output. + + Parameters: + - index (dict): The index data model. + - name (str): The name of the file. + - output (str): The path to the folder. + + Returns: + None + """ + data = validator.PIndex(**index) + filePath = f"{output}/{name}.json" + with open(filePath, "w") as json_file: + json.dump(index, json_file, indent=2) + + +# Create .json form Picture data model +def createJsonPicture(pic, name: str, output: str): + """ + Create .json from picture data model to the specified output. + + Parameters: + - pic (dict): The picture data model. + - name (str): The name of the file. + - output (str): The path to the folder. + + Returns: + None + """ + data = validator.PPicture(**pic) + extension = name.split(".")[-1] + filename = name.removesuffix("." + extension) + filePath = f"{output}/{filename}.json" + with open(filePath, "w") as json_file: + json.dump(pic, json_file, indent=2) + + +# Function to upload the index file located @path to the database +# RETURNS the indexID of the uploaded index +def uploadIndexDB(path: str, userID: str): + """ + Upload the index.json file located at the specified path to the database. + + Parameters: + - path (str): The path to the index file. + - userID (str): The ID of the user. + + Returns: + - The ID of the index. + """ + + # Connect to your PostgreSQL database + conn = psycopg.connect(os.getenv("NACHET_DB_URL")) + + # Create a cursor object + cur = conn.cursor() + + queries.createSearchPath(conn, cur) + + # Open the file + with open(path, "r") as file: + data = file.read() + + # Build indexID + indexID = uuid.uuid4() + + # Execute the INSERT statement + # print(userID) + cur.execute( + "INSERT INTO indexes (id,index, ownerID) VALUES (%s,%s, %s)", + (indexID, data, userID), + ) + conn.commit() + + # Retrieve the index id + cur.execute("SELECT id FROM indexes ORDER BY id DESC LIMIT 1") + + cur.close() + conn.close() + return indexID + + +def uploadPictureDB(path: str, userID: str, indexID: str): + """ + Uploads the picture file located at the specified path to the database. + + Parameters: + - path (str): The path to the picture file. + - userID (str): The ID of the user. + - indexID (str): The ID of the index. + + Returns: + None + """ + # Connect to your PostgreSQL howatabase + conn = psycopg.connect(os.getenv("NACHET_DB_URL")) + + # Create a cursor object + cur = conn.cursor() + + queries.createSearchPath(conn, cur) + # Open the file + with open(path, "r") as file: + data = file.read() + + # Generate pictureID + pictureID = uuid.uuid4() + + # Execute the INSERT statement + cur.execute( + "INSERT INTO pictures (id,picture, indexID) VALUES (%s,%s, %s)", + (pictureID, data, indexID), + ) + conn.commit() + + cur.close() + conn.close() + + +if __name__ == "__main__": + manualMetaDataImport(path) diff --git a/nachetDb-1.0.0/storage-download.py b/db/setup/storage-download.py similarity index 74% rename from nachetDb-1.0.0/storage-download.py rename to db/setup/storage-download.py index e04d1dfd..2ed4c374 100644 --- a/nachetDb-1.0.0/storage-download.py +++ b/db/setup/storage-download.py @@ -2,7 +2,7 @@ from azure.storage.blob import BlobServiceClient -def folderProcessing(storage_url,container_name): +def folderProcessing(storage_url, container_name): """ This function downloads all the files from a container in a storage account to the local directory "test" @@ -15,13 +15,13 @@ def folderProcessing(storage_url,container_name): Returns: None """ - + # Create a blob service client blob_service_client = BlobServiceClient.from_connection_string(conn_str=storage_url) # # Get the container client container_client = blob_service_client.get_container_client(container_name) - if container_client.exists() : + if container_client.exists(): print("Container exists") else: print("Container does not exist") @@ -29,22 +29,24 @@ def folderProcessing(storage_url,container_name): # print(container_name) # Specify the local directory local_dir = "test" - + # List blobs in the container blob_list = container_client.list_blobs() i = 0 # Iterate through each blob for blob in blob_list: + # Create a blob client + blob_client = blob_service_client.get_blob_client( + container=container_name, blob=blob.name + ) # Download the blob local_file_path = f"{local_dir}/{blob.name}" os.makedirs(os.path.dirname(local_file_path), exist_ok=True) with open(local_file_path, "wb") as file: - blob_data = container_client.download_blob(blob=blob.name) + blob_data = blob_client.download_blob() + # blob_data = container_client.download_blob(blob=blob.name) blob_data.readinto(file) - i= i + 1 - print("downloaded files: " + str(i) ) - -if __name__ == "__main__": - folderProcessing("","") + i = i + 1 + print("downloaded : " + str(i) + "/" + str(len(blob_list))) diff --git a/file-validator.py b/db/validator.py similarity index 60% rename from file-validator.py rename to db/validator.py index 9d20fbea..ec8e1687 100644 --- a/file-validator.py +++ b/db/validator.py @@ -1,40 +1,38 @@ from datetime import date from pydantic import BaseModel -from typing import List -import yaml -import json -import os -import psycopg -from azure.storage.blob import BlobServiceClient -from azure.core import paging -from PIL import Image + class ClientData(BaseModel): clientEmail: str clientExpertise: str + class SeedData(BaseModel): seedID: int seedFamily: str seedGenus: str seedSpecies: str + class ImageDataindex(BaseModel): numberOfImages: int - + + class AuditTrail(BaseModel): uploadDate: date editedBy: str editDate: date changeLog: str accessLog: str - privacyFlag: bool + privacyFlag: bool + class Info(BaseModel): userID: int uploadDate: date indexID: int + class ImageData(BaseModel): format: str height: int @@ -43,6 +41,7 @@ class ImageData(BaseModel): source: str parent: str + class QualityCheck(BaseModel): imageChecksum: str uploadCheck: bool @@ -50,53 +49,37 @@ class QualityCheck(BaseModel): errorType: str dataQualityScore: float + class UserData(BaseModel): description: str numberOfSeeds: int - zoom: float + zoom: float + class Index(BaseModel): clientData: ClientData imageData: ImageDataindex seedData: SeedData - + + class PIndex(BaseModel): clientData: ClientData imageData: ImageDataindex seedData: SeedData auditTrail: AuditTrail - + + class Picture(BaseModel): - userData: UserData - + userData: UserData + + class PPicture(BaseModel): userData: UserData info: Info imageData: ImageData qualityCheck: QualityCheck + class ClientFeedback(BaseModel): correctIdentification: bool historicalComparison: str - - -# def is_json_empty(filename): -# with open(filename) as json_file: -# data = json.load(json_file) -# return len(data) == 0 - -# def fill_index_system(filename): -# if is_json_empty(filename): -# with open(filename, 'w') as json_file: -# json.dump(default_json_data, json_file, indent=2) - -# def is_yaml_file(file_path): -# _, file_extension = os.path.splitext(file_path) -# return file_extension.lower() == '.yaml' - -container_URL="https://seedgroup.blob.core.windows.net/sas1706-tagarno-exp2-3-15species" - - - -if __name__ == "__main__": - print("test") diff --git a/img/malicious-site.png b/img/malicious-site.png new file mode 100644 index 0000000000000000000000000000000000000000..6ba56704c508ed4aa54f0af0bd3d3996ebf6be05 GIT binary patch literal 103753 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 literal 0 HcmV?d00001 diff --git a/index-mockData.yaml b/index-mockData.yaml index d78ec56c..9479d805 100644 --- a/index-mockData.yaml +++ b/index-mockData.yaml @@ -4,7 +4,7 @@ clientData: imageData: numberOfImages: 4 seedData: - seedID: BrNa-02 - seedFamily: ddada - seedGenus: dadaa - seedSpecies: dada \ No newline at end of file + seedID: e5b69945-377d-47e5-98d1-7f350c1fe240 + seedFamily: Poaceae + seedGenus: Triticum + seedSpecies: Aestivum \ No newline at end of file diff --git a/index.yaml b/index.yaml index bb5bcd9d..126ec56f 100644 --- a/index.yaml +++ b/index.yaml @@ -5,7 +5,7 @@ clientData: imageData: numberOfImages: number of images seedData: - seedID: seed ID + seedID: seed ID (uuid) seedFamily: seed Family seedGenus: seed Genus seedSpecies: seed Species diff --git a/metadata-doc.md b/metadata-doc.md index 537ce3b4..4cdfcfa6 100644 --- a/metadata-doc.md +++ b/metadata-doc.md @@ -89,7 +89,8 @@ sequenceDiagram; Notebook -) Azure Storage: Processing files into metadata DataScientist->> Azure Storage: Use files to train the model ``` -This sequence illustrate the manual task done by our team to maintain the storage of user's data. +This sequence illustrate the manual task done by our team to maintain the +storage of user's data. ### Legend | Element | Description | | ---------------------- | -------------------------------------------------------------------------------------------------------------------------- | @@ -137,7 +138,8 @@ flowchart LR; linkStyle 3,4,5 stroke:#f00,stroke-width:4px,color:red; style dbProcess stroke:#f00,stroke-width:2px ``` -*Note that the bottom process wont be present on the deployed versions of Nachet* +*Note that the bottom process wont be present on the deployed versions of +Nachet* ## New Process ``` mermaid @@ -182,12 +184,79 @@ sequenceDiagram; ``` This sequence encapsulate the expected tasks of the new feature. - +### Queries +To communicate with the database and perform the request, we will need to build +a structure representing the schema. + +```mermaid + classDiagram + class User { + <> uuid id + string email + User(email) User + getUser(id) User + isUser(email) bool + registerUser() uuid + update() + getAllIndexes(id) List~Index~ + getAllPictures(id) List~Pictures~ + } + + class Index { + <> uuid id + json index + <> uuid ownerID + getIndex(id) Index + update() + getSeed() Seed + getNbPicture int + getAllPictures(id) List~Pictures~ + } + + class Picture { + <> uuid id + json picture + <> uuid indexID + <> uuid parentID + getPicture(id) + update() + getParent(id) + getUrlSource() string + } + class PictureSeed { + json pictureSeed + getSeed() Seed + getNbSeeds() int + getZoom() float + getUrlSas() string + + } + class Seed{ + <> uuid id + string name + getAllPictures() List~Pictures~ + getAllIndexes() List~Index~ + } + + class Search{ + uuid userID + uuid indexID + uuid pictureID + uuid seedID + float zoom + nbSeed int + date startDate + date endDate + } + Picture <|-- PictureSeed +``` ### Requests (Backend) -Nachet backend will need the following requests to be able to handle the new process. +Nachet backend will need the following requests to be able to handle the new +process. -*Note the name of the requests are subject to change and are currently meant to be explicit about the purpose of the call* +*Note the name of the requests are subject to change and are currently meant to +be explicit about the purpose of the call* #### User requests | Name | Description | @@ -210,15 +279,22 @@ Nachet backend will need the following requests to be able to handle the new pro | MaliciousPictureCheck|This will not be a request related to the DB or metadata, but it is imperative to check the picture file and make sure it's a picture and not a malicious file with hidden code or content into it. | | uploadDataSet | This request happen once the data set receive the ok to all the validation checks. It serves as uploading all the folder to diverse endpoint depending on the file type. | #### Validation Errors -Here's a list of the errors that can be returned turing the validation of the upload -| Name | Description | -| ---------------------- | -------------------------------------------------------------------------------------------------------------------------- | -| Wrong structure | This type if error indicate the folder uploaded by the user doesn't follow the required structure. | -| Missing Index | An Index is missing which means the whole folder of picture couldn't be processed. This might stop the upload process as a whole | -|Index content | A specific Index either has missing fields or unexpected values | -|Unexpected file | Based on the value given by the user within the index, there are more files present in the subfolder than expected | -|Missing file | Based on the value given by the user within the index, there are less picture files than expected| -| content | This error indicate there's an issue with one of the data field in the file called 'picture.yml'
*(If the number of seeds and zoom field are not removed from picture.yaml)* | +Here's a list of the errors that can be returned turing the validation of the +upload | Name | Description +| | ---------------------- | +-------------------------------------------------------------------------------------------------------------------------- +| | Wrong structure | This type if error indicate the +folder uploaded by the user doesn't follow the required structure. +| | Missing Index | An Index is missing which means the whole folder of +picture couldn't be processed. This might stop the upload process as a whole +| |Index content | A specific Index either has missing fields or unexpected +values | |Unexpected file | Based on the value given by the user within the +index, there are more files present in the subfolder than expected | |Missing +file | Based on the value given by the user within the index, there are less +picture files than expected| | content | This error indicate +there's an issue with one of the data field in the file called 'picture.yml' +
*(If the number of seeds and zoom field are not removed from picture.yaml)* +| ### Files Structure We aim to have a standard file structure to enable the use of a script to manage @@ -264,9 +340,11 @@ about the user and the project/session. Each picture should have their .yaml conterpart. This will allow us to run scripts into the session folder and monitor each picture easily. -*Note: 'picture' in this exemple is replacing the picture number or name of the .tiff file* +*Note: 'picture' in this exemple is replacing the picture number or name of the +.tiff file* ## Database -We plan on storing the metadata of the user's files in a postgreSQL Database. The database should have the following structure: +We plan on storing the metadata of the user's files in a postgreSQL Database. +The database should have the following structure: ``` mermaid --- title: Nachet DB Structure @@ -280,19 +358,20 @@ erDiagram indexes{ uuid id PK json index - int ownerID FK + uuid ownerID FK } pictures{ uuid id PK json picture - int indexID FK + uuid indexID FK + uuid parent FK } feedbacks{ int ID PK json feedback } seeds{ - int id PK + uuid id PK string name json information } @@ -305,7 +384,11 @@ erDiagram ``` ## Blob Storage -Finally the picture uploaded by the users will need to be stored in a blob storage. Therefore we are using a Azure blob Storage account which currently contains a list of containers either for the users upload or our Data scientists training sets. The current structure needs to be revised and a standarized structure needs to pe applied for the futur of Nachet. +Finally the picture uploaded by the users will need to be stored in a blob +storage. Therefore we are using a Azure blob Storage account which currently +contains a list of containers either for the users upload or our Data scientists +training sets. The current structure needs to be revised and a standarized +structure needs to pe applied for the futur of Nachet. ``` Storage account diff --git a/nachetDb-1.0.0/db-mock-seeds-population.py b/nachetDb-1.0.0/db-mock-seeds-population.py deleted file mode 100644 index 9447f0d0..00000000 --- a/nachetDb-1.0.0/db-mock-seeds-population.py +++ /dev/null @@ -1,31 +0,0 @@ -import psycopg -import os -import uuid - -# Connect to your PostgreSQL database with the DB URL -conn = psycopg.connect(os.getenv("NACHET_DB_URL")) -# Create a cursor object -cur = conn.cursor() - -id = uuid.uuid4() - -#Query to insert a seed -query="INSERT INTO \"nachetdb_1.0.0\".seeds (name) VALUES ('Solanum nigrum')" - -#query = "Select id,name from \"nachetdb_1.0.0\".seeds" - -print(query) - -cur.execute(query) - -## Commit the transaction -conn.commit() - -for row in cur.fetchall(): - print(row) - - -# Close the cursor and connection -cur.close() -conn.close() -print("done") diff --git a/nachetDb-1.0.0/db-queries.py b/nachetDb-1.0.0/db-queries.py deleted file mode 100644 index ce108f64..00000000 --- a/nachetDb-1.0.0/db-queries.py +++ /dev/null @@ -1,25 +0,0 @@ -import psycopg -import os - -print(os.getenv("NACHET_DB_URL")) -# Connect to your PostgreSQL database -conn = psycopg.connect(os.getenv("NACHET_DB_URL")) -# Create a cursor object -cur = conn.cursor() - -# check if the table exists -cur.execute(""" - SELECT id,picture from "nachetdb_1.0.0".pictures -""") - -## Commit the transaction -conn.commit() - -for record in cur: - print(record) -#print(cur.fetchone()[1]) - -# Close the cursor and connection -cur.close() -conn.close() -print("done") \ No newline at end of file diff --git a/nachetDb-1.0.0/deployment-mass-import.py b/nachetDb-1.0.0/deployment-mass-import.py deleted file mode 100644 index 0708663c..00000000 --- a/nachetDb-1.0.0/deployment-mass-import.py +++ /dev/null @@ -1,520 +0,0 @@ -from datetime import date -from pydantic import BaseModel -import uuid -import yaml -import json -import os -import psycopg -from azure.storage.blob import BlobServiceClient -from PIL import Image - -# File: nachetDb-1.0.0/mass-import.py -# This script is used to import the missing metadata from an Azure container to the database - - -# Constants -container_URL="" - - -# Class to represent the files metadata - -class ClientData(BaseModel): - clientEmail: str - clientExpertise: str - -class SeedData(BaseModel): - seedID: int - seedFamily: str - seedGenus: str - seedSpecies: str - -class ImageDataindex(BaseModel): - numberOfImages: int - -class AuditTrail(BaseModel): - uploadDate: date - editedBy: str - editDate: date - changeLog: str - accessLog: str - privacyFlag: bool - -class Info(BaseModel): - userID: str - uploadDate: date - indexID: str - -class ImageData(BaseModel): - format: str - height: int - width: int - resolution: str - source: str - parent: str - -class QualityCheck(BaseModel): - imageChecksum: str - uploadCheck: bool - validData: bool - errorType: str - dataQualityScore: float - -class UserData(BaseModel): - description: str - numberOfSeeds: int - zoom: float - -class Index(BaseModel): - clientData: ClientData - imageData: ImageDataindex - seedData: SeedData - -class PIndex(BaseModel): - clientData: ClientData - imageData: ImageDataindex - seedData: SeedData - auditTrail: AuditTrail - -class Picture(BaseModel): - userData: UserData - -class PPicture(BaseModel): - userData: UserData - info: Info - imageData: ImageData - qualityCheck: QualityCheck - -class ClientFeedback(BaseModel): - correctIdentification: bool - historicalComparison: str - - -def manualMetaDataImport(picturefolder:str): - """ - Template function to do the importation process of the metadata from the Azure container to the database. - The user is prompted to input the client email, the zoom level and the number of seeds for the index. - The container needs to be downloaded locally before running this function. - """ - - # Manually define the picture folder - picturefolder = "" #manually inputted before each import sequence - - # Input the client email to identify the user or register him if needed - clientEmail = input("clientEmail: ") - userID = getUserID(clientEmail) - - # #build index - nbPic = buildIndex(picturefolder,userID) - - # upload index to database - indexID = uploadIndexDB(f'{picturefolder}/index.json',userID=userID) - #indexID=1 - print("indexID : " + str(indexID)) - - # for each picture in field - zoomlevel = input("Zoom level for this index: ") - seedNumber = input("Number of seed for this index: ") - - # Get a list of files in the directory - files = [f for f in os.listdir(picturefolder) if os.path.isfile(os.path.join(picturefolder, f))] - i = 0 - # Loop through each file in the folder - for filename in files: - if filename.endswith(".tiff") or filename.endswith(".tif"): - buildPicture(picturefolder,seedNumber,zoomlevel,indexID,filename,userID) - picPath = f'{picturefolder}/{filename.removesuffix(".tiff")}.json' - #upload picture to database - uploadPictureDB(picPath,userID,indexID) - i=i+1 - - if i != nbPic: - print("Error: number of pictures processed does not match the index") - print("Number of picture processed: " + str(i)) - print("Number of picture in index: " + str(nbPic)) - else: - print("importation of " + picturefolder + "/ complete") - print("Number of picture processed: " + str(i)) - - -# Function to retrieve the userID from the database based on the email -# Returns the userID if the email is already registered, else it registers the email and returns the userID -def getUserID(email:str): - """ - Function to retrieve the userID from the database based on the email. - If the email is not already registered, it registers the email and returns the userID. - - Parameters: - - email (str): The email of the user. - - Returns: - - The userID of the user. - """ - # Connect to your PostgreSQL database - conn = psycopg.connect(os.getenv("NACHET_DB_URL")) - # Create a cursor object - cur = conn.cursor() - # Check if the email is already registered - cur.execute(f"SELECT Exists(SELECT 1 FROM \"nachetdb_1.0.0\".users WHERE email='{email}')") - if cur.fetchone()[0]: #Already registered - cur.execute(f"SELECT id FROM\"nachetdb_1.0.0\".users WHERE email='{email}'") - res = cur.fetchone()[0] - else: #Not registered -> Creates new user - cur.execute(f"INSERT INTO \"nachetdb_1.0.0\".users (id,email) VALUES (,'{email}')") - cur.execute(f"SELECT id FROM \"nachetdb_1.0.0\".users WHERE email='{email}'") - res = cur.fetchone()[0] - cur.close() - conn.close() - return res - -# Function to build the index metadata file -def buildIndex(output:str,clientID): - """ - This function builds the index needed to represent each folder (with pictures in it) of a container. - It prompts the user to input the expertise of the client and the number of images in the folder. - - Parameters: - - output (str): The path to the folder. - - clientID (str): The UUID of the client. - - Returns: - - The number of pictures in the folder that are supposed to be processed. - """ - - #clientEmail = input("clientEmail: ") - clientEmail="test@email" - print("clientEmail: " + clientEmail) - clientExpertise = input("Expertise: ") - - #Create the index metadata (sub part) normally filled by the user - clientData = ClientData(clientEmail=clientEmail,clientExpertise=clientExpertise) - nb = input("numberOfImages: ") - imageData = ImageDataindex(numberOfImages=nb) - - # ATM===> I dont have access to the seedID db - seedID = input("SeedID: ") - - - #family = input("Family: ") - #genus = input("Genus: ") - #species = input("Species: ") - family="" - genus="" - species="" - seedData = SeedData(seedID=seedID,seedFamily=family,seedGenus=genus,seedSpecies=species) - - # Create the Index object - index = Index(clientData=clientData,imageData=imageData,seedData=seedData) - print("File created, name: " + output + "/index.json") - indexJson=index.dict() - - # Creating index metadata collected from the system - sysData=IndexProcessing(openIndexSystem()) - - #Append the system data to the index - indexJson.update(sysData) - - #Create the index file - createJsonIndex(index=indexJson,name="index",output=output) - return nb - -def buildPicture(output:str,number:int,level, indexID:int,name:str,userID:str): - """ - This function builds the picture metadata file (.json) for each picture in the folder. - - Parameters: - - output (str): The path to the folder. - - number (int): The number of seeds in the picture. - - level (float): The zoom level of the picture. - - indexID (int): The ID of the index. - - name (str): The name of the picture. - - userID (str): The UUID of the user. - - Returns: - None - """ - - desc= "This image was uploaded before the creation of the database and was apart of the first importation batch." - nb=number - zoom=level - userData = UserData(description=desc,numberOfSeeds=nb,zoom=zoom) - - # Create the Picture object with the user data - pic = Picture(userData=userData) - picJson=pic.dict() - print("File created, name: " + name) - picturePath=f'{output}/{name}' - - # Creating picture metadata collected from the system - picData=PictureProcessing(openPictureSystem(),userID,indexID,picturePath) - picJson.update(picData) - createJsonPicture(pic=picJson,name=name,output=output) - -def IndexProcessing(jsonData: str): - """ - Function to create the system index metadata. - - Parameters: - - jsonData (str): The JSON data of the index. - - Returns: - - The system index object populated with the metadata. - """ - today = date.today() - editedBy = "Francois Werbrouck" #not permanent, will be changed once the mass import is over - editDate = date.today() - change="" - access="" - privacy=False - return IndexSystemPopulating(jsonData,today,editedBy,editDate,change,access,privacy) - -# Function to create the system picture metadata -# Returns the system picture object populated with the metadata -def PictureProcessing(jsonData:str,userID:str,indexID:int,picturePath:str): - """ - Function to create the system picture metadata. - - Parameters: - - jsonData (str): The JSON data of the picture. - - userID (str): The UUID of the user. - - indexID (int): The ID of the index. - - picturePath (str): The path to the picture. - - Returns: - - The system picture object populated with the metadata. - """ - today=date.today() - # userID - # indexID - info=getImageProperties(picturePath) - parent="" - source=container_URL+picturePath.removesuffix("test") - format=info[2] - height=info[1] - width=info[0] - resolution="" - return PictureSystemPopulating(jsonData,today,str(userID),indexID,format,height,width,resolution,source,parent) - -# Function to populate the index metadata file provided -# Returns the index metadata object populated with the metadata -def IndexSystemPopulating(data,date:date,editedBy,editDate:date,changes:str,access,privacy): - """ - Function to populate the index metadata file provided. - - Parameters: - - data (str): The JSON data template of the index. - - date (date): The date of the upload - - editedBy (str): The name of the person who edited the file - - editDate (date): The date of the last edit - - changes (str): The changes made to the file - - access (str): The access log - - Returns: - - The index metadata object populated with the metadata. - """ - #print(data) - data["auditTrail"]["uploadDate"]=date.strftime("%Y-%m-%d") - data["auditTrail"]["editedBy"]=editedBy - data["auditTrail"]["editDate"]=editDate.strftime("%Y-%m-%d") - data["auditTrail"]["changeLog"]=changes - data["auditTrail"]["accessLog"]=access - data["auditTrail"]["privacyFlag"]=privacy - return data - -# Function to populate the picture metadata file provided -# Returns the picture metadata object populated with the metadata -def PictureSystemPopulating(data,date:date,userID:str,indexID:int,format:str,height:int,width:int,resolution:str,source:str,parent:str): - """ - Function to populate the Picture metadata file provided. - - Parameters: - - data (str): The JSON data template of the Picture. - - date (date): The date of the upload - - userID (str): The UUID of the user - - indexID (int): The ID of the index - - format (str): The format of the picture - - height (int): The height of the picture - - width (int): The width of the picture - - resolution (str): The resolution of the picture - - source (str): The source of the picture - - parent (str): The parent of the picture - - Returns: - - The Picture metadata object populated with the metadata. - """ - data["info"]["userID"]=userID - data["info"]["uploadDate"]=date.strftime("%Y-%m-%d") - data["info"]["indexID"]=indexID - data["imageData"]["height"]=height - data["imageData"]["width"]=width - data["imageData"]["format"]=format - data["imageData"]["source"]=source - data["imageData"]["parent"]=parent - data["imageData"]["resolution"]=resolution - - data["qualityCheck"]["imageChecksum"]="" - data["qualityCheck"]["uploadCheck"]=True - data["qualityCheck"]["validData"]=True - data["qualityCheck"]["errorType"]="" - data["qualityCheck"]["dataQualityScore"]=1.0 - - return data - -def getImageProperties(path:str): - """ - Function to retrieve an image's properties. - - Parameters: - - path (str): The path to the image. - - Returns: - - The image's width, height and format as a tuple. - """ - with Image.open(path) as img: - width, height = img.size - img_format = img.format - return width,height,img_format - -# Function to open the system index metadata template file -# Returns an empty system index metadata object -def openIndexSystem(): - """ - Function to open the system index metadata template file. - - Returns: - - An empty system index metadata object. - """ - with open('index-template-system.json','r') as file: - sysData=json.load(file) - #print(sysData) - return sysData - -# Function to open the system picture metadata template file -# Returns an empty system picture metadata object -def openPictureSystem(): - """ - Function to open the system picture metadata template file. - - Returns: - - An empty system picture metadata object. - """ - with open('picture-template-system.json','r') as file: - sysData=json.load(file) - #print(sysData) - return sysData - -# Create .json from Index data model -def createJsonIndex(index,name: str,output:str): - """ - Create .json from index data model to the specified output. - - Parameters: - - index (dict): The index data model. - - name (str): The name of the file. - - output (str): The path to the folder. - - Returns: - None - """ - #data=index.dict() - data = PIndex(**index) - filePath = f'{output}/{name}.json' - with open(filePath,'w') as json_file: - json.dump(index,json_file,indent=2) - -#Create .json form Picture data model -def createJsonPicture(pic,name:str,output:str): - """ - Create .json from picture data model to the specified output. - - Parameters: - - pic (dict): The picture data model. - - name (str): The name of the file. - - output (str): The path to the folder. - - Returns: - None - """ - data = PPicture(**pic) - extension=name.split(".")[-1] - filename = name.removesuffix("."+extension) - filePath = f'{output}/{filename}.json' - with open(filePath,'w') as json_file: - json.dump(pic,json_file,indent=2) - -# Function to upload the index file located @path to the database -# RETURNS the indexID of the uploaded index -def uploadIndexDB(path:str,userID:str): - """ - Upload the index.json file located at the specified path to the database. - - Parameters: - - path (str): The path to the index file. - - userID (str): The ID of the user. - - Returns: - - The ID of the index. - """ - - # Connect to your PostgreSQL database - conn = psycopg.connect(os.getenv("NACHET_DB_URL")) - - # Create a cursor object - cur = conn.cursor() - - # Open the file - with open(path, 'r') as file: - data = file.read() - - #Build indexID - indexID = uuid.uuid4() - - # Execute the INSERT statement - print(userID) - cur.execute("INSERT INTO \"nachetdb_1.0.0\".indexes (id,index, ownerID) VALUES (%s,%s, %s)", (indexID,data, userID)) - conn.commit() - - #Retrieve the index id - #cur.execute("SELECT id FROM \"nachetdb_1.0.0\".indexes ORDER BY id DESC LIMIT 1") - - - cur.close() - conn.close() - return indexID - - -def uploadPictureDB(path:str,userID:str,indexID:int): - """ - Uploads the picture file located at the specified path to the database. - - Parameters: - - path (str): The path to the picture file. - - userID (str): The ID of the user. - - indexID (int): The ID of the index. - - Returns: - None - """ - # Connect to your PostgreSQL howatabase - conn = psycopg.connect(os.getenv("NACHET_DB_URL")) - - # Create a cursor object - cur = conn.cursor() - - # Open the file - with open(path, 'r') as file: - data = file.read() - - #Generate pictureID - pictureID = uuid.uuid4() - - # Execute the INSERT statement - cur.execute("INSERT INTO \"nachetdb_1.0.0\".pictures (id,picture, indexID) VALUES (%s,%s, %s)", (pictureID,data, indexID)) - conn.commit() - - cur.close() - conn.close() - - -if __name__ == "__main__": - manualMetaDataImport("") - \ No newline at end of file