6
0
Răsfoiți Sursa

Merge branch 'ammod' into ammod-feature-pre-generation-of-thumbnails

Dimitri Korsch 3 ani în urmă
părinte
comite
78315bf6a5

+ 6 - 0
app.py

@@ -1,8 +1,14 @@
 #!/usr/bin/env python
 
+import logging.config
+
 from pycs import app
 from pycs import settings
 from pycs.frontend.WebServer import WebServer
+from pycs.management import setup_commands
+
+logging.config.dictConfig(settings.logging)
+setup_commands(app)
 
 if __name__ == '__main__':
     server = WebServer(app, settings)

Fișier diff suprimat deoarece este prea mare
+ 964 - 0
labels/LepiForum_PandasVersion/LepiForum_Species_edited_by_GBrehm.csv


+ 133 - 0
labels/LepiForum_PandasVersion/Provider.py

@@ -0,0 +1,133 @@
+import re
+import numpy as np
+import pandas as pd
+import typing as T
+
+from pathlib import Path
+from munch import munchify
+
+from pycs import app
+from pycs.interfaces.LabelProvider import LabelProvider
+
+class Provider(LabelProvider):
+
+    names = [
+        'is_local',
+        'rarity',
+        'super_family',
+        'family',
+        'sub_family',
+        'tribe',
+        'german',
+        'swiss',
+        'austrian',
+        'kr_nr',
+        'genus',
+        'species',
+        'authors',
+        'comment',
+        'remove_me',
+        'changed',
+        'version1_comment',
+        'misc', # 'D-CH-A / non-KR / Kaukasus',
+        'german_name',
+    ]
+
+    dtype = {
+        'is_local': pd.CategoricalDtype(['nur lokal', 'tagaktiv']),
+        'rarity': np.float32,
+        'super_family': "category",
+        'family': "category",
+        'sub_family': "category",
+        'tribe': "category",
+        'german': pd.CategoricalDtype(['D', 'e', '?']),
+        'swiss': pd.CategoricalDtype(['C', 'e', '?']),
+        'austrian': pd.CategoricalDtype(['A', 'e', '?']),
+        'kr_nr': "object",
+        'genus': "category",
+        'species': "category",
+        'authors': "object",
+        'comment': "object",
+        'remove_me': "category",
+        'changed': "object",
+        'version1_comment': "object",
+        'misc': "object",
+        'german_name': str,
+    }
+
+    KR_REGEX = re.compile(r"^[\d\-a-zA-Z]+")
+
+
+    def __init__(self, root_folder: str, configuration: T.Dict):
+        config = munchify(configuration)
+        self.root = Path(root_folder)
+
+        self.label_file = self.root / config.filename
+        self.min_rarity = config.minimumRarity
+        self.hierarchy_levels = config.hierarchyLevels
+        self.only_german = config.onlyGerman
+
+    def close(self):
+        pass
+
+    def get_labels(self) -> T.List[dict]:
+        result = []
+
+        lepi_list = pd.read_csv(self.label_file,
+                        names=self.names,
+                        dtype=self.dtype,
+                        sep="\t", header=0
+                       )
+        app.logger.info(f"Found {len(lepi_list)} labels in {self.label_file}")
+
+        if self.min_rarity is not None:
+            mask = lepi_list.rarity >= self.min_rarity
+            lepi_list = lepi_list[mask]
+            app.logger.info(f"Labels {len(lepi_list):,d} with {self.min_rarity=}")
+
+        if self.only_german:
+            mask = (
+                lepi_list.german.eq("D") |
+                lepi_list.austrian.eq("A") |
+                lepi_list.swiss.eq("C")
+                ) & \
+                lepi_list["remove_me"].isin([np.nan])
+
+            lepi_list = lepi_list[mask]
+            app.logger.info(f"Labels {len(lepi_list):,d} for german-speaking countries")
+
+
+        parents = set()
+        for i, entry in lepi_list.iterrows():
+            parent_reference = None
+
+            for level, level_name in self.hierarchy_levels:
+                level_entry = entry[level]
+                if level_entry is None:
+                    continue
+
+                reference, name = f'{level}_{level_entry.lower()}', level_entry
+
+                # parents should be added once
+                if reference not in parents:
+                    result.append(self.create_label(reference, name, parent_reference, level_name))
+                    parents.add(reference)
+
+                parent_reference = reference
+
+
+            # add label itself
+            if self.KR_REGEX.match(entry.kr_nr):
+                name = f'{entry.genus} {entry.species} ({entry.kr_nr})'
+                reference = entry.kr_nr
+
+            else:
+                name = f'{entry.genus} {entry.species}'
+                reference = f'_{name.lower()}'
+            result.append(self.create_label(reference, name, parent_reference))
+
+
+        app.logger.info(f"Finally, provided {len(result):,d} labels")
+        return result
+
+

+ 16 - 0
labels/LepiForum_PandasVersion/configuration1.json

