endoreg-db 0.8.4.1__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.

@@ -1,5 +1,27 @@
1
+ # Model metadata configuration with setup hints
1
2
  - model: endoreg_db.model_meta
2
3
  fields:
3
4
  name: "image_multilabel_classification_colonoscopy_default"
4
- version: 1
5
+ version: "1"
6
+ model: "image_multilabel_classification_colonoscopy_default"
5
7
  labelset: "multilabel_classification_colonoscopy_default"
8
+ activation: "sigmoid"
9
+ mean: "0.485,0.456,0.406" # ImageNet defaults
10
+ std: "0.229,0.224,0.225" # ImageNet defaults
11
+ size_x: 224
12
+ size_y: 224
13
+ axes: "CHW"
14
+ batchsize: 32
15
+ num_workers: 4
16
+ description: "Default colonoscopy image multilabel classification model from YAML"
17
+ # Note: weights field intentionally left empty - will be set by setup command or model meta logic
18
+
19
+ # Setup configuration hints for this model
20
+ setup_config:
21
+ is_primary_model: true # Mark this as the primary model for setup
22
+ weight_filenames: # Specific weight file patterns for this model
23
+ - "colo_segmentation_RegNetX800MF_6.ckpt"
24
+ - "image_multilabel_classification_colonoscopy_default_v1_*.ckpt"
25
+ huggingface_fallback: # HF configuration for this model
26
+ repo_id: "wg-lux/colo_segmentation_RegNetX800MF_base"
27
+ filename: "colo_segmentation_RegNetX800MF_base.ckpt"
@@ -0,0 +1,38 @@
1
+ # EndoReg DB Setup Configuration
2
+ # This file defines which models should be used for setup and their fallback configurations
3
+
4
+ default_models:
5
+ primary_classification_model: "image_multilabel_classification_colonoscopy_default"
6
+ primary_labelset: "multilabel_classification_colonoscopy_default"
7
+
8
+ # HuggingFace fallback configuration
9
+ huggingface_fallback:
10
+ enabled: true
11
+ repo_id: "wg-lux/colo_segmentation_RegNetX800MF_base"
12
+ filename: "colo_segmentation_RegNetX800MF_base.ckpt"
13
+ labelset_name: "multilabel_classification_colonoscopy_default"
14
+
15
+ # Local weights search patterns
16
+ weights_search_patterns:
17
+ # Primary model weight filename patterns (supports wildcards)
18
+ - "colo_segmentation_RegNetX800MF_*.ckpt"
19
+ - "image_multilabel_classification_colonoscopy_default_*.ckpt"
20
+ - "*_colonoscopy_*.ckpt"
21
+
22
+ # Search directories for local weights (relative to project root)
23
+ weights_search_dirs:
24
+ - "tests/assets"
25
+ - "assets"
26
+ - "data/storage/model_weights"
27
+ - "${STORAGE_DIR}/model_weights" # Environment variable substitution
28
+
29
+ # Auto-generation defaults when creating metadata
30
+ auto_generation_defaults:
31
+ activation: "sigmoid"
32
+ mean: "0.485,0.456,0.406" # ImageNet defaults
33
+ std: "0.229,0.224,0.225" # ImageNet defaults
34
+ size_x: 224
35
+ size_y: 224
36
+ axes: "CHW"
37
+ batchsize: 32
38
+ num_workers: 4
@@ -1,25 +1,27 @@
1
1
  from django.core.management.base import BaseCommand
2
+
3
+ from ...data import (
4
+ AI_MODEL_DATA_DIR,
5
+ AI_MODEL_META_DATA_DIR, # Add this import
6
+ MODEL_TYPE_DATA_DIR,
7
+ VIDEO_SEGMENTATION_LABEL_DATA_DIR,
8
+ VIDEO_SEGMENTATION_LABELSET_DATA_DIR,
9
+ )
2
10
  from ...models import (
11
+ AiModel,
12
+ ModelMeta, # Add ModelMeta back to imports
3
13
  ModelType,
4
14
  VideoSegmentationLabel,
5
15
  VideoSegmentationLabelSet,
6
- AiModel,
7
16
  )
8
17
  from ...utils import load_model_data_from_yaml
9
- from ...data import (
10
- MODEL_TYPE_DATA_DIR,
11
- VIDEO_SEGMENTATION_LABEL_DATA_DIR,
12
- VIDEO_SEGMENTATION_LABELSET_DATA_DIR,
13
- AI_MODEL_DATA_DIR,
14
- )
15
-
16
18
 
