endoreg-db 0.8.3.7__py3-none-any.whl → 0.8.6.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.
- endoreg_db/data/ai_model_meta/default_multilabel_classification.yaml +23 -1
- endoreg_db/data/setup_config.yaml +38 -0
- endoreg_db/management/commands/create_model_meta_from_huggingface.py +19 -5
- endoreg_db/management/commands/load_ai_model_data.py +18 -15
- endoreg_db/management/commands/setup_endoreg_db.py +218 -33
- endoreg_db/models/media/pdf/raw_pdf.py +241 -97
- endoreg_db/models/media/video/pipe_1.py +30 -33
- endoreg_db/models/media/video/video_file.py +300 -187
- endoreg_db/models/medical/hardware/endoscopy_processor.py +10 -1
- endoreg_db/models/metadata/model_meta_logic.py +63 -43
- endoreg_db/models/metadata/sensitive_meta_logic.py +251 -25
- endoreg_db/serializers/__init__.py +26 -55
- endoreg_db/serializers/misc/__init__.py +1 -1
- endoreg_db/serializers/misc/file_overview.py +65 -35
- endoreg_db/serializers/misc/{vop_patient_data.py → sensitive_patient_data.py} +1 -1
- endoreg_db/serializers/video_examination.py +198 -0
- endoreg_db/services/lookup_service.py +228 -58
- endoreg_db/services/lookup_store.py +174 -30
- endoreg_db/services/pdf_import.py +585 -282
- endoreg_db/services/video_import.py +485 -242
- endoreg_db/urls/__init__.py +36 -23
- endoreg_db/urls/label_video_segments.py +2 -0
- endoreg_db/urls/media.py +3 -2
- endoreg_db/utils/setup_config.py +177 -0
- endoreg_db/views/__init__.py +5 -3
- endoreg_db/views/media/pdf_media.py +3 -1
- endoreg_db/views/media/video_media.py +1 -1
- endoreg_db/views/media/video_segments.py +187 -259
- endoreg_db/views/pdf/__init__.py +5 -8
- endoreg_db/views/pdf/pdf_stream.py +187 -0
- endoreg_db/views/pdf/reimport.py +110 -94
- endoreg_db/views/requirement/lookup.py +171 -287
- endoreg_db/views/video/__init__.py +0 -2
- endoreg_db/views/video/video_examination_viewset.py +202 -289
- {endoreg_db-0.8.3.7.dist-info → endoreg_db-0.8.6.3.dist-info}/METADATA +1 -2
- {endoreg_db-0.8.3.7.dist-info → endoreg_db-0.8.6.3.dist-info}/RECORD +38 -37
- endoreg_db/views/pdf/pdf_media.py +0 -239
- endoreg_db/views/pdf/pdf_stream_views.py +0 -127
- endoreg_db/views/video/video_media.py +0 -158
- {endoreg_db-0.8.3.7.dist-info → endoreg_db-0.8.6.3.dist-info}/WHEEL +0 -0
- {endoreg_db-0.8.3.7.dist-info → endoreg_db-0.8.6.3.dist-info}/licenses/LICENSE +0 -0
endoreg_db/urls/__init__.py
CHANGED
|
@@ -1,36 +1,39 @@
|
|
|
1
|
-
from django.urls import path, include
|
|
2
1
|
from django.conf import settings
|
|
3
2
|
from django.conf.urls.static import static
|
|
3
|
+
from django.urls import include, path
|
|
4
4
|
from rest_framework.routers import DefaultRouter
|
|
5
5
|
|
|
6
|
-
# Phase 1.2: Media Management URLs ✅ IMPLEMENTED
|
|
7
|
-
from .media import urlpatterns as media_url_patterns
|
|
8
|
-
|
|
9
6
|
from endoreg_db.views import (
|
|
10
|
-
VideoViewSet,
|
|
11
7
|
ExaminationViewSet,
|
|
12
|
-
|
|
8
|
+
FindingClassificationViewSet,
|
|
13
9
|
FindingViewSet,
|
|
14
|
-
|
|
10
|
+
PatientExaminationViewSet,
|
|
15
11
|
PatientFindingViewSet,
|
|
16
|
-
|
|
12
|
+
VideoExaminationViewSet,
|
|
13
|
+
VideoViewSet,
|
|
17
14
|
)
|
|
18
15
|
|
|
19
16
|
from .anonymization import url_patterns as anonymization_url_patterns
|
|
20
|
-
from .classification import url_patterns as classification_url_patterns
|
|
21
17
|
from .auth import urlpatterns as auth_url_patterns
|
|
18
|
+
from .classification import url_patterns as classification_url_patterns
|
|
22
19
|
from .examination import urlpatterns as examination_url_patterns
|
|
23
20
|
from .files import urlpatterns as files_url_patterns
|
|
21
|
+
from .label_video_segment_validate import (
|
|
22
|
+
url_patterns as label_video_segment_validate_url_patterns,
|
|
23
|
+
)
|
|
24
24
|
from .label_video_segments import url_patterns as label_video_segments_url_patterns
|
|
25
|
-
|
|
25
|
+
|
|
26
|
+
# Phase 1.2: Media Management URLs ✅ IMPLEMENTED
|
|
27
|
+
from .media import urlpatterns as media_url_patterns
|
|
28
|
+
from .patient import urlpatterns as patient_url_patterns
|
|
29
|
+
|
|
26
30
|
# TODO Phase 1.2: Implement VideoMediaView and PDFMediaView before enabling
|
|
27
31
|
# from .media import urlpatterns as media_url_patterns
|
|
28
32
|
from .report import url_patterns as report_url_patterns
|
|
29
|
-
from .upload import urlpatterns as upload_url_patterns
|
|
30
|
-
from .video import url_patterns as video_url_patterns
|
|
31
33
|
from .requirements import urlpatterns as requirements_url_patterns
|
|
32
|
-
from .patient import urlpatterns as patient_url_patterns
|
|
33
34
|
from .stats import url_patterns as stats_url_patterns
|
|
35
|
+
from .upload import urlpatterns as upload_url_patterns
|
|
36
|
+
from .video import url_patterns as video_url_patterns
|
|
34
37
|
|
|
35
38
|
api_urls = []
|
|
36
39
|
api_urls += classification_url_patterns
|
|
@@ -50,21 +53,31 @@ api_urls += patient_url_patterns
|
|
|
50
53
|
api_urls += stats_url_patterns
|
|
51
54
|
|
|
52
55
|
router = DefaultRouter()
|
|
53
|
-
router.register(r
|
|
54
|
-
router.register(r
|
|
55
|
-
router.register(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
router.register(r
|
|
59
|
-
router.register(r
|
|
56
|
+
router.register(r"videos", VideoViewSet, basename="videos")
|
|
57
|
+
router.register(r"examinations", ExaminationViewSet)
|
|
58
|
+
router.register(
|
|
59
|
+
r"video-examinations", VideoExaminationViewSet, basename="video-examinations"
|
|
60
|
+
)
|
|
61
|
+
router.register(r"findings", FindingViewSet)
|
|
62
|
+
router.register(r"classifications", FindingClassificationViewSet)
|
|
63
|
+
router.register(r"patient-findings", PatientFindingViewSet)
|
|
64
|
+
router.register(r"patient-examinations", PatientExaminationViewSet)
|
|
65
|
+
|
|
66
|
+
# Additional custom video examination routes
|
|
67
|
+
# Frontend expects: GET /api/video/{id}/examinations/
|
|
68
|
+
video_examinations_list = VideoExaminationViewSet.as_view({"get": "by_video"})
|
|
60
69
|
|
|
61
70
|
# Export raw API urlpatterns (no prefix). The project-level endoreg_db/urls.py mounts these under /api/.
|
|
62
71
|
urlpatterns = [
|
|
63
|
-
path(
|
|
64
|
-
|
|
72
|
+
path(
|
|
73
|
+
"video/<int:video_id>/examinations/",
|
|
74
|
+
video_examinations_list,
|
|
75
|
+
name="video-examinations-by-video",
|
|
76
|
+
),
|
|
77
|
+
path("", include(api_urls)), # Specific routes first
|
|
78
|
+
path("", include(router.urls)), # Generic router routes second
|
|
65
79
|
]
|
|
66
80
|
|
|
67
81
|
if settings.DEBUG:
|
|
68
82
|
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
|
69
83
|
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
|
70
|
-
|
endoreg_db/urls/media.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from PIL.PdfParser import PdfStream
|
|
1
2
|
from django.urls import path
|
|
2
3
|
|
|
3
4
|
from endoreg_db.views.media import (
|
|
@@ -22,6 +23,7 @@ from endoreg_db.views import (
|
|
|
22
23
|
VideoStreamView,
|
|
23
24
|
)
|
|
24
25
|
from endoreg_db.views.pdf.reimport import PdfReimportView
|
|
26
|
+
from endoreg_db.views.pdf.pdf_stream import PdfStreamView
|
|
25
27
|
from endoreg_db.views.video.reimport import VideoReimportView
|
|
26
28
|
from endoreg_db.views.video.correction import (
|
|
27
29
|
VideoMetadataView,
|
|
@@ -216,8 +218,7 @@ urlpatterns = [
|
|
|
216
218
|
# PDF media endpoints
|
|
217
219
|
path("media/pdfs/", PdfMediaView.as_view(), name="pdf-list"),
|
|
218
220
|
path("media/pdfs/<int:pk>/", PdfMediaView.as_view(), name="pdf-detail"),
|
|
219
|
-
path("media/pdfs/<int:pk>/stream/",
|
|
220
|
-
|
|
221
|
+
path("media/pdfs/<int:pk>/stream/", PdfStreamView.as_view(), name="pdf-stream"), # Support ?type=raw|anonymized params
|
|
221
222
|
# PDF Re-import API endpoint (modern media framework)
|
|
222
223
|
# POST /api/media/pdfs/<int:pk>/reimport/
|
|
223
224
|
# Re-imports a PDF file to regenerate metadata when OCR failed or data is incomplete
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration loader for EndoReg DB setup.
|
|
3
|
+
Handles loading and parsing of setup configuration from YAML files.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import glob
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
import yaml
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SetupConfig:
|
|
18
|
+
"""
|
|
19
|
+
Handles loading and accessing setup configuration from YAML files.
|
|
20
|
+
Provides methods to get model names, search patterns, and fallback configurations.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, config_file: Optional[Path] = None):
|
|
24
|
+
"""
|
|
25
|
+
Initialize the setup configuration.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
config_file: Path to the setup configuration YAML file.
|
|
29
|
+
If None, uses default location.
|
|
30
|
+
"""
|
|
31
|
+
if config_file is None:
|
|
32
|
+
# Default to setup_config.yaml in data directory
|
|
33
|
+
config_file = Path(__file__).parent.parent / "data" / "setup_config.yaml"
|
|
34
|
+
|
|
35
|
+
self.config_file = config_file
|
|
36
|
+
self._config = self._load_config()
|
|
37
|
+
|
|
38
|
+
def _load_config(self) -> Dict[str, Any]:
|
|
39
|
+
"""Load configuration from YAML file."""
|
|
40
|
+
try:
|
|
41
|
+
if self.config_file.exists():
|
|
42
|
+
with open(self.config_file, "r") as f:
|
|
43
|
+
config = yaml.safe_load(f)
|
|
44
|
+
logger.info(f"Loaded setup configuration from {self.config_file}")
|
|
45
|
+
return config or {}
|
|
46
|
+
else:
|
|
47
|
+
logger.warning(f"Setup config file not found: {self.config_file}")
|
|
48
|
+
return self._get_default_config()
|
|
49
|
+
except Exception as e:
|
|
50
|
+
logger.error(f"Error loading setup config: {e}")
|
|
51
|
+
return self._get_default_config()
|
|
52
|
+
|
|
53
|
+
def _get_default_config(self) -> Dict[str, Any]:
|
|
54
|
+
"""Return default configuration if file is not available."""
|
|
55
|
+
return {
|
|
56
|
+
"default_models": {
|
|
57
|
+
"primary_classification_model": "image_multilabel_classification_colonoscopy_default",
|
|
58
|
+
"primary_labelset": "multilabel_classification_colonoscopy_default",
|
|
59
|
+
},
|
|
60
|
+
"huggingface_fallback": {
|
|
61
|
+
"enabled": True,
|
|
62
|
+
"repo_id": "wg-lux/colo_segmentation_RegNetX800MF_base",
|
|
63
|
+
"filename": "colo_segmentation_RegNetX800MF_base.ckpt",
|
|
64
|
+
"labelset_name": "multilabel_classification_colonoscopy_default",
|
|
65
|
+
},
|
|
66
|
+
"weights_search_patterns": [
|
|
67
|
+
"colo_segmentation_RegNetX800MF_*.ckpt",
|
|
68
|
+
"image_multilabel_classification_colonoscopy_default_*.ckpt",
|
|
69
|
+
"*_colonoscopy_*.ckpt",
|
|
70
|
+
],
|
|
71
|
+
"weights_search_dirs": ["tests/assets", "assets", "data/storage/model_weights", "${STORAGE_DIR}/model_weights"],
|
|
72
|
+
"auto_generation_defaults": {
|
|
73
|
+
"activation": "sigmoid",
|
|
74
|
+
"mean": "0.485,0.456,0.406",
|
|
75
|
+
"std": "0.229,0.224,0.225",
|
|
76
|
+
"size_x": 224,
|
|
77
|
+
"size_y": 224,
|
|
78
|
+
"axes": "CHW",
|
|
79
|
+
"batchsize": 32,
|
|
80
|
+
"num_workers": 4,
|
|
81
|
+
},
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
def get_primary_model_name(self) -> str:
|
|
85
|
+
"""Get the primary classification model name."""
|
|
86
|
+
return self._config.get("default_models", {}).get("primary_classification_model", "image_multilabel_classification_colonoscopy_default")
|
|
87
|
+
|
|
88
|
+
def get_primary_labelset_name(self) -> str:
|
|
89
|
+
"""Get the primary labelset name."""
|
|
90
|
+
return self._config.get("default_models", {}).get("primary_labelset", "multilabel_classification_colonoscopy_default")
|
|
91
|
+
|
|
92
|
+
def get_huggingface_config(self) -> Dict[str, Any]:
|
|
93
|
+
"""Get HuggingFace fallback configuration."""
|
|
94
|
+
return self._config.get("huggingface_fallback", {})
|
|
95
|
+
|
|
96
|
+
def get_weights_search_patterns(self) -> List[str]:
|
|
97
|
+
"""Get weight file search patterns."""
|
|
98
|
+
return self._config.get("weights_search_patterns", ["colo_segmentation_RegNetX800MF_*.ckpt", "*_colonoscopy_*.ckpt"])
|
|
99
|
+
|
|
100
|
+
def get_weights_search_dirs(self) -> List[Path]:
|
|
101
|
+
"""
|
|
102
|
+
Get weight file search directories with environment variable substitution.
|
|
103
|
+
"""
|
|
104
|
+
dirs = self._config.get("weights_search_dirs", [])
|
|
105
|
+
resolved_dirs = []
|
|
106
|
+
|
|
107
|
+
for dir_str in dirs:
|
|
108
|
+
# Handle environment variable substitution
|
|
109
|
+
if "${" in dir_str:
|
|
110
|
+
dir_str = os.path.expandvars(dir_str)
|
|
111
|
+
|
|
112
|
+
resolved_dirs.append(Path(dir_str))
|
|
113
|
+
|
|
114
|
+
return resolved_dirs
|
|
115
|
+
|
|
116
|
+
def get_auto_generation_defaults(self) -> Dict[str, Any]:
|
|
117
|
+
"""Get default values for auto-generated metadata."""
|
|
118
|
+
return self._config.get("auto_generation_defaults", {})
|
|
119
|
+
|
|
120
|
+
def find_model_weights_files(self) -> List[Path]:
|
|
121
|
+
"""
|
|
122
|
+
Find model weight files using configured search patterns and directories.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
List of paths to found weight files
|
|
126
|
+
"""
|
|
127
|
+
found_files = []
|
|
128
|
+
search_dirs = self.get_weights_search_dirs()
|
|
129
|
+
search_patterns = self.get_weights_search_patterns()
|
|
130
|
+
|
|
131
|
+
for search_dir in search_dirs:
|
|
132
|
+
if not search_dir.exists():
|
|
133
|
+
continue
|
|
134
|
+
|
|
135
|
+
for pattern in search_patterns:
|
|
136
|
+
# Use glob to find files matching pattern
|
|
137
|
+
pattern_path = search_dir / pattern
|
|
138
|
+
matches = glob.glob(str(pattern_path))
|
|
139
|
+
for match in matches:
|
|
140
|
+
path = Path(match)
|
|
141
|
+
if path.exists() and path not in found_files:
|
|
142
|
+
found_files.append(path)
|
|
143
|
+
logger.info(f"Found weight file: {path}")
|
|
144
|
+
|
|
145
|
+
return found_files
|
|
146
|
+
|
|
147
|
+
def get_model_specific_config(self, model_name: str) -> Optional[Dict[str, Any]]:
|
|
148
|
+
"""
|
|
149
|
+
Get model-specific configuration from YAML metadata files.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
model_name: Name of the model to get config for
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
Model-specific setup configuration if found
|
|
156
|
+
"""
|
|
157
|
+
# This would need to parse the ai_model_meta YAML files
|
|
158
|
+
# and extract setup_config sections for the specified model
|
|
159
|
+
try:
|
|
160
|
+
from endoreg_db.data import AI_MODEL_META_DATA_DIR
|
|
161
|
+
|
|
162
|
+
for yaml_file in AI_MODEL_META_DATA_DIR.glob("*.yaml"):
|
|
163
|
+
with open(yaml_file, "r") as f:
|
|
164
|
+
data = yaml.safe_load(f)
|
|
165
|
+
|
|
166
|
+
if isinstance(data, list):
|
|
167
|
+
for item in data:
|
|
168
|
+
if item.get("fields", {}).get("name") == model_name or item.get("fields", {}).get("model") == model_name:
|
|
169
|
+
return item.get("setup_config", {})
|
|
170
|
+
except Exception as e:
|
|
171
|
+
logger.warning(f"Error loading model-specific config for {model_name}: {e}")
|
|
172
|
+
|
|
173
|
+
return None
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
# Global instance for easy access
|
|
177
|
+
setup_config = SetupConfig()
|
endoreg_db/views/__init__.py
CHANGED
|
@@ -108,8 +108,8 @@ from .patient_finding_classification import (
|
|
|
108
108
|
)
|
|
109
109
|
|
|
110
110
|
from .pdf import (
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
PdfReimportView,
|
|
112
|
+
PdfStreamView,
|
|
113
113
|
)
|
|
114
114
|
|
|
115
115
|
from .report import (
|
|
@@ -240,7 +240,9 @@ __all__ = [
|
|
|
240
240
|
"create_patient_finding_classification",
|
|
241
241
|
|
|
242
242
|
# PDF
|
|
243
|
-
"
|
|
243
|
+
"PdfMediaView",
|
|
244
|
+
"PdfReimportView",
|
|
245
|
+
"PdfStreamView",
|
|
244
246
|
|
|
245
247
|
# Report
|
|
246
248
|
"ReportListView",
|
|
@@ -13,6 +13,7 @@ from django.http import Http404, FileResponse
|
|
|
13
13
|
from rest_framework import status
|
|
14
14
|
from rest_framework.response import Response
|
|
15
15
|
from rest_framework.views import APIView
|
|
16
|
+
from django.views.decorators.clickjacking import xframe_options_exempt
|
|
16
17
|
from django.db.models import Q
|
|
17
18
|
|
|
18
19
|
from endoreg_db.models import RawPdfFile
|
|
@@ -133,7 +134,8 @@ class PdfMediaView(APIView):
|
|
|
133
134
|
{"error": "Failed to retrieve PDF details"},
|
|
134
135
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
|
135
136
|
)
|
|
136
|
-
|
|
137
|
+
|
|
138
|
+
@xframe_options_exempt
|
|
137
139
|
def _stream_pdf(self, pk):
|
|
138
140
|
"""
|
|
139
141
|
Stream PDF file content for viewing/download.
|
|
@@ -30,7 +30,7 @@ class VideoMediaView(APIView):
|
|
|
30
30
|
- GET /api/media/videos/ - List all videos with filtering
|
|
31
31
|
- GET /api/media/videos/{id}/ - Get video details
|
|
32
32
|
- PATCH /api/media/videos/{id}/ - Update video metadata (future)
|
|
33
|
-
- DELETE /api/media/videos/{id}/ - Delete video
|
|
33
|
+
- DELETE /api/media/videos/{id}/ - Delete video
|
|
34
34
|
|
|
35
35
|
Query Parameters:
|
|
36
36
|
- status: Filter by processing status (not_started, processing, done, failed, validated)
|