@@ -0,0 +1,16 @@
+{
+  "name": "LepiForum (Alle Spezies)",
+  "description": "Stand: 01.12.2021, bearbeitet GBrehm",
+  "code": {
+    "module": "Provider",
+    "class": "Provider"
+  },
+
+  "filename": "LepiForum_Species_edited_by_GBrehm.csv",
+  "minimumRarity": null,
+  "onlyGerman": false,
+  "hierarchyLevels": [
+    ["family", "Familie"],
+    ["genus", "Gattung"]
+  ]
+}

+ 16 - 0
labels/LepiForum_PandasVersion/configuration2.json

@@ -0,0 +1,16 @@
+{
+  "name": "LepiForum (Alle Spezies aus D/A/CH)",
+  "description": "Stand: 01.12.2021, bearbeitet GBrehm",
+  "code": {
+    "module": "Provider",
+    "class": "Provider"
+  },
+
+  "filename": "LepiForum_Species_edited_by_GBrehm.csv",
+  "minimumRarity": null,
+  "onlyGerman": true,
+  "hierarchyLevels": [
+    ["family", "Familie"],
+    ["genus", "Gattung"]
+  ]
+}

+ 16 - 0
labels/LepiForum_PandasVersion/configuration3.json

@@ -0,0 +1,16 @@
+{
+  "name": "LepiForum (Nur häufige Spezies aus D/A/CH)",
+  "description": "Stand: 01.12.2021, bearbeitet GBrehm",
+  "code": {
+    "module": "Provider",
+    "class": "Provider"
+  },
+
+  "filename": "LepiForum_Species_edited_by_GBrehm.csv",
+  "minimumRarity": 0,
+  "onlyGerman": true,
+  "hierarchyLevels": [
+    ["family", "Familie"],
+    ["genus", "Gattung"]
+  ]
+}

+ 14 - 0
labels/LepiForum_PandasVersion/configuration4.json

@@ -0,0 +1,14 @@
+{
+  "name": "LepiForum (Alle Spezies aus D/A/CH, ohne Hierarchie)",
+  "description": "Stand: 01.12.2021, bearbeitet GBrehm",
+  "code": {
+    "module": "Provider",
+    "class": "Provider"
+  },
+
+  "filename": "LepiForum_Species_edited_by_GBrehm.csv",
+  "minimumRarity": null,
+  "onlyGerman": true,
+  "hierarchyLevels": [
+  ]
+}

+ 4 - 0
pycs/__init__.py

@@ -15,6 +15,7 @@ from sqlalchemy import event
 from sqlalchemy import pool
 from sqlalchemy.engine import Engine
 
+
 print('=== Loading settings ===')
 with open('settings.json') as file:
     settings = munchify(json.load(file))
@@ -25,6 +26,8 @@ if not os.path.exists(settings.projects_folder):
 
 DB_FILE = Path.cwd() / settings.database
 
+
+
 app = Flask(__name__)
 app.config["SQLALCHEMY_DATABASE_URI"] = f"sqlite:///{DB_FILE}"
 app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
@@ -45,3 +48,4 @@ db = SQLAlchemy(app, engine_options=dict(
     )
 )
 migrate = Migrate(app, db)
+

+ 7 - 2
pycs/database/File.py

@@ -196,7 +196,7 @@ class File(NamedBaseModel):
     def create_result(self,
                       origin: str,
                       result_type: str,
-                      label: T.Optional[T.Union[Label, int]] = None,
+                      label: T.Optional[T.Union[Label, int, str]] = None,
                       data: T.Optional[dict] = None) -> Result:
         """
         Creates a result and returns the created object
@@ -212,7 +212,12 @@ class File(NamedBaseModel):
         result.data = data
 
         if label is not None:
-            assert isinstance(label, (int, Label)), f"Wrong label type: {type(label)}"
+            assert isinstance(label, (int, Label, str)), f"Label \"{label}\" has invalid type: {type(label)}"
+
+            if isinstance(label, str):
+                label = Label.query.filter(
+                    Label.project_id == self.project_id,
+                    Label.reference == label).one_or_none()
 
             if isinstance(label, Label):
                 label = label.id

+ 3 - 0
pycs/database/Project.py

@@ -174,6 +174,9 @@ class Project(NamedBaseModel):
                 - AssertionError if project_id and reference are not unique
                 - ValueError if a cycle in the hierarchy is found
         """
+        if len(labels) == 0:
+            return labels
+
         if clean_old_labels:
             self.labels.delete()
 

+ 0 - 2
pycs/frontend/WebServer.py

@@ -1,4 +1,3 @@
-import logging.config
 import typing as T
 
 from glob import glob
@@ -66,7 +65,6 @@ class WebServer:
 
     def __init__(self, app, settings: munch.Munch, discovery: bool = True):
 
-        logging.config.dictConfig(settings.logging)
         self.app = app
         # set json encoder so database objects are serialized correctly
         self.app.json_encoder = JSONEncoder

