endoreg-db 0.6.2__py3-none-any.whl → 0.6.4__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/__init__.py +14 -0
- endoreg_db/data/disease_classification/chronic_kidney_disease.yaml +2 -2
- endoreg_db/data/disease_classification_choice/chronic_kidney_disease.yaml +6 -6
- endoreg_db/data/distribution/numeric/data.yaml +1 -1
- endoreg_db/data/examination/examinations/data.yaml +22 -21
- endoreg_db/data/examination/type/data.yaml +12 -0
- endoreg_db/data/examination_indication/endoscopy.yaml +417 -1
- endoreg_db/data/examination_indication_classification/endoscopy.yaml +157 -5
- endoreg_db/data/finding/data.yaml +18 -11
- endoreg_db/data/finding_intervention/endoscopy.yaml +26 -121
- endoreg_db/data/finding_intervention/endoscopy_colonoscopy.yaml +163 -0
- endoreg_db/data/finding_intervention/endoscopy_egd.yaml +128 -0
- endoreg_db/data/finding_intervention/endoscopy_ercp.yaml +32 -0
- endoreg_db/data/finding_intervention/endoscopy_eus_lower.yaml +9 -0
- endoreg_db/data/finding_intervention/endoscopy_eus_upper.yaml +36 -0
- endoreg_db/data/information_source/endoscopy_guidelines.yaml +7 -0
- endoreg_db/data/medication_indication/anticoagulation.yaml +4 -4
- endoreg_db/data/pdf_type/data.yaml +9 -16
- endoreg_db/data/requirement/colonoscopy_indications.yaml +56 -0
- endoreg_db/data/requirement/disease_cardiovascular.yaml +79 -0
- endoreg_db/data/requirement/disease_classification_choice_cardiovascular.yaml +38 -0
- endoreg_db/data/requirement/disease_hepatology.yaml +12 -0
- endoreg_db/data/requirement/disease_misc.yaml +12 -0
- endoreg_db/data/requirement/disease_renal.yaml +80 -0
- endoreg_db/data/requirement/event_cardiology.yaml +251 -0
- endoreg_db/data/requirement/lab_value.yaml +120 -0
- endoreg_db/data/requirement_operator/lab_operators.yaml +128 -0
- endoreg_db/data/requirement_operator/model_operators.yaml +90 -0
- endoreg_db/data/requirement_set/endoscopy_bleeding_risk.yaml +12 -0
- endoreg_db/data/requirement_set_type/data.yaml +20 -0
- endoreg_db/data/requirement_type/requirement_types.yaml +83 -0
- endoreg_db/data/risk/bleeding.yaml +26 -0
- endoreg_db/data/risk/thrombosis.yaml +37 -0
- endoreg_db/data/risk_type/data.yaml +27 -0
- endoreg_db/data/unit/time.yaml +36 -1
- endoreg_db/management/commands/load_base_db_data.py +14 -1
- endoreg_db/management/commands/load_center_data.py +46 -21
- endoreg_db/management/commands/load_examination_indication_data.py +49 -27
- endoreg_db/management/commands/load_requirement_data.py +156 -0
- endoreg_db/management/commands/load_risk_data.py +56 -0
- endoreg_db/mermaid/Overall_flow_patient_finding_intervention.md +10 -0
- endoreg_db/mermaid/anonymized_image_annotation.md +20 -0
- endoreg_db/mermaid/binary_classification_annotation.md +50 -0
- endoreg_db/mermaid/classification.md +8 -0
- endoreg_db/mermaid/examination.md +8 -0
- endoreg_db/mermaid/findings.md +7 -0
- endoreg_db/mermaid/image_classification.md +28 -0
- endoreg_db/mermaid/interventions.md +8 -0
- endoreg_db/mermaid/morphology.md +8 -0
- endoreg_db/mermaid/patient_creation.md +14 -0
- endoreg_db/mermaid/video_segmentation_annotation.md +17 -0
- endoreg_db/migrations/0009_requirementoperator_requirementsettype_and_more.py +154 -0
- endoreg_db/models/__init__.py +20 -0
- endoreg_db/models/ai_model/ai_model.py +0 -13
- endoreg_db/models/ai_model/model_meta.py +2 -12
- endoreg_db/models/data_file/base_classes/abstract_frame.py +0 -2
- endoreg_db/models/data_file/base_classes/abstract_pdf.py +0 -9
- endoreg_db/models/data_file/base_classes/abstract_video.py +7 -8
- endoreg_db/models/data_file/base_classes/utils.py +0 -22
- endoreg_db/models/data_file/frame.py +1 -1
- endoreg_db/models/data_file/import_classes/raw_pdf.py +5 -11
- endoreg_db/models/data_file/import_classes/raw_video.py +6 -4
- endoreg_db/models/data_file/video/video.py +3 -3
- endoreg_db/models/disease.py +88 -19
- endoreg_db/models/event.py +108 -21
- endoreg_db/models/examination/examination_indication.py +108 -29
- endoreg_db/models/examination/examination_type.py +20 -6
- endoreg_db/models/information_source.py +37 -1
- endoreg_db/models/laboratory/lab_value.py +83 -32
- endoreg_db/models/requirement/__init__.py +11 -0
- endoreg_db/models/requirement/requirement.py +325 -0
- endoreg_db/models/requirement/requirement_evaluation/__init__.py +134 -0
- endoreg_db/models/requirement/requirement_evaluation/requirement_type_parser.py +102 -0
- endoreg_db/models/requirement/requirement_operator.py +58 -0
- endoreg_db/models/requirement/requirement_set.py +127 -0
- endoreg_db/models/risk/__init__.py +7 -0
- endoreg_db/models/risk/risk.py +72 -0
- endoreg_db/models/risk/risk_type.py +55 -0
- endoreg_db/serializers/raw_pdf_anony_text_validation.py +137 -0
- endoreg_db/serializers/raw_pdf_meta_validation.py +223 -0
- endoreg_db/serializers/raw_video_meta_validation.py +163 -1
- endoreg_db/serializers/video_segmentation.py +208 -126
- endoreg_db/urls.py +127 -14
- endoreg_db/utils/__init__.py +43 -0
- endoreg_db/utils/dataloader.py +38 -19
- endoreg_db/utils/hashs.py +1 -0
- endoreg_db/utils/paths.py +86 -0
- endoreg_db/views/raw_pdf_anony_text_validation_views.py +95 -0
- endoreg_db/views/raw_pdf_meta_validation_views.py +111 -0
- endoreg_db/views/raw_video_meta_validation_views.py +128 -18
- endoreg_db/views/video_segmentation_views.py +28 -11
- endoreg_db/views/views.py +107 -0
- {endoreg_db-0.6.2.dist-info → endoreg_db-0.6.4.dist-info}/METADATA +1 -1
- {endoreg_db-0.6.2.dist-info → endoreg_db-0.6.4.dist-info}/RECORD +96 -46
- endoreg_db/management/commands/load_name_data.py +0 -37
- {endoreg_db-0.6.2.dist-info → endoreg_db-0.6.4.dist-info}/WHEEL +0 -0
- {endoreg_db-0.6.2.dist-info → endoreg_db-0.6.4.dist-info}/licenses/LICENSE +0 -0
endoreg_db/urls.py
CHANGED
|
@@ -7,23 +7,32 @@ from .views.patient_views import (
|
|
|
7
7
|
get_morphology_choices,
|
|
8
8
|
)
|
|
9
9
|
from .views.csrf import csrf_token_view
|
|
10
|
-
#
|
|
10
|
+
# endoreg_db_production/endoreg_db/urls.py
|
|
11
|
+
from endoreg_db.views.views import (
|
|
12
|
+
VideoView,
|
|
13
|
+
keycloak_login,
|
|
14
|
+
keycloak_callback,
|
|
15
|
+
public_home,
|
|
16
|
+
)#from .views.feature_selection_view import FetchSingleFramePredictionView // its implemented in ando-ai other project need to add here
|
|
11
17
|
from .views.video_segmentation_views import VideoView, VideoLabelView,UpdateLabelSegmentsView
|
|
12
18
|
from .views.views_for_timeline import video_timeline_view
|
|
13
|
-
from .views.raw_video_meta_validation_views import VideoFileForMetaView
|
|
19
|
+
from .views.raw_video_meta_validation_views import VideoFileForMetaView, VideoFileForMetaView
|
|
20
|
+
from .views.raw_pdf_meta_validation_views import PDFFileForMetaView
|
|
21
|
+
from .views.raw_pdf_meta_validation_views import UpdateSensitiveMetaView
|
|
22
|
+
from .views.raw_pdf_anony_text_validation_views import RawPdfAnonyTextView, UpdateAnonymizedTextView
|
|
14
23
|
router = DefaultRouter()
|
|
15
24
|
router.register(r'patients', PatientViewSet)
|
|
16
25
|
|
|
26
|
+
|
|
17
27
|
urlpatterns = [
|
|
18
28
|
path('start-examination/', start_examination, name="start_examination"),
|
|
19
29
|
path('get-location-choices/<int:location_id>/', get_location_choices, name="get_location_choices"),
|
|
20
30
|
path('get-morphology-choices/<int:morphology_id>/', get_morphology_choices, name="get_morphology_choices"),
|
|
21
31
|
path('api/', include(router.urls)),
|
|
22
32
|
path('api/conf/', csrf_token_view, name='csrf_token'),
|
|
23
|
-
|
|
24
33
|
|
|
25
34
|
|
|
26
|
-
#--------------------------------------VIDEO SEGMENTATION END POINTS--------------------------------------
|
|
35
|
+
#--------------------------------------START : VIDEO SEGMENTATION END POINTS--------------------------------------
|
|
27
36
|
|
|
28
37
|
# The dropdown contains video names and their corresponding IDs, which are retrieved from the database(RawVideoFile). Additionally, this route(api/videos) also fetches labels along with their names and IDs from the label table.
|
|
29
38
|
# We will modify this implementation later as per our requirements.
|
|
@@ -31,7 +40,7 @@ urlpatterns = [
|
|
|
31
40
|
# will be displayed on the frontend using route(api/video/<int:video_id>/).
|
|
32
41
|
# once label is selected from the dropdown,using its name, details can be fetched from rawvideofile using route("api/video/<int:video_id>/label/<str:label_name>/)
|
|
33
42
|
#If editing is required, a form will be available for each label. This form dynamically updates when the selected label changes. It will display all segments associated with the selected label, each with a delete option. Below these segments, there may be a button for adding more segments.
|
|
34
|
-
#If any values in the form are modified, the updated data will be saved in the
|
|
43
|
+
#If any values in the form are modified, the updated data will be saved in the dafrom .views import VideoView, keycloak_login, keycloak_callbacktabase table.
|
|
35
44
|
|
|
36
45
|
|
|
37
46
|
|
|
@@ -42,7 +51,15 @@ urlpatterns = [
|
|
|
42
51
|
# Example request: GET /api/videos/
|
|
43
52
|
# Response: Returns a JSON list of videos (id, file path, video_url, sequences, label_names).
|
|
44
53
|
# it also fetch the label from teh label table
|
|
45
|
-
|
|
54
|
+
|
|
55
|
+
# for kaycloak:-
|
|
56
|
+
# /api/videos/ , protected API
|
|
57
|
+
# /login/ , sends user to Keycloak login page
|
|
58
|
+
# /login/callback/ , where Keycloak sends the user after login
|
|
59
|
+
path('', public_home, name='public_home'),
|
|
60
|
+
path('api/videos/', VideoView.as_view(), name='video_list'),
|
|
61
|
+
path('login/', keycloak_login, name='keycloak_login'),
|
|
62
|
+
path('login/callback/', keycloak_callback, name='keycloak_callback'),
|
|
46
63
|
|
|
47
64
|
#need to change this route
|
|
48
65
|
#path('api/prediction/', FetchSingleFramePredictionView.as_view(), name='fetch-single-frame-prediction'),#.as_view() converts the class-based view into a function that Django can use for routing.
|
|
@@ -153,21 +170,117 @@ urlpatterns = [
|
|
|
153
170
|
#
|
|
154
171
|
path("api/video/<int:video_id>/label/<int:label_id>/update_segments/", UpdateLabelSegmentsView.as_view(), name="update_label_segments"),
|
|
155
172
|
|
|
156
|
-
#----------------------------------END--VIDEO SEGMENTATION SECTION-------------------------------
|
|
157
|
-
#this is for to test the timeline
|
|
158
|
-
#need to delete this url and also endoreg_db_production/endoreg_db/views/views_for_timeline.py and endoreg_db_production/endoreg_db/templates/timeline.html
|
|
159
|
-
path('video/<int:video_id>/timeline/', video_timeline_view, name='video_timeline'),
|
|
160
173
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
174
|
+
#----------------------------------START : SENSITIVE META AND RAWVIDEOFILE VIDEO PATIENT DETAILS-------------------------------
|
|
175
|
+
|
|
176
|
+
# API Endpoint for fetching video metadata or streaming the next available video
|
|
177
|
+
# This endpoint is used by the frontend to fetch:
|
|
178
|
+
# - The first available video if `last_id` is NOT provided.
|
|
179
|
+
# - The next available video where `id > last_id` if `last_id` is provided.
|
|
180
|
+
# - If `Accept: application/json` is set in headers, it returns video metadata as JSON.
|
|
181
|
+
# - If no videos are available, it returns {"error": "No more videos available."}.
|
|
164
182
|
# const url = lastId ? `http://localhost:8000/api/video/meta/?last_id=${lastId}` : "http://localhost:8000/api/video/meta/";
|
|
165
183
|
path("api/video/sensitivemeta/", VideoFileForMetaView.as_view(), name="video_meta"), # Single endpoint for both first and next video
|
|
166
|
-
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
# This API endpoint allows updating specific patient details (SensitiveMeta)
|
|
188
|
+
# linked to a video. It is used to correct or modify the patient's first name,
|
|
189
|
+
# last name, date of birth, and examination date.
|
|
190
|
+
# Fetch video metadata and update patient details
|
|
191
|
+
# The frontend should send a JSON request body like this:
|
|
192
|
+
# {
|
|
193
|
+
# "sensitive_meta_id": 2, # The ID of the SensitiveMeta entry (REQUIRED)
|
|
194
|
+
# "patient_first_name": "John", # New first name (REQUIRED, cannot be empty)
|
|
195
|
+
# "patient_last_name": "Doe", # New last name (REQUIRED, cannot be empty)
|
|
196
|
+
# "patient_dob": "1985-06-15", # New Date of Birth (REQUIRED, format YYYY-MM-DD)
|
|
197
|
+
# "examination_date": "2024-03-20" # New Examination Date (OPTIONAL, format YYYY-MM-DD)
|
|
198
|
+
# }
|
|
199
|
+
# - The frontend sends a PATCH request to this endpoint with updated patient data.
|
|
200
|
+
# - The backend validates the input using the serializer (`SensitiveMetaUpdateSerializer`).
|
|
201
|
+
# - If validation passes, the patient information is updated in the database.
|
|
202
|
+
# - If there are errors (e.g., missing fields, incorrect date format),
|
|
203
|
+
# the API returns structured error messages.
|
|
204
|
+
path("api/video/update_sensitivemeta/", VideoFileForMetaView.as_view(), name="update_patient_meta"),
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
#----------------------------------START : SENSITIVE META AND RAWPDFOFILE PDF PATIENT DETAILS-------------------------------
|
|
210
|
+
|
|
211
|
+
#The first request (without id) fetches the first available PDF metadata.
|
|
212
|
+
#The "Next" button (with id) fetches the next available PDF.
|
|
213
|
+
#If an id is provided, the API returns the actual PDF file instead of JSON.
|
|
214
|
+
path("api/pdf/sensitivemeta/", PDFFileForMetaView.as_view(), name="pdf_meta"),
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
# This API endpoint allows updating specific patient details (SensitiveMeta)
|
|
220
|
+
# linked to a PDF record. It enables modifying the patient's first name,
|
|
221
|
+
# last name, date of birth, and examination date.
|
|
222
|
+
|
|
223
|
+
# The frontend should send a JSON request body like this:
|
|
224
|
+
# {
|
|
225
|
+
# "sensitive_meta_id": 2, # The ID of the SensitiveMeta entry (REQUIRED)
|
|
226
|
+
# "patient_first_name": "John", # New first name (OPTIONAL, if provided, cannot be empty)
|
|
227
|
+
# "patient_last_name": "Doe", # New last name (OPTIONAL, if provided, cannot be empty)
|
|
228
|
+
# "patient_dob": "1985-06-15", # New Date of Birth (OPTIONAL, format YYYY-MM-DD)
|
|
229
|
+
# "examination_date": "2024-03-20" # New Examination Date (OPTIONAL, format YYYY-MM-DD)
|
|
230
|
+
# }
|
|
231
|
+
|
|
232
|
+
# - The frontend sends a PATCH request to this endpoint with the updated patient data.
|
|
233
|
+
# - The backend processes the request and updates only the fields that are provided.
|
|
234
|
+
# - If validation passes, the corresponding SensitiveMeta entry is updated in the database.
|
|
235
|
+
# - If errors occur (e.g., invalid ID, empty fields, incorrect date format),
|
|
236
|
+
# the API returns structured error messages.
|
|
237
|
+
|
|
238
|
+
path("api/pdf/update_sensitivemeta/", UpdateSensitiveMetaView.as_view(), name="update_pdf_meta"),
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
# API Endpoint for Fetching PDF Data (Including Anonymized Text)
|
|
245
|
+
# - This endpoint is used when the page loads.
|
|
246
|
+
# - Fetches the first available PDF (if no `last_id` is provided).
|
|
247
|
+
# - If `last_id` is given, fetches the next available PDF.
|
|
248
|
+
# - The frontend calls this endpoint on **page load** and when clicking the **next button**.
|
|
249
|
+
# Example frontend usage:
|
|
250
|
+
# const url = lastId ? `http://localhost:8000/api/pdf/anony_text/?last_id=${lastId}`
|
|
251
|
+
# : "http://localhost:8000/api/pdf/anony_text/";
|
|
252
|
+
path("api/pdf/anony_text/", RawPdfAnonyTextView.as_view(), name="pdf_anony_text"),
|
|
253
|
+
|
|
254
|
+
# API Endpoint for Updating the `anonymized_text` Field in `RawPdfFile`
|
|
255
|
+
# - This endpoint is called when the user edits the anonymized text and clicks **Save**.
|
|
256
|
+
# - Updates only the `anonymized_text` field for the specified PDF `id`.
|
|
257
|
+
# - The frontend sends a **PATCH request** to this endpoint with the updated text.
|
|
258
|
+
# Example frontend usage:
|
|
259
|
+
# axios.patch("http://localhost:8000/api/pdf/update_anony_text/", { id: 1, anonymized_text: "Updated text" });
|
|
260
|
+
path("api/pdf/update_anony_text/", UpdateAnonymizedTextView.as_view(), name="update_pdf_anony_text"),
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
|
|
167
264
|
|
|
168
265
|
|
|
169
266
|
|
|
170
267
|
|
|
171
268
|
|
|
172
269
|
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
#this is for, to test the timeline
|
|
277
|
+
#need to delete this url and also endoreg_db_production/endoreg_db/views/views_for_timeline.py and endoreg_db_production/endoreg_db/templates/timeline.html
|
|
278
|
+
path('video/<int:video_id>/timeline/', video_timeline_view, name='video_timeline'),
|
|
279
|
+
]
|
|
280
|
+
|
|
281
|
+
|
|
173
282
|
#https://biigle.de/manual/tutorials/videos/navigating-timeline#for time line example
|
|
283
|
+
from django.conf import settings
|
|
284
|
+
from django.conf.urls.static import static
|
|
285
|
+
if settings.DEBUG:
|
|
286
|
+
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
endoreg_db/utils/__init__.py
CHANGED
|
@@ -11,6 +11,7 @@ from .hashs import (
|
|
|
11
11
|
get_examiner_hash,
|
|
12
12
|
get_video_hash,
|
|
13
13
|
get_patient_examination_hash,
|
|
14
|
+
DJANGO_NAME_SALT
|
|
14
15
|
)
|
|
15
16
|
from .names import (
|
|
16
17
|
create_mock_patient_name,
|
|
@@ -18,6 +19,28 @@ from .names import (
|
|
|
18
19
|
guess_name_gender,
|
|
19
20
|
)
|
|
20
21
|
|
|
22
|
+
from .paths import (
|
|
23
|
+
STORAGE_DIR,
|
|
24
|
+
FRAME_DIR,
|
|
25
|
+
VIDEO_DIR,
|
|
26
|
+
RAW_VIDEO_DIR,
|
|
27
|
+
RAW_FRAME_DIR,
|
|
28
|
+
TEST_RUN,
|
|
29
|
+
TEST_RUN_FRAME_NUMBER,
|
|
30
|
+
FRAME_PROCESSING_BATCH_SIZE,
|
|
31
|
+
WEIGHTS_DIR,
|
|
32
|
+
WEIGHTS_DIR_NAME,
|
|
33
|
+
FRAME_DIR_NAME,
|
|
34
|
+
VIDEO_DIR_NAME,
|
|
35
|
+
STORAGE_DIR_NAME,
|
|
36
|
+
RAW_FRAME_DIR_NAME,
|
|
37
|
+
RAW_VIDEO_DIR_NAME,
|
|
38
|
+
PDF_DIR_NAME,
|
|
39
|
+
PDF_DIR,
|
|
40
|
+
RAW_PDF_DIR,
|
|
41
|
+
RAW_PDF_DIR_NAME,
|
|
42
|
+
)
|
|
43
|
+
|
|
21
44
|
__all__ = [
|
|
22
45
|
"load_model_data_from_yaml",
|
|
23
46
|
"collect_center_names",
|
|
@@ -33,4 +56,24 @@ __all__ = [
|
|
|
33
56
|
"get_pdf_hash",
|
|
34
57
|
"get_video_hash",
|
|
35
58
|
"get_patient_examination_hash",
|
|
59
|
+
"STORAGE_DIR",
|
|
60
|
+
"FRAME_DIR",
|
|
61
|
+
"VIDEO_DIR",
|
|
62
|
+
"RAW_VIDEO_DIR",
|
|
63
|
+
"RAW_FRAME_DIR",
|
|
64
|
+
"TEST_RUN",
|
|
65
|
+
"TEST_RUN_FRAME_NUMBER",
|
|
66
|
+
"FRAME_PROCESSING_BATCH_SIZE",
|
|
67
|
+
"DJANGO_NAME_SALT",
|
|
68
|
+
"WEIGHTS_DIR",
|
|
69
|
+
"WEIGHTS_DIR_NAME",
|
|
70
|
+
"FRAME_DIR_NAME",
|
|
71
|
+
"VIDEO_DIR_NAME",
|
|
72
|
+
"STORAGE_DIR_NAME",
|
|
73
|
+
"RAW_FRAME_DIR_NAME",
|
|
74
|
+
"RAW_VIDEO_DIR_NAME",
|
|
75
|
+
"RAW_PDF_DIR_NAME",
|
|
76
|
+
"PDF_DIR_NAME",
|
|
77
|
+
"PDF_DIR",
|
|
78
|
+
"RAW_PDF_DIR",
|
|
36
79
|
]
|
endoreg_db/utils/dataloader.py
CHANGED
|
@@ -38,15 +38,22 @@ def load_data_with_foreign_keys(
|
|
|
38
38
|
command, model, yaml_data, foreign_keys, foreign_key_models, verbose
|
|
39
39
|
):
|
|
40
40
|
"""
|
|
41
|
-
Load data
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
41
|
+
Load YAML data into Django model instances with FK and M2M support.
|
|
42
|
+
|
|
43
|
+
Processes each YAML entry to create or update a model instance. For each entry, the
|
|
44
|
+
function extracts field data and uses the presence of a 'name' field to decide whether
|
|
45
|
+
to update an existing instance or create a new one. Foreign key fields listed in
|
|
46
|
+
foreign_keys are handled by retrieving related objects via natural keys. When a field
|
|
47
|
+
contains a list, it is treated as a many-to-many relationship and the corresponding
|
|
48
|
+
objects are set after the instance is saved. Missing or unresolved foreign keys trigger
|
|
49
|
+
warnings if verbose output is enabled.
|
|
50
|
+
|
|
51
|
+
Parameters:
|
|
52
|
+
model: The Django model class representing the data.
|
|
53
|
+
yaml_data: A list of dictionaries representing YAML entries.
|
|
54
|
+
foreign_keys: A list of foreign key field names to process from each entry.
|
|
55
|
+
foreign_key_models: The corresponding Django model classes for each foreign key.
|
|
56
|
+
verbose: If True, prints detailed output and warnings during processing.
|
|
50
57
|
"""
|
|
51
58
|
for entry in yaml_data:
|
|
52
59
|
fields = entry.get("fields", {})
|
|
@@ -56,7 +63,10 @@ def load_data_with_foreign_keys(
|
|
|
56
63
|
|
|
57
64
|
# Handle foreign keys and many-to-many relationships
|
|
58
65
|
for fk_field, fk_model in zip(foreign_keys, foreign_key_models):
|
|
59
|
-
#
|
|
66
|
+
# Skip fields that are not in the data
|
|
67
|
+
if fk_field not in fields:
|
|
68
|
+
continue
|
|
69
|
+
|
|
60
70
|
target_keys = fields.pop(fk_field, None)
|
|
61
71
|
|
|
62
72
|
# Ensure the foreign key exists
|
|
@@ -73,18 +83,20 @@ def load_data_with_foreign_keys(
|
|
|
73
83
|
if isinstance(target_keys, list): # Assume many-to-many relationship
|
|
74
84
|
related_objects = []
|
|
75
85
|
for key in target_keys:
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
86
|
+
try:
|
|
87
|
+
obj = fk_model.objects.get_by_natural_key(key)
|
|
88
|
+
except ObjectDoesNotExist:
|
|
89
|
+
if verbose:
|
|
90
|
+
command.stdout.write(
|
|
91
|
+
command.style.WARNING(
|
|
92
|
+
f"{fk_model.__name__} with key {key} not found"
|
|
93
|
+
)
|
|
94
|
+
)
|
|
95
|
+
continue
|
|
81
96
|
related_objects.append(obj)
|
|
82
97
|
m2m_relationships[fk_field] = related_objects
|
|
83
98
|
else: # Single foreign key relationship
|
|
84
99
|
try:
|
|
85
|
-
if model == "endoreg_db.case_template_rule":
|
|
86
|
-
# print(fk_model, target_keys)
|
|
87
|
-
pass
|
|
88
100
|
obj = fk_model.objects.get_by_natural_key(target_keys)
|
|
89
101
|
except ObjectDoesNotExist:
|
|
90
102
|
if verbose:
|
|
@@ -115,4 +127,11 @@ def load_data_with_foreign_keys(
|
|
|
115
127
|
|
|
116
128
|
# Set many-to-many relationships
|
|
117
129
|
for field_name, related_objs in m2m_relationships.items():
|
|
118
|
-
|
|
130
|
+
if related_objs: # Only set if there are objects to set
|
|
131
|
+
getattr(obj, field_name).set(related_objs)
|
|
132
|
+
if verbose:
|
|
133
|
+
command.stdout.write(
|
|
134
|
+
command.style.SUCCESS(
|
|
135
|
+
f"Set {len(related_objs)} {field_name} for {model.__name__} {name}"
|
|
136
|
+
)
|
|
137
|
+
)
|
endoreg_db/utils/hashs.py
CHANGED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
STORAGE_DIR_NAME = os.environ.get("DJANGO_STORAGE_DIR_NAME", "data")
|
|
5
|
+
FRAME_DIR_NAME = os.environ.get("DJANGO_FRAME_DIR_NAME", "db_frames")
|
|
6
|
+
VIDEO_DIR_NAME = os.environ.get("DJANGO_VIDEO_DIR_NAME", "db_videos")
|
|
7
|
+
WEIGHTS_DIR_NAME = os.environ.get("DJANGO_WEIGHTS_DIR_NAME", "db_model_weights")
|
|
8
|
+
PDF_DIR_NAME = os.environ.get("DJANGO_PDF_DIR_NAME", "pdfs")
|
|
9
|
+
|
|
10
|
+
def get_storage_dir(raw: bool = False):
|
|
11
|
+
"""
|
|
12
|
+
Get the storage directory from the environment variable or settings.
|
|
13
|
+
"""
|
|
14
|
+
storage_dir = os.environ.get("DJANGO_STORAGE_DIR", "data")
|
|
15
|
+
storage_dir = Path(storage_dir).expanduser()
|
|
16
|
+
|
|
17
|
+
if raw:
|
|
18
|
+
name = storage_dir.name
|
|
19
|
+
storage_dir = storage_dir.parent / f"raw_{name}"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
if not storage_dir.exists():
|
|
23
|
+
storage_dir.mkdir(parents=True, exist_ok=True)
|
|
24
|
+
return storage_dir
|
|
25
|
+
|
|
26
|
+
def get_frame_dir(raw: bool = False):
|
|
27
|
+
"""
|
|
28
|
+
Get the frame directory from the environment variable or settings.
|
|
29
|
+
"""
|
|
30
|
+
frame_dir_name = os.environ.get("DJANGO_FRAME_DIR_NAME", FRAME_DIR_NAME)
|
|
31
|
+
storage_dir = get_storage_dir(raw)
|
|
32
|
+
frame_dir = storage_dir / frame_dir_name
|
|
33
|
+
if not frame_dir.exists():
|
|
34
|
+
frame_dir.mkdir(parents=True, exist_ok=True)
|
|
35
|
+
return frame_dir
|
|
36
|
+
|
|
37
|
+
def get_video_dir(raw: bool = False):
|
|
38
|
+
"""
|
|
39
|
+
Get the video directory from the environment variable or settings.
|
|
40
|
+
"""
|
|
41
|
+
video_dir_name = os.environ.get("DJANGO_VIDEO_DIR_NAME", VIDEO_DIR_NAME)
|
|
42
|
+
storage_dir = get_storage_dir(raw)
|
|
43
|
+
video_dir = storage_dir / video_dir_name
|
|
44
|
+
if not video_dir.exists():
|
|
45
|
+
video_dir.mkdir(parents=True, exist_ok=True)
|
|
46
|
+
return video_dir
|
|
47
|
+
|
|
48
|
+
def get_weights_dir():
|
|
49
|
+
"""
|
|
50
|
+
Get the weights directory from the environment variable or settings.
|
|
51
|
+
"""
|
|
52
|
+
weights_dir_name = os.environ.get("DJANGO_WEIGHTS_DIR_NAME", WEIGHTS_DIR_NAME)
|
|
53
|
+
storage_dir = get_storage_dir()
|
|
54
|
+
weights_dir = storage_dir / weights_dir_name
|
|
55
|
+
if not weights_dir.exists():
|
|
56
|
+
weights_dir.mkdir(parents=True, exist_ok=True)
|
|
57
|
+
return weights_dir
|
|
58
|
+
|
|
59
|
+
def get_pdf_dir(raw: bool = False):
|
|
60
|
+
"""
|
|
61
|
+
Get the pdf directory from the environment variable or settings.
|
|
62
|
+
"""
|
|
63
|
+
pdf_dir_name = os.environ.get("DJANGO_PDF_DIR_NAME", PDF_DIR_NAME)
|
|
64
|
+
storage_dir = get_storage_dir(raw)
|
|
65
|
+
pdf_dir = storage_dir / pdf_dir_name
|
|
66
|
+
if not pdf_dir.exists():
|
|
67
|
+
pdf_dir.mkdir(parents=True, exist_ok=True)
|
|
68
|
+
return pdf_dir
|
|
69
|
+
|
|
70
|
+
STORAGE_DIR = get_storage_dir()
|
|
71
|
+
FRAME_DIR = get_frame_dir()
|
|
72
|
+
VIDEO_DIR = get_video_dir()
|
|
73
|
+
PDF_DIR = get_pdf_dir()
|
|
74
|
+
PDF_DIR_NAME = PDF_DIR.name
|
|
75
|
+
RAW_VIDEO_DIR = get_video_dir(raw=True)
|
|
76
|
+
RAW_VIDEO_DIR_NAME = RAW_VIDEO_DIR.name
|
|
77
|
+
RAW_FRAME_DIR = get_frame_dir(raw=True)
|
|
78
|
+
RAW_FRAME_DIR_NAME = RAW_FRAME_DIR.name
|
|
79
|
+
RAW_PDF_DIR = get_pdf_dir(raw=True)
|
|
80
|
+
RAW_PDF_DIR_NAME = RAW_PDF_DIR.name
|
|
81
|
+
WEIGHTS_DIR = get_weights_dir()
|
|
82
|
+
TEST_RUN = os.environ.get("TEST_RUN", False)
|
|
83
|
+
TEST_RUN_FRAME_NUMBER = os.environ.get("TEST_RUN_FRAME_NUMBER", 1000)
|
|
84
|
+
# AI Stuff
|
|
85
|
+
FRAME_PROCESSING_BATCH_SIZE = os.environ.get("DJANGO_FRAME_PROCESSING_BATCH_SIZE", 10)
|
|
86
|
+
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from rest_framework.views import APIView
|
|
2
|
+
from rest_framework.response import Response
|
|
3
|
+
from rest_framework import status
|
|
4
|
+
from django.http import FileResponse, Http404
|
|
5
|
+
import os, mimetypes
|
|
6
|
+
from ..models import RawPdfFile
|
|
7
|
+
from ..serializers.raw_pdf_anony_text_validation import RawPdfAnonyTextSerializer
|
|
8
|
+
|
|
9
|
+
class RawPdfAnonyTextView(APIView):
|
|
10
|
+
"""
|
|
11
|
+
API for:
|
|
12
|
+
- Fetching PDF metadata including `anonymized_text`.
|
|
13
|
+
- Fetching the next available PDF.
|
|
14
|
+
- Serving the actual PDF file.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def get(self, request):
|
|
18
|
+
"""
|
|
19
|
+
Handles:
|
|
20
|
+
- First available PDF if `last_id` is NOT provided.
|
|
21
|
+
- Next available PDF if `last_id` is provided.
|
|
22
|
+
- Returns the actual PDF file if `id` is provided.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
pdf_id = request.GET.get("id")
|
|
26
|
+
last_id = request.GET.get("last_id")
|
|
27
|
+
|
|
28
|
+
if pdf_id:
|
|
29
|
+
return self.serve_pdf_file(pdf_id)
|
|
30
|
+
else:
|
|
31
|
+
return self.fetch_pdf_metadata(last_id)
|
|
32
|
+
|
|
33
|
+
def fetch_pdf_metadata(self, last_id):
|
|
34
|
+
"""
|
|
35
|
+
Fetches the next available PDF metadata, including `anonymized_text`.
|
|
36
|
+
"""
|
|
37
|
+
pdf_entry = RawPdfAnonyTextSerializer.get_next_pdf(last_id)
|
|
38
|
+
|
|
39
|
+
if pdf_entry is None:
|
|
40
|
+
return Response({"error": "No more PDFs available."}, status=status.HTTP_404_NOT_FOUND)
|
|
41
|
+
|
|
42
|
+
serialized_pdf = RawPdfAnonyTextSerializer(pdf_entry, context={'request': self.request})
|
|
43
|
+
return Response(serialized_pdf.data, status=status.HTTP_200_OK)
|
|
44
|
+
|
|
45
|
+
def serve_pdf_file(self, pdf_id):
|
|
46
|
+
"""
|
|
47
|
+
Serves the actual PDF file for viewing.
|
|
48
|
+
"""
|
|
49
|
+
try:
|
|
50
|
+
pdf_entry = RawPdfFile.objects.get(id=pdf_id)
|
|
51
|
+
if not pdf_entry.file:
|
|
52
|
+
return Response({"error": "PDF file not found."}, status=status.HTTP_404_NOT_FOUND)
|
|
53
|
+
|
|
54
|
+
full_pdf_path = pdf_entry.file.path
|
|
55
|
+
if not os.path.exists(full_pdf_path):
|
|
56
|
+
raise Http404("PDF file not found on server.")
|
|
57
|
+
|
|
58
|
+
mime_type, _ = mimetypes.guess_type(full_pdf_path)
|
|
59
|
+
response = FileResponse(open(full_pdf_path, "rb"), content_type=mime_type or "application/pdf")
|
|
60
|
+
response["Content-Disposition"] = f'inline; filename="{os.path.basename(full_pdf_path)}"'
|
|
61
|
+
return response
|
|
62
|
+
|
|
63
|
+
except RawPdfFile.DoesNotExist:
|
|
64
|
+
return Response({"error": "Invalid PDF ID."}, status=status.HTTP_400_BAD_REQUEST)
|
|
65
|
+
except Exception as e:
|
|
66
|
+
return Response({"error": f"Internal error: {str(e)}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class UpdateAnonymizedTextView(APIView):
|
|
70
|
+
"""
|
|
71
|
+
API to update only `anonymized_text` in `RawPdfFile`
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
def patch(self, request):
|
|
75
|
+
"""
|
|
76
|
+
Updates `anonymized_text` for a given `pdf_id`.
|
|
77
|
+
"""
|
|
78
|
+
pdf_id = request.data.get("id")
|
|
79
|
+
|
|
80
|
+
if not pdf_id:
|
|
81
|
+
return Response({"error": "pdf_id is required."}, status=status.HTTP_400_BAD_REQUEST)
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
pdf_entry = RawPdfFile.objects.get(id=pdf_id)
|
|
85
|
+
except RawPdfFile.DoesNotExist:
|
|
86
|
+
return Response({"error": "PDF not found."}, status=status.HTTP_404_NOT_FOUND)
|
|
87
|
+
|
|
88
|
+
serializer = RawPdfAnonyTextSerializer(pdf_entry, data=request.data, partial=True)
|
|
89
|
+
|
|
90
|
+
if serializer.is_valid():
|
|
91
|
+
serializer.save()
|
|
92
|
+
return Response({"message": "PDF anonymized_text updated successfully.", "updated_data": serializer.data},
|
|
93
|
+
status=status.HTTP_200_OK)
|
|
94
|
+
|
|
95
|
+
return Response({"error": "Invalid data.", "details": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from rest_framework.views import APIView
|
|
2
|
+
from rest_framework.response import Response
|
|
3
|
+
from rest_framework import status
|
|
4
|
+
from django.http import FileResponse, Http404
|
|
5
|
+
import mimetypes
|
|
6
|
+
import os
|
|
7
|
+
from ..models import RawPdfFile
|
|
8
|
+
from ..serializers.raw_pdf_meta_validation import PDFFileForMetaSerializer
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PDFFileForMetaView(APIView):
|
|
12
|
+
"""
|
|
13
|
+
API endpoint to:
|
|
14
|
+
- Fetch PDF metadata if `id` is NOT provided.
|
|
15
|
+
- Serve the actual PDF file if `id` is provided.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def get(self, request):
|
|
19
|
+
"""
|
|
20
|
+
Handles both:
|
|
21
|
+
Fetching PDF metadata** (if `id` is NOT provided)
|
|
22
|
+
Serving the actual PDF file** (if `id` is provided)
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
pdf_id = request.GET.get("id") # Check if 'id' is provided in the query params
|
|
26
|
+
last_id = request.GET.get("last_id") # Check if 'last_id' is provided for pagination
|
|
27
|
+
|
|
28
|
+
if pdf_id:
|
|
29
|
+
return self.serve_pdf_file(pdf_id) # Serve the actual PDF file
|
|
30
|
+
else:
|
|
31
|
+
return self.fetch_pdf_metadata(last_id) # Fetch metadata for the first or next PDF
|
|
32
|
+
|
|
33
|
+
def fetch_pdf_metadata(self, last_id):
|
|
34
|
+
"""
|
|
35
|
+
Fetches the first or next available PDF metadata.
|
|
36
|
+
"""
|
|
37
|
+
pdf_entry = PDFFileForMetaSerializer.get_next_pdf(last_id)
|
|
38
|
+
|
|
39
|
+
if pdf_entry is None:
|
|
40
|
+
return Response({"error": "No more PDFs available."}, status=status.HTTP_404_NOT_FOUND)
|
|
41
|
+
|
|
42
|
+
serialized_pdf = PDFFileForMetaSerializer(pdf_entry, context={'request': self.request})
|
|
43
|
+
|
|
44
|
+
print("Debugging API Response:")
|
|
45
|
+
print("Serialized Data:", serialized_pdf.data) # Debugging
|
|
46
|
+
return Response(serialized_pdf.data, status=status.HTTP_200_OK)
|
|
47
|
+
|
|
48
|
+
def serve_pdf_file(self, pdf_id):
|
|
49
|
+
"""
|
|
50
|
+
Serves the actual PDF file for download or viewing.
|
|
51
|
+
"""
|
|
52
|
+
try:
|
|
53
|
+
pdf_entry = RawPdfFile.objects.get(id=pdf_id) # Get the PDF file by ID
|
|
54
|
+
|
|
55
|
+
if not pdf_entry.file:
|
|
56
|
+
return Response({"error": "PDF file not found."}, status=status.HTTP_404_NOT_FOUND)
|
|
57
|
+
|
|
58
|
+
full_pdf_path = pdf_entry.file.path # Get the absolute file path
|
|
59
|
+
|
|
60
|
+
if not os.path.exists(full_pdf_path):
|
|
61
|
+
raise Http404("PDF file not found on server.")
|
|
62
|
+
|
|
63
|
+
mime_type, _ = mimetypes.guess_type(full_pdf_path) # Detect file type
|
|
64
|
+
response = FileResponse(open(full_pdf_path, "rb"), content_type=mime_type or "application/pdf")
|
|
65
|
+
|
|
66
|
+
response["Content-Disposition"] = f'inline; filename="{os.path.basename(full_pdf_path)}"' # Allows direct viewing
|
|
67
|
+
|
|
68
|
+
return response # Sends the PDF file as a stream
|
|
69
|
+
|
|
70
|
+
except RawPdfFile.DoesNotExist:
|
|
71
|
+
return Response({"error": "Invalid PDF ID."}, status=status.HTTP_400_BAD_REQUEST)
|
|
72
|
+
|
|
73
|
+
except Exception as e:
|
|
74
|
+
return Response({"error": f"Internal error: {str(e)}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
75
|
+
|
|
76
|
+
from rest_framework.views import APIView
|
|
77
|
+
from rest_framework.response import Response
|
|
78
|
+
from rest_framework import status
|
|
79
|
+
from ..models import SensitiveMeta
|
|
80
|
+
from ..serializers.raw_pdf_meta_validation import SensitiveMetaUpdateSerializer
|
|
81
|
+
|
|
82
|
+
class UpdateSensitiveMetaView(APIView):
|
|
83
|
+
"""
|
|
84
|
+
API endpoint to update patient details in the SensitiveMeta table.
|
|
85
|
+
Handles partial updates (only edited fields).
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
def patch(self, request, *args, **kwargs):
|
|
89
|
+
"""
|
|
90
|
+
Updates the provided fields for a specific patient record.
|
|
91
|
+
Only updates fields that are sent in the request.
|
|
92
|
+
"""
|
|
93
|
+
sensitive_meta_id = request.data.get("sensitive_meta_id") # Required field
|
|
94
|
+
|
|
95
|
+
if not sensitive_meta_id:
|
|
96
|
+
return Response({"error": "sensitive_meta_id is required."}, status=status.HTTP_400_BAD_REQUEST)
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
sensitive_meta = SensitiveMeta.objects.get(id=sensitive_meta_id)
|
|
100
|
+
except SensitiveMeta.DoesNotExist:
|
|
101
|
+
return Response({"error": "Patient record not found."}, status=status.HTTP_404_NOT_FOUND)
|
|
102
|
+
|
|
103
|
+
# Serialize the request data with partial=True to allow partial updates
|
|
104
|
+
serializer = SensitiveMetaUpdateSerializer(sensitive_meta, data=request.data, partial=True)
|
|
105
|
+
|
|
106
|
+
if serializer.is_valid():
|
|
107
|
+
serializer.save()
|
|
108
|
+
return Response({"message": "Patient information updated successfully.", "updated_data": serializer.data},
|
|
109
|
+
status=status.HTTP_200_OK)
|
|
110
|
+
|
|
111
|
+
return Response({"error": "Invalid data.", "details": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
|