endoreg-db 0.8.3.3__py3-none-any.whl → 0.8.6.5__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/create_model_meta_from_huggingface.py +1 -2
- 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 +34 -45
- endoreg_db/models/metadata/sensitive_meta_logic.py +555 -150
- 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 +493 -240
- endoreg_db/urls/__init__.py +36 -23
- endoreg_db/urls/label_video_segments.py +2 -0
- endoreg_db/urls/media.py +103 -66
- 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 +186 -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.3.dist-info → endoreg_db-0.8.6.5.dist-info}/METADATA +1 -2
- {endoreg_db-0.8.3.3.dist-info → endoreg_db-0.8.6.5.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.3.dist-info → endoreg_db-0.8.6.5.dist-info}/WHEEL +0 -0
- {endoreg_db-0.8.3.3.dist-info → endoreg_db-0.8.6.5.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,35 +1,36 @@
|
|
|
1
1
|
from django.urls import path
|
|
2
|
+
from PIL.PdfParser import PdfStream
|
|
2
3
|
|
|
4
|
+
from endoreg_db.views import VideoStreamView
|
|
3
5
|
from endoreg_db.views.media import (
|
|
4
|
-
VideoMediaView,
|
|
5
6
|
PdfMediaView, # Alias to avoid conflict with legacy pdf.PDFMediaView
|
|
7
|
+
VideoMediaView,
|
|
8
|
+
pdf_sensitive_metadata,
|
|
9
|
+
pdf_sensitive_metadata_list,
|
|
10
|
+
pdf_sensitive_metadata_verify,
|
|
11
|
+
sensitive_metadata_list,
|
|
12
|
+
video_segment_detail,
|
|
13
|
+
video_segment_validate,
|
|
6
14
|
video_segments_by_pk,
|
|
7
|
-
video_segments_collection,
|
|
8
15
|
video_segments_by_video,
|
|
9
|
-
|
|
16
|
+
video_segments_collection,
|
|
10
17
|
video_segments_stats,
|
|
11
|
-
video_segment_validate,
|
|
12
18
|
video_segments_validate_bulk,
|
|
13
19
|
video_segments_validation_status,
|
|
14
20
|
video_sensitive_metadata,
|
|
15
21
|
video_sensitive_metadata_verify,
|
|
16
|
-
pdf_sensitive_metadata,
|
|
17
|
-
pdf_sensitive_metadata_verify,
|
|
18
|
-
sensitive_metadata_list,
|
|
19
|
-
pdf_sensitive_metadata_list,
|
|
20
|
-
)
|
|
21
|
-
from endoreg_db.views import (
|
|
22
|
-
VideoStreamView,
|
|
23
22
|
)
|
|
23
|
+
from endoreg_db.views.pdf.pdf_stream import PdfStreamView
|
|
24
24
|
from endoreg_db.views.pdf.reimport import PdfReimportView
|
|
25
|
-
from endoreg_db.views.video.reimport import VideoReimportView
|
|
26
25
|
from endoreg_db.views.video.correction import (
|
|
26
|
+
VideoApplyMaskView,
|
|
27
|
+
VideoCorrectionView,
|
|
27
28
|
VideoMetadataView,
|
|
28
29
|
VideoProcessingHistoryView,
|
|
29
|
-
VideoApplyMaskView,
|
|
30
30
|
VideoRemoveFramesView,
|
|
31
|
-
VideoCorrectionView,
|
|
32
31
|
)
|
|
32
|
+
from endoreg_db.views.video.reimport import VideoReimportView
|
|
33
|
+
|
|
33
34
|
# ---------------------------------------------------------------------------------------
|
|
34
35
|
# ANNOTATION API ENDPOINTS
|
|
35
36
|
#
|
|
@@ -39,19 +40,27 @@ from endoreg_db.views.video.correction import (
|
|
|
39
40
|
# ---------------------------------------------------------------------------------------
|
|
40
41
|
|
|
41
42
|
# Simplified Meta and Validation Endpoints
|
|
42
|
-
|
|
43
|
+
|
|
43
44
|
urlpatterns = [
|
|
44
45
|
# Video media endpoints
|
|
45
46
|
path("media/videos/", VideoMediaView.as_view(), name="video-list"),
|
|
46
|
-
path(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
path(
|
|
48
|
+
"media/videos/<int:pk>/", VideoStreamView.as_view(), name="video-detail-stream"
|
|
49
|
+
), # Support ?type= params
|
|
50
|
+
path(
|
|
51
|
+
"media/videos/<int:pk>/details/", VideoMediaView.as_view(), name="video-detail"
|
|
52
|
+
), # JSON metadata
|
|
53
|
+
path(
|
|
54
|
+
"media/videos/<int:pk>/stream/", VideoStreamView.as_view(), name="video-stream"
|
|
55
|
+
), # Legacy support
|
|
50
56
|
# Video Re-import API endpoint (modern media framework)
|
|
51
57
|
# POST /api/media/videos/<int:pk>/reimport/
|
|
52
58
|
# Re-imports a video file to regenerate metadata when OCR failed or data is incomplete
|
|
53
|
-
path(
|
|
54
|
-
|
|
59
|
+
path(
|
|
60
|
+
"media/videos/<int:pk>/reimport/",
|
|
61
|
+
VideoReimportView.as_view(),
|
|
62
|
+
name="video-reimport",
|
|
63
|
+
),
|
|
55
64
|
# ---------------------------------------------------------------------------------------
|
|
56
65
|
# VIDEO CORRECTION API ENDPOINTS (Modern Media Framework - October 14, 2025)
|
|
57
66
|
#
|
|
@@ -64,38 +73,51 @@ urlpatterns = [
|
|
|
64
73
|
# - Metadata: View analysis results
|
|
65
74
|
# - History: Track all correction operations
|
|
66
75
|
# ---------------------------------------------------------------------------------------
|
|
67
|
-
|
|
68
76
|
# Video Correction API
|
|
69
77
|
# GET /api/media/videos/video-correction/{id}/ - Get video details for correction
|
|
70
|
-
path(
|
|
71
|
-
|
|
78
|
+
path(
|
|
79
|
+
"media/videos/video-correction/<int:pk>",
|
|
80
|
+
VideoCorrectionView.as_view(),
|
|
81
|
+
name="video-correction",
|
|
82
|
+
),
|
|
72
83
|
# Video Metadata API
|
|
73
84
|
# GET /api/media/videos/<int:pk>/metadata/
|
|
74
85
|
# Returns analysis results (sensitive frame count, ratio, frame IDs)
|
|
75
|
-
path(
|
|
76
|
-
|
|
86
|
+
path(
|
|
87
|
+
"media/videos/<int:pk>/metadata/",
|
|
88
|
+
VideoMetadataView.as_view(),
|
|
89
|
+
name="video-metadata",
|
|
90
|
+
),
|
|
77
91
|
# Video Processing History API
|
|
78
92
|
# GET /api/media/videos/<int:pk>/processing-history/
|
|
79
93
|
# Returns history of all processing operations (masking, frame removal, analysis)
|
|
80
|
-
path(
|
|
81
|
-
|
|
94
|
+
path(
|
|
95
|
+
"media/videos/<int:pk>/processing-history/",
|
|
96
|
+
VideoProcessingHistoryView.as_view(),
|
|
97
|
+
name="video-processing-history",
|
|
98
|
+
),
|
|
82
99
|
# Video Analysis API
|
|
83
100
|
# POST /api/media/videos/<int:pk>/analyze/
|
|
84
101
|
# Analyzes video for sensitive frames using MiniCPM-o 2.6 or OCR+LLM
|
|
85
102
|
# Body: { detection_method: 'minicpm'|'ocr_llm'|'hybrid', sample_interval: 30 }
|
|
86
|
-
|
|
87
103
|
# Video Masking API
|
|
88
104
|
# POST /api/media/videos/<int:pk>/apply-mask/
|
|
89
105
|
# Applies device mask or custom ROI mask to video
|
|
90
106
|
# Body: { mask_type: 'device'|'custom', device_name: 'olympus', roi: {...} }
|
|
91
|
-
path(
|
|
92
|
-
|
|
107
|
+
path(
|
|
108
|
+
"media/videos/<int:pk>/apply-mask/",
|
|
109
|
+
VideoApplyMaskView.as_view(),
|
|
110
|
+
name="video-apply-mask",
|
|
111
|
+
),
|
|
93
112
|
# Video Frame Removal API
|
|
94
113
|
# POST /api/media/videos/<int:pk>/remove-frames/
|
|
95
114
|
# Removes specified frames from video
|
|
96
115
|
# Body: { frame_list: [10,20,30] OR frame_ranges: '10-20,30' OR detection_method: 'automatic' }
|
|
97
|
-
path(
|
|
98
|
-
|
|
116
|
+
path(
|
|
117
|
+
"media/videos/<int:pk>/remove-frames/",
|
|
118
|
+
VideoRemoveFramesView.as_view(),
|
|
119
|
+
name="video-remove-frames",
|
|
120
|
+
),
|
|
99
121
|
# ---------------------------------------------------------------------------------------
|
|
100
122
|
# VIDEO SEGMENT API ENDPOINTS (Modern Media Framework - October 14, 2025)
|
|
101
123
|
#
|
|
@@ -104,29 +126,40 @@ urlpatterns = [
|
|
|
104
126
|
# Video-scoped: GET/POST segments for specific video
|
|
105
127
|
# Detail: GET/PATCH/DELETE individual segment
|
|
106
128
|
# ---------------------------------------------------------------------------------------
|
|
107
|
-
|
|
108
129
|
# Video Segments Collection API
|
|
109
130
|
# GET/POST /api/media/videos/segments/
|
|
110
131
|
# List all video segments across videos or create new segment
|
|
111
|
-
path(
|
|
112
|
-
|
|
132
|
+
path(
|
|
133
|
+
"media/videos/segments/",
|
|
134
|
+
video_segments_collection,
|
|
135
|
+
name="video-segments-collection",
|
|
136
|
+
),
|
|
113
137
|
# Video Segments Stats API
|
|
114
138
|
# GET /api/media/videos/segments/stats/
|
|
115
139
|
# Get statistics about video segments
|
|
116
|
-
path(
|
|
117
|
-
|
|
140
|
+
path(
|
|
141
|
+
"media/videos/segments/stats/",
|
|
142
|
+
video_segments_stats,
|
|
143
|
+
name="video-segments-stats",
|
|
144
|
+
),
|
|
118
145
|
# Video-Specific Segments API
|
|
119
146
|
# GET/POST /api/media/videos/<int:pk>/segments/
|
|
120
147
|
# List segments for specific video or create segment for video
|
|
121
|
-
path(
|
|
122
|
-
|
|
148
|
+
path(
|
|
149
|
+
"media/videos/<int:pk>/segments/",
|
|
150
|
+
video_segments_by_video,
|
|
151
|
+
name="video-segments-by-video",
|
|
152
|
+
),
|
|
123
153
|
# Segment Detail API
|
|
124
154
|
# GET /api/media/videos/<int:pk>/segments/<int:segment_id>/
|
|
125
155
|
# PATCH /api/media/videos/<int:pk>/segments/<int:segment_id>/
|
|
126
156
|
# DELETE /api/media/videos/<int:pk>/segments/<int:segment_id>/
|
|
127
157
|
# Manages individual segment operations
|
|
128
|
-
path(
|
|
129
|
-
|
|
158
|
+
path(
|
|
159
|
+
"media/videos/<int:pk>/segments/<int:segment_id>/",
|
|
160
|
+
video_segment_detail,
|
|
161
|
+
name="video-segment-detail",
|
|
162
|
+
),
|
|
130
163
|
# ---------------------------------------------------------------------------------------
|
|
131
164
|
# VIDEO SEGMENT VALIDATION API ENDPOINTS (Modern Media Framework - October 14, 2025)
|
|
132
165
|
#
|
|
@@ -135,65 +168,68 @@ urlpatterns = [
|
|
|
135
168
|
# Bulk: POST validate multiple segments
|
|
136
169
|
# Status: GET/POST validation status for all segments
|
|
137
170
|
# ---------------------------------------------------------------------------------------
|
|
138
|
-
|
|
139
171
|
# Single Segment Validation API
|
|
140
172
|
# POST /api/media/videos/<int:pk>/segments/<int:segment_id>/validate/
|
|
141
173
|
# Validates a single video segment
|
|
142
174
|
# Body: { "is_validated": true, "notes": "..." }
|
|
143
|
-
path(
|
|
144
|
-
|
|
175
|
+
path(
|
|
176
|
+
"media/videos/<int:pk>/segments/<int:segment_id>/validate/",
|
|
177
|
+
video_segment_validate,
|
|
178
|
+
name="video-segment-validate",
|
|
179
|
+
),
|
|
145
180
|
# Bulk Segment Validation API
|
|
146
181
|
# POST /api/media/videos/<int:pk>/segments/validate-bulk/
|
|
147
182
|
# Validates multiple segments at once
|
|
148
183
|
# Body: { "segment_ids": [1,2,3], "is_validated": true, "notes": "..." }
|
|
149
|
-
path(
|
|
150
|
-
|
|
184
|
+
path(
|
|
185
|
+
"media/videos/<int:pk>/segments/validate-bulk/",
|
|
186
|
+
video_segments_validate_bulk,
|
|
187
|
+
name="video-segments-validate-bulk",
|
|
188
|
+
),
|
|
151
189
|
# Segment Validation Status API
|
|
152
190
|
# GET /api/media/videos/<int:pk>/segments/validation-status/
|
|
153
191
|
# Returns validation statistics for all segments
|
|
154
192
|
# POST /api/media/videos/<int:pk>/segments/validation-status/
|
|
155
193
|
# Marks all segments (or filtered by label) as validated
|
|
156
194
|
# Body: { "label_name": "polyp", "notes": "..." }
|
|
157
|
-
path(
|
|
158
|
-
|
|
195
|
+
path(
|
|
196
|
+
"media/videos/<int:pk>/segments/validation-status/",
|
|
197
|
+
video_segments_validation_status,
|
|
198
|
+
name="video-segments-validation-status",
|
|
199
|
+
),
|
|
159
200
|
# ---------------------------------------------------------------------------------------
|
|
160
201
|
# SENSITIVE METADATA ENDPOINTS (Modern Media Framework)
|
|
161
202
|
# ---------------------------------------------------------------------------------------
|
|
162
|
-
|
|
163
203
|
# Video Sensitive Metadata (Resource-Scoped)
|
|
164
204
|
# GET/PATCH /api/media/videos/<pk>/sensitive-metadata/
|
|
165
205
|
# Get or update sensitive patient data for a video
|
|
166
206
|
path(
|
|
167
207
|
"media/videos/<int:pk>/sensitive-metadata/",
|
|
168
208
|
video_sensitive_metadata,
|
|
169
|
-
name="video-sensitive-metadata"
|
|
209
|
+
name="video-sensitive-metadata",
|
|
170
210
|
),
|
|
171
|
-
|
|
172
211
|
# POST /api/media/videos/<pk>/sensitive-metadata/verify/
|
|
173
212
|
# Update verification state (dob_verified, names_verified)
|
|
174
213
|
path(
|
|
175
214
|
"media/videos/<int:pk>/sensitive-metadata/verify/",
|
|
176
215
|
video_sensitive_metadata_verify,
|
|
177
|
-
name="video-sensitive-metadata-verify"
|
|
216
|
+
name="video-sensitive-metadata-verify",
|
|
178
217
|
),
|
|
179
|
-
|
|
180
218
|
# PDF Sensitive Metadata (Resource-Scoped)
|
|
181
219
|
# GET/PATCH /api/media/pdfs/<pk>/sensitive-metadata/
|
|
182
220
|
# Get or update sensitive patient data for a PDF
|
|
183
221
|
path(
|
|
184
222
|
"media/pdfs/<int:pk>/sensitive-metadata/",
|
|
185
223
|
pdf_sensitive_metadata,
|
|
186
|
-
name="pdf-sensitive-metadata"
|
|
224
|
+
name="pdf-sensitive-metadata",
|
|
187
225
|
),
|
|
188
|
-
|
|
189
226
|
# POST /api/media/pdfs/<pk>/sensitive-metadata/verify/
|
|
190
227
|
# Update verification state (dob_verified, names_verified)
|
|
191
228
|
path(
|
|
192
229
|
"media/pdfs/<int:pk>/sensitive-metadata/verify/",
|
|
193
230
|
pdf_sensitive_metadata_verify,
|
|
194
|
-
name="pdf-sensitive-metadata-verify"
|
|
231
|
+
name="pdf-sensitive-metadata-verify",
|
|
195
232
|
),
|
|
196
|
-
|
|
197
233
|
# List Endpoints (Collection-Level)
|
|
198
234
|
# GET /api/media/sensitive-metadata/
|
|
199
235
|
# List all sensitive metadata (combined PDFs and Videos)
|
|
@@ -201,26 +237,27 @@ urlpatterns = [
|
|
|
201
237
|
path(
|
|
202
238
|
"media/sensitive-metadata/",
|
|
203
239
|
sensitive_metadata_list,
|
|
204
|
-
name="sensitive-metadata-list"
|
|
240
|
+
name="sensitive-metadata-list",
|
|
205
241
|
),
|
|
206
|
-
|
|
207
242
|
# GET /api/media/pdfs/sensitive-metadata/
|
|
208
243
|
# List sensitive metadata for PDFs only
|
|
209
244
|
# Replaces legacy /api/pdf/sensitivemeta/list/
|
|
210
245
|
path(
|
|
211
246
|
"media/pdfs/sensitive-metadata/",
|
|
212
247
|
pdf_sensitive_metadata_list,
|
|
213
|
-
name="pdf-sensitive-metadata-list"
|
|
248
|
+
name="pdf-sensitive-metadata-list",
|
|
214
249
|
),
|
|
215
|
-
|
|
216
250
|
# PDF media endpoints
|
|
217
251
|
path("media/pdfs/", PdfMediaView.as_view(), name="pdf-list"),
|
|
218
252
|
path("media/pdfs/<int:pk>/", PdfMediaView.as_view(), name="pdf-detail"),
|
|
219
|
-
path(
|
|
220
|
-
|
|
253
|
+
path(
|
|
254
|
+
"media/pdfs/<int:pk>/stream/", PdfStreamView.as_view(), name="pdf-stream"
|
|
255
|
+
), # Support ?type=raw|anonymized params
|
|
221
256
|
# PDF Re-import API endpoint (modern media framework)
|
|
222
257
|
# POST /api/media/pdfs/<int:pk>/reimport/
|
|
223
258
|
# Re-imports a PDF file to regenerate metadata when OCR failed or data is incomplete
|
|
224
|
-
path(
|
|
259
|
+
path(
|
|
260
|
+
"media/pdfs/<int:pk>/reimport/", PdfReimportView.as_view(), name="pdf-reimport"
|
|
261
|
+
),
|
|
225
262
|
]
|
|
226
|
-
|
|
263
|
+
# ---------------------------------------------------------------------------------------
|
|
@@ -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)
|