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.

Files changed (97) hide show
  1. endoreg_db/data/__init__.py +14 -0
  2. endoreg_db/data/disease_classification/chronic_kidney_disease.yaml +2 -2
  3. endoreg_db/data/disease_classification_choice/chronic_kidney_disease.yaml +6 -6
  4. endoreg_db/data/distribution/numeric/data.yaml +1 -1
  5. endoreg_db/data/examination/examinations/data.yaml +22 -21
  6. endoreg_db/data/examination/type/data.yaml +12 -0
  7. endoreg_db/data/examination_indication/endoscopy.yaml +417 -1
  8. endoreg_db/data/examination_indication_classification/endoscopy.yaml +157 -5
  9. endoreg_db/data/finding/data.yaml +18 -11
  10. endoreg_db/data/finding_intervention/endoscopy.yaml +26 -121
  11. endoreg_db/data/finding_intervention/endoscopy_colonoscopy.yaml +163 -0
  12. endoreg_db/data/finding_intervention/endoscopy_egd.yaml +128 -0
  13. endoreg_db/data/finding_intervention/endoscopy_ercp.yaml +32 -0
  14. endoreg_db/data/finding_intervention/endoscopy_eus_lower.yaml +9 -0
  15. endoreg_db/data/finding_intervention/endoscopy_eus_upper.yaml +36 -0
  16. endoreg_db/data/information_source/endoscopy_guidelines.yaml +7 -0
  17. endoreg_db/data/medication_indication/anticoagulation.yaml +4 -4
  18. endoreg_db/data/pdf_type/data.yaml +9 -16
  19. endoreg_db/data/requirement/colonoscopy_indications.yaml +56 -0
  20. endoreg_db/data/requirement/disease_cardiovascular.yaml +79 -0
  21. endoreg_db/data/requirement/disease_classification_choice_cardiovascular.yaml +38 -0
  22. endoreg_db/data/requirement/disease_hepatology.yaml +12 -0
  23. endoreg_db/data/requirement/disease_misc.yaml +12 -0
  24. endoreg_db/data/requirement/disease_renal.yaml +80 -0
  25. endoreg_db/data/requirement/event_cardiology.yaml +251 -0
  26. endoreg_db/data/requirement/lab_value.yaml +120 -0
  27. endoreg_db/data/requirement_operator/lab_operators.yaml +128 -0
  28. endoreg_db/data/requirement_operator/model_operators.yaml +90 -0
  29. endoreg_db/data/requirement_set/endoscopy_bleeding_risk.yaml +12 -0
  30. endoreg_db/data/requirement_set_type/data.yaml +20 -0
  31. endoreg_db/data/requirement_type/requirement_types.yaml +83 -0
  32. endoreg_db/data/risk/bleeding.yaml +26 -0
  33. endoreg_db/data/risk/thrombosis.yaml +37 -0
  34. endoreg_db/data/risk_type/data.yaml +27 -0
  35. endoreg_db/data/unit/time.yaml +36 -1
  36. endoreg_db/management/commands/load_base_db_data.py +14 -1
  37. endoreg_db/management/commands/load_center_data.py +46 -21
  38. endoreg_db/management/commands/load_examination_indication_data.py +49 -27
  39. endoreg_db/management/commands/load_requirement_data.py +156 -0
  40. endoreg_db/management/commands/load_risk_data.py +56 -0
  41. endoreg_db/mermaid/Overall_flow_patient_finding_intervention.md +10 -0
  42. endoreg_db/mermaid/anonymized_image_annotation.md +20 -0
  43. endoreg_db/mermaid/binary_classification_annotation.md +50 -0
  44. endoreg_db/mermaid/classification.md +8 -0
  45. endoreg_db/mermaid/examination.md +8 -0
  46. endoreg_db/mermaid/findings.md +7 -0
  47. endoreg_db/mermaid/image_classification.md +28 -0
  48. endoreg_db/mermaid/interventions.md +8 -0
  49. endoreg_db/mermaid/morphology.md +8 -0
  50. endoreg_db/mermaid/patient_creation.md +14 -0
  51. endoreg_db/mermaid/video_segmentation_annotation.md +17 -0
  52. endoreg_db/migrations/0009_requirementoperator_requirementsettype_and_more.py +154 -0
  53. endoreg_db/models/__init__.py +20 -0
  54. endoreg_db/models/ai_model/ai_model.py +0 -13
  55. endoreg_db/models/ai_model/model_meta.py +2 -12
  56. endoreg_db/models/data_file/base_classes/abstract_frame.py +0 -2
  57. endoreg_db/models/data_file/base_classes/abstract_pdf.py +0 -9
  58. endoreg_db/models/data_file/base_classes/abstract_video.py +7 -8
  59. endoreg_db/models/data_file/base_classes/utils.py +0 -22
  60. endoreg_db/models/data_file/frame.py +1 -1
  61. endoreg_db/models/data_file/import_classes/raw_pdf.py +5 -11
  62. endoreg_db/models/data_file/import_classes/raw_video.py +6 -4
  63. endoreg_db/models/data_file/video/video.py +3 -3
  64. endoreg_db/models/disease.py +88 -19
  65. endoreg_db/models/event.py +108 -21
  66. endoreg_db/models/examination/examination_indication.py +108 -29
  67. endoreg_db/models/examination/examination_type.py +20 -6
  68. endoreg_db/models/information_source.py +37 -1
  69. endoreg_db/models/laboratory/lab_value.py +83 -32
  70. endoreg_db/models/requirement/__init__.py +11 -0
  71. endoreg_db/models/requirement/requirement.py +325 -0
  72. endoreg_db/models/requirement/requirement_evaluation/__init__.py +134 -0
  73. endoreg_db/models/requirement/requirement_evaluation/requirement_type_parser.py +102 -0
  74. endoreg_db/models/requirement/requirement_operator.py +58 -0
  75. endoreg_db/models/requirement/requirement_set.py +127 -0
  76. endoreg_db/models/risk/__init__.py +7 -0
  77. endoreg_db/models/risk/risk.py +72 -0
  78. endoreg_db/models/risk/risk_type.py +55 -0
  79. endoreg_db/serializers/raw_pdf_anony_text_validation.py +137 -0
  80. endoreg_db/serializers/raw_pdf_meta_validation.py +223 -0
  81. endoreg_db/serializers/raw_video_meta_validation.py +163 -1
  82. endoreg_db/serializers/video_segmentation.py +208 -126
  83. endoreg_db/urls.py +127 -14
  84. endoreg_db/utils/__init__.py +43 -0
  85. endoreg_db/utils/dataloader.py +38 -19
  86. endoreg_db/utils/hashs.py +1 -0
  87. endoreg_db/utils/paths.py +86 -0
  88. endoreg_db/views/raw_pdf_anony_text_validation_views.py +95 -0
  89. endoreg_db/views/raw_pdf_meta_validation_views.py +111 -0
  90. endoreg_db/views/raw_video_meta_validation_views.py +128 -18
  91. endoreg_db/views/video_segmentation_views.py +28 -11
  92. endoreg_db/views/views.py +107 -0
  93. {endoreg_db-0.6.2.dist-info → endoreg_db-0.6.4.dist-info}/METADATA +1 -1
  94. {endoreg_db-0.6.2.dist-info → endoreg_db-0.6.4.dist-info}/RECORD +96 -46
  95. endoreg_db/management/commands/load_name_data.py +0 -37
  96. {endoreg_db-0.6.2.dist-info → endoreg_db-0.6.4.dist-info}/WHEEL +0 -0
  97. {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
- #from .views.feature_selection_view import FetchSingleFramePredictionView // its implemented in ando-ai other project need to add here
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 database table.
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
- path("api/videos/", VideoView.as_view(), name="video_list"),
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)
@@ -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
  ]
@@ -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 handling foreign keys and many-to-many relationships.
42
-
43
- Args:
44
- command: Command object for stdout writing.
45
- model: The Django model for the data.
46
- yaml_data: Data loaded from YAML.
47
- foreign_keys: List of foreign keys.
48
- foreign_key_models: Corresponding models for the foreign keys.
49
- verbose: Boolean indicating whether to print verbose output.
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
- # print(fk_field, fk_model)
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
- obj, created = fk_model.objects.get_or_create(name=key)
77
- if created and verbose:
78
- command.stdout.write(
79
- command.style.SUCCESS(f"Created {fk_model.__name__} {key}")
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
- getattr(obj, field_name).set(related_objs)
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
@@ -5,6 +5,7 @@ from datetime import datetime, date
5
5
  import os
6
6
 
7
7
  SALT = os.getenv("DJANGO_SALT", "default_salt")
8
+ DJANGO_NAME_SALT = os.environ.get("DJANGO_SALT", "default_salt")
8
9
 
9
10
 
10
11
  def get_video_hash(video_path):
@@ -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)