+ 5 - 1
pycs/frontend/endpoints/pipelines/PredictModel.py

@@ -12,6 +12,7 @@ from pycs.database.Result import Result
 from pycs.frontend.notifications.NotificationList import NotificationList
 from pycs.frontend.notifications.NotificationManager import NotificationManager
 from pycs.interfaces.MediaFile import MediaFile
+from pycs.interfaces.MediaLabel import MediaLabel
 from pycs.interfaces.MediaBoundingBox import MediaBoundingBox
 from pycs.interfaces.MediaStorage import MediaStorage
 from pycs.jobs.JobGroupBusyException import JobGroupBusyException
@@ -170,7 +171,10 @@ class PredictModel(View):
 
                 # Add the labels determined in the inference process.
                 for i, result in enumerate(result_filter[file_id]):
-                    result.label_id = bbox_labels[i].identifier
+                    bbox_label = bbox_labels[i]
+                    if isinstance(bbox_label, MediaLabel):
+                        result.label_id = bbox_label.identifier
+
                     result.set_origin('user', commit=True)
                     notifications.add(notification_manager.edit_result, result)
 

+ 45 - 0
pycs/management.py

@@ -0,0 +1,45 @@
+import click
+from tabulate import tabulate
+
+from pycs import app
+from pycs.database.Project import Project
+from pycs.util import FileOperations
+
+from flask.cli import AppGroup
+from flask.cli import with_appcontext
+
+def setup_commands(app):
+    app.cli.add_command(project_cli)
+
+project_cli = AppGroup("project")
+
+@project_cli.command()
+@click.argument("project_id")
+def generate_thumbnails(project_id):
+    if project_id == "all":
+        projects = Project.query.all()
+        app.logger.info(f"Generating thumbnails for all projects ({len(projects)})!")
+    else:
+        project = Project.query.get(project_id)
+        if project is None:
+            app.logger.error(f"Could not find project with ID {project_id}!")
+            return
+        app.logger.info(f"Generating thumbnails for project {project}!")
+        projects = [project]
+
+    for project in projects:
+        FileOperations.generate_thumbnails(project)
+
+@project_cli.command("list")
+def list_projects():
+    projects = Project.query.all()
+
+    print(f"Got {len(projects)} projects")
+    rows = [(p.id, p.name, p.description) for p in projects]
+
+    print(tabulate(rows,
+        headers=["id", "name", "description"],
+        tablefmt="fancy_grid"
+    ))
+
+

+ 16 - 0
pycs/util/FileOperations.py

@@ -7,13 +7,17 @@ from pathlib import Path
 import cv2
 
 from PIL import Image
+from tqdm import tqdm
 
+from pycs import app
 from pycs.database.File import File
+from pycs.database.Project import Project
 
 DEFAULT_JPEG_QUALITY = 80
 
 
 BoundingBox = namedtuple("BoundingBox", "x y w h")
+Size = namedtuple("Size", "max_width max_height")
 
 
 def file_info(data_folder: str, file_name: str, file_ext: str):
@@ -258,3 +262,15 @@ def find_images(folder,
             images.append(fpath)
 
     return images
+
+
+def generate_thumbnails(project: Project, sizes = [Size(200, 200), Size(2000, 1200)]):
+    app.logger.info(f"Generating thumbnails for project \"{project.name}\"")
+
+    files = list(project.files)
+    for file in tqdm(files):
+        for size in sizes:
+            resize_file(file,
+                project.root_folder,
+                size.max_width,
+                size.max_height)

+ 2 - 0
requirements.txt

@@ -11,6 +11,8 @@ flask-migrate
 python-socketio
 munch
 scikit-image
+pandas
+tqdm
 
 chainer~=7.8
 chainer-addons~=0.10

+ 1 - 1
settings.json

@@ -1,7 +1,7 @@
 {
   "host": "",
   "port": 5000,
-  "allowedOrigins": ["https://ammod.inf-cv.uni-jena.de", "https://deimos.inf-cv.uni-jena.de"],
+  "allowedOrigins": ["https://ammod.inf-cv.uni-jena.de", "https://deimos.inf-cv.uni-jena.de", "http://localhost:5000"],
   "projects_folder": "projects",
   "database": "db/data.sqlite3",
   "pipeline_cache_time": 120,

+ 5 - 6
webui/src/components/media/cropped-image.vue

@@ -9,12 +9,11 @@
     <div class="label-container">
       <h3> {{ label }} </h3>
 
-      <div  v-if="this.box.origin === 'user'"
-            ref="create_predictions"
-            class="create-predictions-icon"
-            title="create prediction for this image"
-            :class="{active: isPredictionRunning}"
-            @click="predict_cropped_image">
+      <div ref="create_predictions"
+           class="create-predictions-icon"
+           title="create prediction for this image"
+           :class="{active: isPredictionRunning}"
+           @click="predict_cropped_image">
         <img alt="create prediction" src="@/assets/icons/rocket.svg">
       </div>
     </div>

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff