endoreg-db 0.8.4.2__py3-none-any.whl → 0.8.4.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of endoreg-db might be problematic. Click here for more details.
- endoreg_db/data/ai_model_meta/default_multilabel_classification.yaml +23 -1
- endoreg_db/data/setup_config.yaml +38 -0
- endoreg_db/management/commands/load_ai_model_data.py +17 -15
- endoreg_db/management/commands/setup_endoreg_db.py +207 -39
- endoreg_db/management/commands/validate_ai_models.py +124 -0
- endoreg_db/models/medical/hardware/endoscopy_processor.py +10 -1
- endoreg_db/models/metadata/model_meta_logic.py +116 -53
- endoreg_db/services/video_import.py +172 -179
- endoreg_db/utils/setup_config.py +177 -0
- {endoreg_db-0.8.4.2.dist-info → endoreg_db-0.8.4.3.dist-info}/METADATA +1 -1
- {endoreg_db-0.8.4.2.dist-info → endoreg_db-0.8.4.3.dist-info}/RECORD +13 -10
- {endoreg_db-0.8.4.2.dist-info → endoreg_db-0.8.4.3.dist-info}/WHEEL +0 -0
- {endoreg_db-0.8.4.2.dist-info → endoreg_db-0.8.4.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -2,7 +2,7 @@ import shutil
|
|
|
2
2
|
from logging import getLogger
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
from typing import TYPE_CHECKING, Any, Optional, Type
|
|
5
|
-
|
|
5
|
+
|
|
6
6
|
from django.db import transaction
|
|
7
7
|
from huggingface_hub import hf_hub_download
|
|
8
8
|
|
|
@@ -18,18 +18,14 @@ if TYPE_CHECKING:
|
|
|
18
18
|
from .model_meta import ModelMeta # Import ModelMeta for type hinting
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
def get_latest_version_number_logic(
|
|
22
|
-
cls: Type["ModelMeta"], meta_name: str, model_name: str
|
|
23
|
-
) -> int:
|
|
21
|
+
def get_latest_version_number_logic(cls: Type["ModelMeta"], meta_name: str, model_name: str) -> int:
|
|
24
22
|
"""
|
|
25
23
|
Finds the highest numerical version for a given meta_name and model_name.
|
|
26
24
|
Iterates through all versions, attempts to parse them as integers,
|
|
27
25
|
and returns the maximum integer found. If no numeric versions are found,
|
|
28
26
|
returns 0.
|
|
29
27
|
"""
|
|
30
|
-
versions_qs = cls.objects.filter(
|
|
31
|
-
name=meta_name, model__name=model_name
|
|
32
|
-
).values_list("version", flat=True)
|
|
28
|
+
versions_qs = cls.objects.filter(name=meta_name, model__name=model_name).values_list("version", flat=True)
|
|
33
29
|
|
|
34
30
|
max_v = 0
|
|
35
31
|
found_numeric_version = False
|
|
@@ -84,24 +80,17 @@ def create_from_file_logic(
|
|
|
84
80
|
|
|
85
81
|
if requested_version:
|
|
86
82
|
target_version = str(requested_version)
|
|
87
|
-
existing = cls.objects.filter(
|
|
88
|
-
name=meta_name, model=ai_model, version=target_version
|
|
89
|
-
).first()
|
|
83
|
+
existing = cls.objects.filter(name=meta_name, model=ai_model, version=target_version).first()
|
|
90
84
|
if existing and not bump_if_exists:
|
|
91
85
|
raise ValueError(
|
|
92
|
-
f"ModelMeta '{meta_name}' version '{target_version}' for model '{model_name}' "
|
|
93
|
-
f"already exists. Use bump_if_exists=True to increment."
|
|
86
|
+
f"ModelMeta '{meta_name}' version '{target_version}' for model '{model_name}' already exists. Use bump_if_exists=True to increment."
|
|
94
87
|
)
|
|
95
88
|
elif existing and bump_if_exists:
|
|
96
89
|
target_version = str(latest_version_num + 1)
|
|
97
|
-
logger.info(
|
|
98
|
-
f"Bumping version for {meta_name}/{model_name} to {target_version}"
|
|
99
|
-
)
|
|
90
|
+
logger.info(f"Bumping version for {meta_name}/{model_name} to {target_version}")
|
|
100
91
|
else:
|
|
101
92
|
target_version = str(latest_version_num + 1)
|
|
102
|
-
logger.info(
|
|
103
|
-
f"Setting next version for {meta_name}/{model_name} to {target_version}"
|
|
104
|
-
)
|
|
93
|
+
logger.info(f"Setting next version for {meta_name}/{model_name} to {target_version}")
|
|
105
94
|
|
|
106
95
|
# --- Prepare Weights File ---
|
|
107
96
|
source_weights_path = Path(weights_file).resolve()
|
|
@@ -111,10 +100,7 @@ def create_from_file_logic(
|
|
|
111
100
|
# Construct destination path within MEDIA_ROOT/WEIGHTS_DIR
|
|
112
101
|
weights_filename = source_weights_path.name
|
|
113
102
|
# Relative path for the FileField upload_to
|
|
114
|
-
relative_dest_path = (
|
|
115
|
-
Path(WEIGHTS_DIR.relative_to(STORAGE_DIR))
|
|
116
|
-
/ f"{meta_name}_v{target_version}_{weights_filename}"
|
|
117
|
-
)
|
|
103
|
+
relative_dest_path = Path(WEIGHTS_DIR.relative_to(STORAGE_DIR)) / f"{meta_name}_v{target_version}_{weights_filename}"
|
|
118
104
|
# Full path for shutil.copy
|
|
119
105
|
full_dest_path = STORAGE_DIR / relative_dest_path
|
|
120
106
|
|
|
@@ -127,8 +113,6 @@ def create_from_file_logic(
|
|
|
127
113
|
logger.info(f"Copied weights from {source_weights_path} to {full_dest_path}")
|
|
128
114
|
except Exception as e:
|
|
129
115
|
raise IOError(f"Failed to copy weights file: {e}") from e
|
|
130
|
-
|
|
131
|
-
|
|
132
116
|
|
|
133
117
|
# --- Create/Update ModelMeta Instance ---
|
|
134
118
|
defaults = {
|
|
@@ -146,11 +130,6 @@ def create_from_file_logic(
|
|
|
146
130
|
version=target_version,
|
|
147
131
|
defaults=defaults,
|
|
148
132
|
)
|
|
149
|
-
|
|
150
|
-
with open(full_dest_path, "rb") as f:
|
|
151
|
-
model_meta.weights.save(relative_dest_path.name, File(f), save=False)
|
|
152
|
-
model_meta.save()
|
|
153
|
-
|
|
154
133
|
|
|
155
134
|
if created:
|
|
156
135
|
logger.info(f"Created new ModelMeta: {model_meta}")
|
|
@@ -160,8 +139,8 @@ def create_from_file_logic(
|
|
|
160
139
|
# --- Optionally update AiModel's active_meta ---
|
|
161
140
|
# You might want to add logic here to automatically set the newly created/updated
|
|
162
141
|
# meta as the active one for the AiModel, e.g.:
|
|
163
|
-
|
|
164
|
-
|
|
142
|
+
ai_model.active_meta = model_meta
|
|
143
|
+
ai_model.save()
|
|
165
144
|
|
|
166
145
|
return model_meta
|
|
167
146
|
|
|
@@ -240,22 +219,14 @@ def get_model_meta_by_name_version_logic(
|
|
|
240
219
|
try:
|
|
241
220
|
return cls.objects.get(name=meta_name, model=ai_model, version=version)
|
|
242
221
|
except Exception as exc:
|
|
243
|
-
raise cls.DoesNotExist(
|
|
244
|
-
f"ModelMeta '{meta_name}' version '{version}' for model '{model_name}' not found."
|
|
245
|
-
) from exc
|
|
222
|
+
raise cls.DoesNotExist(f"ModelMeta '{meta_name}' version '{version}' for model '{model_name}' not found.") from exc
|
|
246
223
|
else:
|
|
247
224
|
# Get latest version
|
|
248
|
-
latest = (
|
|
249
|
-
cls.objects.filter(name=meta_name, model=ai_model)
|
|
250
|
-
.order_by("-date_created")
|
|
251
|
-
.first()
|
|
252
|
-
)
|
|
225
|
+
latest = cls.objects.filter(name=meta_name, model=ai_model).order_by("-date_created").first()
|
|
253
226
|
if latest:
|
|
254
227
|
return latest
|
|
255
228
|
else:
|
|
256
|
-
raise cls.DoesNotExist(
|
|
257
|
-
f"No ModelMeta found for '{meta_name}' and model '{model_name}'."
|
|
258
|
-
)
|
|
229
|
+
raise cls.DoesNotExist(f"No ModelMeta found for '{meta_name}' and model '{model_name}'.")
|
|
259
230
|
|
|
260
231
|
|
|
261
232
|
import re
|
|
@@ -273,9 +244,7 @@ def infer_default_model_meta_from_hf(model_id: str) -> dict[str, Any]:
|
|
|
273
244
|
"""
|
|
274
245
|
|
|
275
246
|
if not (info := model_info(model_id)):
|
|
276
|
-
logger.info(
|
|
277
|
-
f"Could not retrieve model info for {model_id}, using ColoReg segmentation defaults."
|
|
278
|
-
)
|
|
247
|
+
logger.info(f"Could not retrieve model info for {model_id}, using ColoReg segmentation defaults.")
|
|
279
248
|
return {
|
|
280
249
|
"name": "wg-lux/colo_segmentation_RegNetX800MF_base",
|
|
281
250
|
"activation": "sigmoid",
|
|
@@ -324,9 +293,7 @@ def infer_default_model_meta_from_hf(model_id: str) -> dict[str, Any]:
|
|
|
324
293
|
}
|
|
325
294
|
|
|
326
295
|
|
|
327
|
-
def setup_default_from_huggingface_logic(
|
|
328
|
-
cls, model_id: str, labelset_name: str | None = None
|
|
329
|
-
):
|
|
296
|
+
def setup_default_from_huggingface_logic(cls, model_id: str, labelset_name: str | None = None):
|
|
330
297
|
"""
|
|
331
298
|
Downloads model weights from Hugging Face and auto-fills ModelMeta fields.
|
|
332
299
|
"""
|
|
@@ -340,11 +307,11 @@ def setup_default_from_huggingface_logic(
|
|
|
340
307
|
)
|
|
341
308
|
|
|
342
309
|
ai_model, _ = AiModel.objects.get_or_create(name=meta["name"])
|
|
343
|
-
labelset = (
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
310
|
+
labelset = LabelSet.objects.first() if not labelset_name else LabelSet.objects.get(name=labelset_name)
|
|
311
|
+
model_meta = ModelMeta.objects.filter(name=meta["name"], model=ai_model).first()
|
|
312
|
+
if model_meta:
|
|
313
|
+
logger.info(f"ModelMeta {meta['name']} for model {ai_model.name} already exists. Skipping creation.")
|
|
314
|
+
return model_meta
|
|
348
315
|
|
|
349
316
|
return create_from_file_logic(
|
|
350
317
|
cls,
|
|
@@ -359,3 +326,99 @@ def setup_default_from_huggingface_logic(
|
|
|
359
326
|
size_y=meta["size_y"],
|
|
360
327
|
description=meta["description"],
|
|
361
328
|
)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def validate_and_fix_ai_model_metadata_logic():
|
|
332
|
+
"""
|
|
333
|
+
Validates that all AI models have proper active metadata and fixes any issues.
|
|
334
|
+
This prevents the "No model metadata found for this model" error.
|
|
335
|
+
|
|
336
|
+
Returns:
|
|
337
|
+
dict: Summary of fixes applied
|
|
338
|
+
"""
|
|
339
|
+
from ..administration.ai.ai_model import AiModel
|
|
340
|
+
from ..label.label_set import LabelSet
|
|
341
|
+
|
|
342
|
+
summary = {"models_checked": 0, "models_fixed": 0, "metadata_created": 0, "active_meta_set": 0, "errors": []}
|
|
343
|
+
|
|
344
|
+
try:
|
|
345
|
+
all_models = AiModel.objects.all()
|
|
346
|
+
summary["models_checked"] = all_models.count()
|
|
347
|
+
|
|
348
|
+
for model in all_models:
|
|
349
|
+
logger.info(f"Validating model: {model.name}")
|
|
350
|
+
|
|
351
|
+
# Check if model has metadata versions
|
|
352
|
+
metadata_count = model.metadata_versions.count()
|
|
353
|
+
|
|
354
|
+
if metadata_count == 0:
|
|
355
|
+
# Create metadata for models that don't have any
|
|
356
|
+
logger.info(f"Creating metadata for {model.name}")
|
|
357
|
+
|
|
358
|
+
# Use existing labelset or create default
|
|
359
|
+
labelset = LabelSet.objects.first()
|
|
360
|
+
if not labelset:
|
|
361
|
+
labelset = LabelSet.objects.create(name="default_colonoscopy_labels", description="Default colonoscopy classification labels")
|
|
362
|
+
|
|
363
|
+
# Import here to avoid circular imports
|
|
364
|
+
from .model_meta import ModelMeta
|
|
365
|
+
|
|
366
|
+
# Create basic metadata
|
|
367
|
+
meta = ModelMeta.objects.create(
|
|
368
|
+
name=model.name,
|
|
369
|
+
version="1.0",
|
|
370
|
+
model=model,
|
|
371
|
+
labelset=labelset,
|
|
372
|
+
activation="sigmoid" if "classification" in model.name else "sigmoid",
|
|
373
|
+
mean="0.485,0.456,0.406", # ImageNet defaults
|
|
374
|
+
std="0.229,0.224,0.225", # ImageNet defaults
|
|
375
|
+
size_x=224,
|
|
376
|
+
size_y=224,
|
|
377
|
+
axes="CHW",
|
|
378
|
+
batchsize=32,
|
|
379
|
+
num_workers=4,
|
|
380
|
+
description=f"Auto-generated metadata for {model.name}",
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
model.active_meta = meta
|
|
384
|
+
model.save()
|
|
385
|
+
summary["models_fixed"] += 1
|
|
386
|
+
summary["metadata_created"] += 1
|
|
387
|
+
logger.info(f"Created and set metadata for {model.name}")
|
|
388
|
+
|
|
389
|
+
elif not model.active_meta:
|
|
390
|
+
# Model has metadata but no active meta set
|
|
391
|
+
first_meta = model.metadata_versions.first()
|
|
392
|
+
if first_meta:
|
|
393
|
+
logger.info(f"Setting active metadata for {model.name}")
|
|
394
|
+
model.active_meta = first_meta
|
|
395
|
+
model.save()
|
|
396
|
+
summary["models_fixed"] += 1
|
|
397
|
+
summary["active_meta_set"] += 1
|
|
398
|
+
logger.info(f"Set active metadata: {first_meta.name} v{first_meta.version}")
|
|
399
|
+
else:
|
|
400
|
+
error_msg = f"No metadata versions available for {model.name}"
|
|
401
|
+
logger.warning(error_msg)
|
|
402
|
+
summary["errors"].append(error_msg)
|
|
403
|
+
|
|
404
|
+
else:
|
|
405
|
+
logger.info(f"Model {model.name} has valid active metadata: {model.active_meta}")
|
|
406
|
+
|
|
407
|
+
# Verify all models can get latest version
|
|
408
|
+
logger.info("Testing model metadata access...")
|
|
409
|
+
for model in all_models:
|
|
410
|
+
try:
|
|
411
|
+
latest = model.get_latest_version()
|
|
412
|
+
logger.info(f"✅ {model.name}: {latest}")
|
|
413
|
+
except Exception as e:
|
|
414
|
+
error_msg = f"Model {model.name} metadata test failed: {e}"
|
|
415
|
+
logger.error(error_msg)
|
|
416
|
+
summary["errors"].append(error_msg)
|
|
417
|
+
|
|
418
|
+
return summary
|
|
419
|
+
|
|
420
|
+
except Exception as e:
|
|
421
|
+
error_msg = f"Validation failed: {e}"
|
|
422
|
+
logger.error(error_msg)
|
|
423
|
+
summary["errors"].append(error_msg)
|
|
424
|
+
return summary
|