17
19
  IMPORT_MODELS = [ # string as model key, serves as key in IMPORT_METADATA
18
20
  ModelType.__name__,
19
- # ModelMeta.__name__,
20
21
  VideoSegmentationLabel.__name__,
21
22
  VideoSegmentationLabelSet.__name__,
22
23
  AiModel.__name__,
24
+ ModelMeta.__name__, # Re-enable ModelMeta loading
23
25
  ]
24
26
 
25
27
  IMPORT_METADATA = {
@@ -29,12 +31,12 @@ IMPORT_METADATA = {
29
31
  "foreign_keys": [], # e.g. ["interventions"]
30
32
  "foreign_key_models": [], # e.g. [Intervention]
31
33
  },
32
- # ModelMeta.__name__: {
33
- # "dir": AI_MODEL_META_DATA_DIR, # e.g. "intervention_types"
34
- # "model": ModelMeta, # e.g. InterventionType
35
- # "foreign_keys": ["labelset", "type"], # e.g. ["interventions"]
36
- # "foreign_key_models": [LabelSet, ModelType], # e.g. [Intervention]
37
- # },
34
+ ModelMeta.__name__: {
35
+ "dir": AI_MODEL_META_DATA_DIR, # e.g. "ai_model_meta"
36
+ "model": ModelMeta, # e.g. ModelMeta
37
+ "foreign_keys": ["labelset", "model"], # Foreign key relationships
38
+ "foreign_key_models": [VideoSegmentationLabelSet, AiModel], # Actual model classes
39
+ },
38
40
  VideoSegmentationLabel.__name__: {
39
41
  "dir": VIDEO_SEGMENTATION_LABEL_DATA_DIR, # e.g. "interventions"
40
42
  "model": VideoSegmentationLabel, # e.g. Intervention
@@ -3,9 +3,6 @@ Django management command to perform complete setup for EndoReg DB when used as
3
3
  This command ensures all necessary data and configurations are initialized.
4
4
  """
5
5
 
6
- import os
7
- from pathlib import Path
8
-
9
6
  from django.core.management import call_command
10
7
  from django.core.management.base import BaseCommand
11
8
 
@@ -17,9 +14,10 @@ class Command(BaseCommand):
17
14
  Complete setup for EndoReg DB when used as an embedded app.
18
15
  This command performs all necessary initialization steps:
19
16
  1. Loads base database data
20
- 2. Sets up AI models and labels
21
- 3. Creates cache table
22
- 4. Initializes model metadata
17
+ 2. Sets up caching (if using db cache)
18
+ 3. Loads default models from setup configuration file (setup_config.yaml)
19
+ 4. Loads models according to fallback chain (Local Files -> HuggingFace -> graceful failure)
20
+ 5. Initializes model metadata
23
21
  """
24
22
 
25
23
  def add_arguments(self, parser):
@@ -33,13 +31,22 @@ class Command(BaseCommand):
33
31
  action="store_true",
34
32
  help="Force recreation of AI model metadata even if it exists",
35
33
  )
34
+ parser.add_argument(
35
+ "--yaml-only",
36
+ action="store_true",
37
+ help="Only use YAML-defined models, don't auto-generate missing metadata",
38
+ )
36
39
 
37
40
  def handle(self, *args, **options):
38
41
  skip_ai = options.get("skip_ai_setup", False)
39
42
  force_recreate = options.get("force_recreate", False)
43
+ yaml_only = options.get("yaml_only", False)
40
44
 
41
45
  self.stdout.write(self.style.SUCCESS("🚀 Starting EndoReg DB embedded app setup..."))
42
46
 
47
+ if yaml_only:
48
+ self.stdout.write(self.style.WARNING("📋 YAML-only mode: Will not auto-generate missing metadata"))
49
+
43
50
  # Step 1: Load base database data
44
51
  self.stdout.write("\n📊 Step 1: Loading base database data...")
45
52
  try:
@@ -49,7 +56,7 @@ class Command(BaseCommand):
49
56
  self.stdout.write(self.style.ERROR(f"❌ Failed to load base data: {e}"))
50
57
  return
51
58
 
52
- # Step 2: Create cache table (only if using database caching)
59
+ # Step 2: Create cache table (only if using database caching)
53
60
  self.stdout.write("\n💾 Step 2: Setting up caching...")
54
61
  from django.conf import settings
55
62
 
@@ -89,10 +96,16 @@ class Command(BaseCommand):
89
96
  # Step 5: Create model metadata
90
97
  self.stdout.write("\n📋 Step 5: Creating AI model metadata...")
91
98
  try:
99
+ # Load setup configuration
100
+ from endoreg_db.utils.setup_config import setup_config
101
+
102
+ # Get primary model from configuration
103
+ default_model_name = setup_config.get_primary_model_name()
104
+ primary_labelset = setup_config.get_primary_labelset_name()
105
+
92
106
  # Check if model metadata already exists
93
107
  from endoreg_db.models import AiModel
94
108
 
95
- default_model_name = "image_multilabel_classification_colonoscopy_default"
96
109
  ai_model = AiModel.objects.filter(name=default_model_name).first()
97
110
 
98
111
  if not ai_model:
@@ -103,14 +116,14 @@ class Command(BaseCommand):
103
116
  if existing_meta and not force_recreate:
104
117
  self.stdout.write(self.style.SUCCESS("✅ Model metadata already exists (use --force-recreate to recreate)"))
105
118
  else:
106
- # Try to create model metadata
119
+ # Try to create model metadata using configurable approach
107
120
  model_path = self._find_model_weights_file()
108
121
  if model_path:
109
122
  call_command(
110
123
  "create_multilabel_model_meta",
111
124
  model_name=default_model_name,
112
125
  model_meta_version=1,
113
- image_classification_labelset_name="multilabel_classification_colonoscopy_default",
126
+ image_classification_labelset_name=primary_labelset,
114
127
  model_path=str(model_path),
115
128
  )
116
129
  self.stdout.write(self.style.SUCCESS("✅ AI model metadata created successfully"))
@@ -124,7 +137,7 @@ class Command(BaseCommand):
124
137
  # Step 5.5: Validate and fix AI model active metadata
125
138
  self.stdout.write("\n🔧 Step 5.5: Validating AI model active metadata...")
126
139
  try:
127
- self._validate_and_fix_ai_model_metadata()
140
+ self._validate_and_fix_ai_model_metadata(yaml_only)
128
141
  self.stdout.write(self.style.SUCCESS("✅ AI model metadata validation completed"))
129
142
  except Exception as e:
130
143
  self.stdout.write(self.style.ERROR(f"❌ Failed to validate AI model metadata: {e}"))
@@ -146,33 +159,37 @@ class Command(BaseCommand):
146
159
  self.stdout.write("3. Start development server: python manage.py runserver")
147
160
 
148
161
  def _find_model_weights_file(self):
149
- """Find the model weights file in various possible locations."""
150
- # Check common locations for model weights
151
-
152
- if not ModelMeta.objects.exists():
153
- print("📦 No model metadata found creating from Hugging Face...")
154
- ModelMeta.setup_default_from_huggingface(
155
- "wg-lux/colo_segmentation_RegNetX800MF_base", labelset_name="multilabel_classification_colonoscopy_default"
156
- )
157
- print("✅ Default ModelMeta created.")
158
- possible_paths = [
159
- # Test assets (for development)
160
- Path("tests/assets/colo_segmentation_RegNetX800MF_6.ckpt"),
161
- # Project root assets
162
- Path("assets/colo_segmentation_RegNetX800MF_6.ckpt"),
163
- # Storage directory
164
- Path("data/storage/model_weights/colo_segmentation_RegNetX800MF_6.ckpt"),
165
- # Absolute paths based on environment
166
- Path(os.getenv("STORAGE_DIR", "storage")) / "model_weights" / "colo_segmentation_RegNetX800MF_6.ckpt",
167
- ]
168
-
169
- for path in possible_paths:
170
- if path.exists():
171
- self.stdout.write(f"Found model weights at: {path}")
172
- return path
162
+ """Find the model weights file using configurable search patterns and directories."""
163
+ # Load setup configuration
164
+ from endoreg_db.utils.setup_config import setup_config
165
+
166
+ # First try to find weights using configured patterns
167
+ found_files = setup_config.find_model_weights_files()
168
+ if found_files:
169
+ self.stdout.write(f"Found model weights at: {found_files[0]}")
170
+ return found_files[0]
171
+
172
+ # If no local weights found and HuggingFace fallback is enabled
173
+ hf_config = setup_config.get_huggingface_config()
174
+ if hf_config.get("enabled", True):
175
+ self.stdout.write("📦 No local model weights found — attempting HuggingFace download...")
176
+ try:
177
+ if not ModelMeta.objects.exists():
178
+ ModelMeta.setup_default_from_huggingface(
179
+ hf_config.get("repo_id", "wg-lux/colo_segmentation_RegNetX800MF_base"),
180
+ labelset_name=hf_config.get("labelset_name", "multilabel_classification_colonoscopy_default"),
181
+ )
182
+ self.stdout.write("✅ Default ModelMeta created from HuggingFace.")
183
+
184
+ # Try to find the downloaded weights
185
+ found_files = setup_config.find_model_weights_files()
186
+ if found_files:
187
+ return found_files[0]
173
188
 
174
- self.stdout.write("Model weights file not found in standard locations")
189
+ except Exception as e:
190
+ self.stdout.write(f"⚠️ HuggingFace download failed: {e}")
175
191
 
192
+ self.stdout.write("Model weights file not found in configured locations")
176
193
  return None
177
194
 
178
195
  def _verify_setup(self):
@@ -214,16 +231,24 @@ class Command(BaseCommand):
214
231
 
215
232
  self.stdout.write("Setup verification passed")
216
233
 
217
- def _validate_and_fix_ai_model_metadata(self):
234
+ def _validate_and_fix_ai_model_metadata(self, yaml_only=False):
218
235
  """
219
236
  Validate that all AI models have proper active metadata and fix if necessary.
220
237
  This addresses the "No model metadata found for this model" error.
238
+
239
+ Args:
240
+ yaml_only (bool): If True, only set active metadata but don't create new metadata
221
241
  """
222
242
  from endoreg_db.models import AiModel, LabelSet, ModelMeta
243
+ from endoreg_db.utils.setup_config import setup_config
223
244
 
224
245
  all_models = AiModel.objects.all()
225
246
  fixed_count = 0
226
247
 
248
+ # Get configurable defaults
249
+ defaults = setup_config.get_auto_generation_defaults()
250
+ primary_labelset_name = setup_config.get_primary_labelset_name()
251
+
227
252
  for model in all_models:
228
253
  self.stdout.write(f"Checking model: {model.name}")
229
254
 
@@ -232,28 +257,59 @@ class Command(BaseCommand):
232
257
  self.stdout.write(f" Metadata versions: {metadata_count}")
233
258
 
234
259
  if metadata_count == 0:
260
+ if yaml_only:
261
+ self.stdout.write(f" ⚠️ YAML-only mode: Skipping auto-generation for {model.name}")
262
+ continue
263
+
235
264
  # Create metadata for models that don't have any
236
265
  self.stdout.write(f" Creating metadata for {model.name}...")
237
266
 
238
- # Use existing labelset or create default
239
- labelset = LabelSet.objects.first()
240
- if not labelset:
241
- labelset = LabelSet.objects.create(name="default_colonoscopy_labels", description="Default colonoscopy classification labels")
242
-
243
- # Create basic metadata
267
+ # Use configured labelset or create default
268
+ labelset = None
269
+ try:
270
+ labelset = LabelSet.objects.get(name=primary_labelset_name)
271
+ except LabelSet.DoesNotExist:
272
+ labelset = LabelSet.objects.first()
273
+ if not labelset:
274
+ labelset = LabelSet.objects.create(name="default_colonoscopy_labels", description="Default colonoscopy classification labels")
275
+
276
+ # Create basic metadata WITH weights if available
277
+ weights_file = self._find_model_weights_file()
278
+ weights_path = ""
279
+ if weights_file:
280
+ # If we have weights, set up the relative path
281
+ from pathlib import Path
282
+
283
+ from endoreg_db.utils.paths import STORAGE_DIR
284
+
285
+ try:
286
+ weights_path = str(Path(weights_file).relative_to(STORAGE_DIR))
287
+ except ValueError:
288
+ # If file is not in storage dir, copy it there
289
+ import shutil
290
+
291
+ weights_dir = STORAGE_DIR / "model_weights"
292
+ weights_dir.mkdir(parents=True, exist_ok=True)
293
+ dest_path = weights_dir / Path(weights_file).name
294
+ shutil.copy2(weights_file, dest_path)
295
+ weights_path = str(dest_path.relative_to(STORAGE_DIR))
296
+ self.stdout.write(f" Copied weights to: {dest_path}")
297
+
298
+ # Create basic metadata using configurable defaults
244
299
  meta = ModelMeta.objects.create(
245
300
  name=model.name,
246
301
  version="1.0",
247
302
  model=model,
248
303
  labelset=labelset,
249
- activation="sigmoid" if "classification" in model.name else "sigmoid",
250
- mean="0.485,0.456,0.406", # ImageNet defaults
251
- std="0.229,0.224,0.225", # ImageNet defaults
252
- size_x=224,
253
- size_y=224,
254
- axes="CHW",
255
- batchsize=32,
256
- num_workers=4,
304
+ weights=weights_path, # Set weights if available
305
+ activation=defaults.get("activation", "sigmoid"),
306
+ mean=defaults.get("mean", "0.485,0.456,0.406"),
307
+ std=defaults.get("std", "0.229,0.224,0.225"),
308
+ size_x=defaults.get("size_x", 224),
309
+ size_y=defaults.get("size_y", 224),
310
+ axes=defaults.get("axes", "CHW"),
311
+ batchsize=defaults.get("batchsize", 32),
312
+ num_workers=defaults.get("num_workers", 4),
257
313
  description=f"Auto-generated metadata for {model.name}",
258
314
  )
259
315
 
@@ -267,6 +323,34 @@ class Command(BaseCommand):
267
323
  first_meta = model.metadata_versions.first()
268
324
  if first_meta:
269
325
  self.stdout.write(f" Setting active metadata for {model.name}...")
326
+
327
+ # Check if the metadata has weights - if not, try to assign them
328
+ if not first_meta.weights:
329
+ self.stdout.write(" Metadata exists but no weights assigned, attempting to add weights...")
330
+ weights_file = self._find_model_weights_file()
331
+ if weights_file:
332
+ from pathlib import Path
333
+
334
+ from endoreg_db.utils.paths import STORAGE_DIR
335
+
336
+ try:
337
+ weights_path = str(Path(weights_file).relative_to(STORAGE_DIR))
338
+ except ValueError:
339
+ # Copy weights to storage if not already there
340
+ import shutil
341
+
342
+ weights_dir = STORAGE_DIR / "model_weights"
343
+ weights_dir.mkdir(parents=True, exist_ok=True)
344
+ dest_path = weights_dir / Path(weights_file).name
345
+ shutil.copy2(weights_file, dest_path)
346
+ weights_path = str(dest_path.relative_to(STORAGE_DIR))
347
+ self.stdout.write(f" Copied weights to: {dest_path}")
348
+
349
+ # Assign the relative path to the FileField
350
+ first_meta.weights.name = weights_path
351
+ first_meta.save(update_fields=["weights"])
352
+ self.stdout.write(f" Added weights to existing metadata: {weights_path}")
353
+
270
354
  model.active_meta = first_meta
271
355
  model.save()
272
356
  fixed_count += 1
@@ -160,7 +160,7 @@ class EndoscopyProcessor(models.Model):
160
160
  "height": self.endoscope_sn_height,
161
161
  }
