endoreg-db 0.8.5.4__py3-none-any.whl → 0.8.5.6__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/models/media/pdf/raw_pdf.py +241 -97
- endoreg_db/models/media/video/video_file.py +23 -5
- endoreg_db/serializers/__init__.py +26 -55
- endoreg_db/serializers/video_examination.py +198 -0
- endoreg_db/services/pdf_import.py +292 -77
- endoreg_db/urls/__init__.py +36 -23
- endoreg_db/views/pdf/reimport.py +110 -94
- endoreg_db/views/video/video_examination_viewset.py +202 -289
- {endoreg_db-0.8.5.4.dist-info → endoreg_db-0.8.5.6.dist-info}/METADATA +1 -1
- {endoreg_db-0.8.5.4.dist-info → endoreg_db-0.8.5.6.dist-info}/RECORD +12 -11
- {endoreg_db-0.8.5.4.dist-info → endoreg_db-0.8.5.6.dist-info}/WHEEL +0 -0
- {endoreg_db-0.8.5.4.dist-info → endoreg_db-0.8.5.6.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/views/pdf/reimport.py
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
|
-
from rest_framework.views import APIView
|
|
2
|
-
from rest_framework.response import Response
|
|
3
|
-
from rest_framework import status
|
|
4
1
|
import logging
|
|
5
|
-
|
|
2
|
+
|
|
6
3
|
from django.db import transaction
|
|
4
|
+
from rest_framework import status
|
|
5
|
+
from rest_framework.response import Response
|
|
6
|
+
from rest_framework.views import APIView
|
|
7
|
+
|
|
7
8
|
from ...models import RawPdfFile, SensitiveMeta
|
|
8
9
|
from ...services.pdf_import import PdfImportService
|
|
10
|
+
|
|
9
11
|
logger = logging.getLogger(__name__)
|
|
10
12
|
|
|
13
|
+
|
|
11
14
|
class PdfReimportView(APIView):
|
|
12
15
|
"""
|
|
13
16
|
API endpoint to re-import a pdf file and regenerate metadata.
|
|
14
17
|
This is useful when OCR failed or metadata is incomplete.
|
|
15
18
|
"""
|
|
16
|
-
|
|
19
|
+
|
|
17
20
|
def __init__(self, **kwargs):
|
|
18
21
|
super().__init__(**kwargs)
|
|
19
22
|
self.pdf_service = PdfImportService()
|
|
@@ -22,140 +25,153 @@ class PdfReimportView(APIView):
|
|
|
22
25
|
"""
|
|
23
26
|
Re-import a pdf file to regenerate SensitiveMeta and other metadata.
|
|
24
27
|
Instead of creating a new pdf, this updates the existing one.
|
|
25
|
-
|
|
28
|
+
|
|
26
29
|
Args:
|
|
27
30
|
request: HTTP request object
|
|
28
31
|
pk: PDF primary key (ID)
|
|
29
32
|
"""
|
|
30
33
|
pdf_id = pk # Align with media framework naming convention
|
|
31
|
-
|
|
34
|
+
|
|
32
35
|
# Validate pdf_id parameter
|
|
33
36
|
if not pdf_id or not isinstance(pdf_id, int):
|
|
34
37
|
return Response(
|
|
35
|
-
{"error": "Invalid PDF ID provided."},
|
|
36
|
-
status=status.HTTP_400_BAD_REQUEST
|
|
38
|
+
{"error": "Invalid PDF ID provided."},
|
|
39
|
+
status=status.HTTP_400_BAD_REQUEST,
|
|
37
40
|
)
|
|
38
41
|
|
|
39
42
|
try:
|
|
40
43
|
pdf = RawPdfFile.objects.get(id=pdf_id)
|
|
41
|
-
logger.info(f"Found PDF {pdf.
|
|
44
|
+
logger.info(f"Found PDF {pdf.pdf_hash} (ID: {pdf_id}) for re-import")
|
|
42
45
|
except RawPdfFile.DoesNotExist:
|
|
43
46
|
logger.warning(f"PDF with ID {pdf_id} not found")
|
|
44
47
|
return Response(
|
|
45
|
-
{"error": f"PDF with ID {pdf_id} not found."},
|
|
46
|
-
status=status.HTTP_404_NOT_FOUND
|
|
48
|
+
{"error": f"PDF with ID {pdf_id} not found."},
|
|
49
|
+
status=status.HTTP_404_NOT_FOUND,
|
|
47
50
|
)
|
|
48
51
|
|
|
52
|
+
# Get raw file path using the model method
|
|
53
|
+
raw_file_path = pdf.get_raw_file_path()
|
|
49
54
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
logger.error(f"Raw file not found on disk: {raw_file_path}")
|
|
55
|
+
if not raw_file_path or not raw_file_path.exists():
|
|
56
|
+
logger.error(
|
|
57
|
+
f"Raw PDF file not found for hash {pdf.pdf_hash}: {raw_file_path}"
|
|
58
|
+
)
|
|
55
59
|
return Response(
|
|
56
|
-
{
|
|
57
|
-
|
|
60
|
+
{
|
|
61
|
+
"error": f"Raw PDF file not found for PDF {pdf.pdf_hash}. Please upload the original file again."
|
|
62
|
+
},
|
|
63
|
+
status=status.HTTP_404_NOT_FOUND,
|
|
58
64
|
)
|
|
59
65
|
|
|
60
66
|
# Check if PDF has required relationships
|
|
61
67
|
if not pdf.center:
|
|
62
|
-
logger.warning(f"PDF {pdf.
|
|
68
|
+
logger.warning(f"PDF {pdf.pdf_hash} has no associated center")
|
|
63
69
|
return Response(
|
|
64
|
-
{"error": "
|
|
65
|
-
status=status.HTTP_400_BAD_REQUEST
|
|
70
|
+
{"error": "PDF has no associated center."},
|
|
71
|
+
status=status.HTTP_400_BAD_REQUEST,
|
|
66
72
|
)
|
|
67
73
|
|
|
68
74
|
try:
|
|
69
|
-
logger.info(f"Starting
|
|
70
|
-
|
|
75
|
+
logger.info(f"Starting re-import for PDF {pdf.pdf_hash} (ID: {pdf_id})")
|
|
76
|
+
|
|
71
77
|
with transaction.atomic():
|
|
72
78
|
# Clear existing metadata to force regeneration
|
|
73
79
|
old_meta_id = None
|
|
74
80
|
if pdf.sensitive_meta:
|
|
75
|
-
old_meta_id = pdf.sensitive_meta.
|
|
76
|
-
logger.info(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
81
|
+
old_meta_id = pdf.sensitive_meta.pk
|
|
82
|
+
logger.info(
|
|
83
|
+
f"Clearing existing SensitiveMeta {old_meta_id} for PDF {pdf.pdf_hash}"
|
|
84
|
+
)
|
|
85
|
+
pdf.sensitive_meta = None # type: ignore
|
|
86
|
+
pdf.save(update_fields=["sensitive_meta"])
|
|
87
|
+
|
|
80
88
|
# Delete the old SensitiveMeta record
|
|
81
89
|
try:
|
|
82
|
-
SensitiveMeta.objects.filter(
|
|
90
|
+
SensitiveMeta.objects.filter(pk=old_meta_id).delete()
|
|
83
91
|
logger.info(f"Deleted old SensitiveMeta {old_meta_id}")
|
|
84
92
|
except Exception as e:
|
|
85
|
-
logger.warning(
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
# Ensure minimum patient data is available
|
|
92
|
-
logger.info(f"Ensuring minimum patient data for {pdf.uuid}")
|
|
93
|
-
self.pdf_service._ensure_default_patient_data(pdf)
|
|
94
|
-
|
|
95
|
-
# Refresh from database to get updated data
|
|
96
|
-
pdf.refresh_from_db()
|
|
97
|
-
|
|
98
|
-
# Use VideoImportService for anonymization
|
|
93
|
+
logger.warning(
|
|
94
|
+
f"Could not delete old SensitiveMeta {old_meta_id}: {e}"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# Use PdfImportService for reprocessing
|
|
99
98
|
try:
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
logger.info(
|
|
100
|
+
f"Starting reprocessing using PdfImportService for {pdf.pdf_hash}"
|
|
101
|
+
)
|
|
102
102
|
self.pdf_service.import_and_anonymize(
|
|
103
103
|
file_path=raw_file_path,
|
|
104
104
|
center_name=pdf.center.name,
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
delete_source=False
|
|
105
|
+
delete_source=False, # Don't delete during reimport
|
|
106
|
+
retry=True, # Mark as retry attempt
|
|
108
107
|
)
|
|
109
|
-
|
|
110
|
-
logger.info(
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
108
|
+
|
|
109
|
+
logger.info(
|
|
110
|
+
f"PdfImportService reprocessing completed for {pdf.pdf_hash}"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# Refresh to get updated state
|
|
114
|
+
pdf.refresh_from_db()
|
|
115
|
+
|
|
116
|
+
return Response(
|
|
117
|
+
{
|
|
118
|
+
"message": "PDF re-import completed successfully.",
|
|
119
|
+
"pdf_id": pdf_id,
|
|
120
|
+
"pdf_hash": str(pdf.pdf_hash),
|
|
121
|
+
"sensitive_meta_created": pdf.sensitive_meta is not None,
|
|
122
|
+
"sensitive_meta_id": pdf.sensitive_meta.pk
|
|
123
|
+
if pdf.sensitive_meta
|
|
124
|
+
else None,
|
|
125
|
+
"text_extracted": bool(pdf.text),
|
|
126
|
+
"anonymized": pdf.anonymized,
|
|
127
|
+
"status": "done",
|
|
128
|
+
},
|
|
129
|
+
status=status.HTTP_200_OK,
|
|
130
|
+
)
|
|
131
|
+
|
|
124
132
|
except Exception as e:
|
|
125
|
-
logger.exception(
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
"updated_in_place": True,
|
|
138
|
-
"status": "done"
|
|
139
|
-
}, status=status.HTTP_200_OK)
|
|
133
|
+
logger.exception(
|
|
134
|
+
f"PdfImportService reprocessing failed for PDF {pdf.pdf_hash}: {e}"
|
|
135
|
+
)
|
|
136
|
+
return Response(
|
|
137
|
+
{
|
|
138
|
+
"error": f"Reprocessing failed: {str(e)}",
|
|
139
|
+
"error_type": "processing_error",
|
|
140
|
+
"pdf_id": pdf_id,
|
|
141
|
+
"pdf_hash": str(pdf.pdf_hash),
|
|
142
|
+
},
|
|
143
|
+
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
144
|
+
)
|
|
140
145
|
|
|
141
146
|
except Exception as e:
|
|
142
|
-
logger.error(
|
|
143
|
-
|
|
147
|
+
logger.error(
|
|
148
|
+
f"Failed to re-import PDF {pdf.pdf_hash}: {str(e)}", exc_info=True
|
|
149
|
+
)
|
|
150
|
+
|
|
144
151
|
# Handle specific error types
|
|
145
152
|
error_msg = str(e)
|
|
146
|
-
if any(
|
|
153
|
+
if any(
|
|
154
|
+
phrase in error_msg.lower()
|
|
155
|
+
for phrase in ["insufficient storage", "no space left", "disk full"]
|
|
156
|
+
):
|
|
147
157
|
# Storage error - return specific error message
|
|
148
|
-
return Response(
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
158
|
+
return Response(
|
|
159
|
+
{
|
|
160
|
+
"error": f"Storage error during re-import: {error_msg}",
|
|
161
|
+
"error_type": "storage_error",
|
|
162
|
+
"pdf_id": pdf_id,
|
|
163
|
+
"pdf_hash": str(pdf.pdf_hash),
|
|
164
|
+
},
|
|
165
|
+
status=status.HTTP_507_INSUFFICIENT_STORAGE,
|
|
166
|
+
)
|
|
154
167
|
else:
|
|
155
168
|
# Other errors
|
|
156
|
-
return Response(
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
169
|
+
return Response(
|
|
170
|
+
{
|
|
171
|
+
"error": f"Re-import failed: {error_msg}",
|
|
172
|
+
"error_type": "processing_error",
|
|
173
|
+
"pdf_id": pdf_id,
|
|
174
|
+
"pdf_hash": str(pdf.pdf_hash),
|
|
175
|
+
},
|
|
176
|
+
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
177
|
+
)
|