162
162
 
163
- def get_rois(self) -> dict[ str, dict[str, int | None] | None]:
163
+ def get_rois(self) -> dict[str, dict[str, int | None] | None]:
164
164
  return {
165
165
  "endoscope_image": self.get_roi_endoscope_image(),
166
166
  "examination_date": self.get_roi_examination_date(),
@@ -171,3 +171,12 @@ class EndoscopyProcessor(models.Model):
171
171
  "endoscope_type": self.get_roi_endoscope_type(),
172
172
  "endoscope_sn": self.get_roi_endoscopy_sn(),
173
173
  }
174
+
175
+ def get_sensitive_rois(self) -> dict[str, dict[str, int | None] | None]:
176
+ return {
177
+ "examination_date": self.get_roi_examination_date(),
178
+ "examination_time": self.get_roi_examination_time(),
179
+ "patient_first_name": self.get_roi_patient_first_name(),
180
+ "patient_last_name": self.get_roi_patient_last_name(),
181
+ "patient_dob": self.get_roi_patient_dob(),
182
+ }
@@ -139,8 +139,8 @@ def create_from_file_logic(
139
139
  # --- Optionally update AiModel's active_meta ---
140
140
  # You might want to add logic here to automatically set the newly created/updated
141
141
  # meta as the active one for the AiModel, e.g.:
142
- # ai_model.active_meta = model_meta
143
- # ai_model.save()
142
+ ai_model.active_meta = model_meta
143
+ ai_model.save()
144
144
 
145
145
  return model_meta
146
146
 
@@ -308,6 +308,10 @@ def setup_default_from_huggingface_logic(cls, model_id: str, labelset_name: str
308
308
 
309
309
  ai_model, _ = AiModel.objects.get_or_create(name=meta["name"])
310
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
311
315
 
312
316
  return create_from_file_logic(
313
317
  cls,