endoreg-db 0.8.9.2__py3-none-any.whl → 0.8.9.10__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 (450) hide show
  1. endoreg_db/admin.py +10 -5
  2. endoreg_db/apps.py +4 -7
  3. endoreg_db/authz/auth.py +1 -0
  4. endoreg_db/authz/backends.py +1 -1
  5. endoreg_db/authz/management/commands/list_routes.py +2 -0
  6. endoreg_db/authz/middleware.py +8 -7
  7. endoreg_db/authz/permissions.py +21 -10
  8. endoreg_db/authz/policy.py +14 -19
  9. endoreg_db/authz/views_auth.py +14 -10
  10. endoreg_db/codemods/rename_datetime_fields.py +8 -1
  11. endoreg_db/exceptions.py +5 -2
  12. endoreg_db/forms/__init__.py +0 -1
  13. endoreg_db/forms/examination_form.py +4 -3
  14. endoreg_db/forms/patient_finding_intervention_form.py +30 -8
  15. endoreg_db/forms/patient_form.py +9 -13
  16. endoreg_db/forms/questionnaires/__init__.py +1 -1
  17. endoreg_db/forms/settings/__init__.py +4 -1
  18. endoreg_db/forms/unit.py +2 -1
  19. endoreg_db/helpers/count_db.py +17 -14
  20. endoreg_db/helpers/default_objects.py +2 -1
  21. endoreg_db/helpers/download_segmentation_model.py +4 -3
  22. endoreg_db/helpers/interact.py +0 -5
  23. endoreg_db/helpers/test_video_helper.py +33 -25
  24. endoreg_db/import_files/__init__.py +1 -1
  25. endoreg_db/import_files/context/__init__.py +1 -1
  26. endoreg_db/import_files/context/default_sensitive_meta.py +11 -9
  27. endoreg_db/import_files/context/ensure_center.py +4 -4
  28. endoreg_db/import_files/context/file_lock.py +3 -3
  29. endoreg_db/import_files/context/import_context.py +11 -12
  30. endoreg_db/import_files/context/validate_directories.py +1 -0
  31. endoreg_db/import_files/file_storage/create_report_file.py +57 -34
  32. endoreg_db/import_files/file_storage/create_video_file.py +64 -35
  33. endoreg_db/import_files/file_storage/sensitive_meta_storage.py +5 -2
  34. endoreg_db/import_files/file_storage/state_management.py +89 -122
  35. endoreg_db/import_files/file_storage/storage.py +5 -1
  36. endoreg_db/import_files/processing/report_processing/report_anonymization.py +24 -19
  37. endoreg_db/import_files/processing/sensitive_meta_adapter.py +3 -3
  38. endoreg_db/import_files/processing/video_processing/video_anonymization.py +18 -18
  39. endoreg_db/import_files/pseudonymization/k_anonymity.py +8 -9
  40. endoreg_db/import_files/pseudonymization/k_pseudonymity.py +16 -5
  41. endoreg_db/import_files/report_import_service.py +36 -30
  42. endoreg_db/import_files/video_import_service.py +27 -23
  43. endoreg_db/logger_conf.py +56 -40
  44. endoreg_db/management/__init__.py +1 -1
  45. endoreg_db/management/commands/__init__.py +1 -1
  46. endoreg_db/management/commands/check_auth.py +45 -38
  47. endoreg_db/management/commands/create_model_meta_from_huggingface.py +53 -2
  48. endoreg_db/management/commands/create_multilabel_model_meta.py +54 -19
  49. endoreg_db/management/commands/fix_missing_patient_data.py +105 -71
  50. endoreg_db/management/commands/fix_video_paths.py +75 -54
  51. endoreg_db/management/commands/import_report.py +1 -3
  52. endoreg_db/management/commands/list_routes.py +2 -0
  53. endoreg_db/management/commands/load_ai_model_data.py +8 -2
  54. endoreg_db/management/commands/load_ai_model_label_data.py +0 -1
  55. endoreg_db/management/commands/load_center_data.py +3 -3
  56. endoreg_db/management/commands/load_distribution_data.py +35 -38
  57. endoreg_db/management/commands/load_endoscope_data.py +0 -3
  58. endoreg_db/management/commands/load_examination_data.py +20 -4
  59. endoreg_db/management/commands/load_finding_data.py +18 -3
  60. endoreg_db/management/commands/load_gender_data.py +17 -24
  61. endoreg_db/management/commands/load_green_endoscopy_wuerzburg_data.py +95 -85
  62. endoreg_db/management/commands/load_information_source.py +0 -3
  63. endoreg_db/management/commands/load_lab_value_data.py +14 -3
  64. endoreg_db/management/commands/load_legacy_data.py +303 -0
  65. endoreg_db/management/commands/load_name_data.py +1 -2
  66. endoreg_db/management/commands/load_pdf_type_data.py +4 -8
  67. endoreg_db/management/commands/load_profession_data.py +0 -1
  68. endoreg_db/management/commands/load_report_reader_flag_data.py +0 -4
  69. endoreg_db/management/commands/load_requirement_data.py +6 -2
  70. endoreg_db/management/commands/load_unit_data.py +0 -4
  71. endoreg_db/management/commands/load_user_groups.py +5 -7
  72. endoreg_db/management/commands/model_input.py +169 -0
  73. endoreg_db/management/commands/register_ai_model.py +22 -16
  74. endoreg_db/management/commands/setup_endoreg_db.py +110 -32
  75. endoreg_db/management/commands/storage_management.py +14 -8
  76. endoreg_db/management/commands/summarize_db_content.py +154 -63
  77. endoreg_db/management/commands/train_image_multilabel_model.py +144 -0
  78. endoreg_db/management/commands/validate_video_files.py +82 -50
  79. endoreg_db/management/commands/video_validation.py +4 -6
  80. endoreg_db/migrations/0001_initial.py +112 -63
  81. endoreg_db/models/__init__.py +8 -0
  82. endoreg_db/models/administration/ai/active_model.py +5 -5
  83. endoreg_db/models/administration/ai/ai_model.py +41 -18
  84. endoreg_db/models/administration/ai/model_type.py +1 -0
  85. endoreg_db/models/administration/case/case.py +22 -22
  86. endoreg_db/models/administration/center/__init__.py +5 -5
  87. endoreg_db/models/administration/center/center.py +6 -2
  88. endoreg_db/models/administration/center/center_resource.py +18 -4
  89. endoreg_db/models/administration/center/center_shift.py +3 -1
  90. endoreg_db/models/administration/center/center_waste.py +6 -2
  91. endoreg_db/models/administration/person/__init__.py +1 -1
  92. endoreg_db/models/administration/person/employee/__init__.py +1 -1
  93. endoreg_db/models/administration/person/employee/employee_type.py +3 -1
  94. endoreg_db/models/administration/person/examiner/__init__.py +1 -1
  95. endoreg_db/models/administration/person/examiner/examiner.py +10 -2
  96. endoreg_db/models/administration/person/names/first_name.py +6 -4
  97. endoreg_db/models/administration/person/names/last_name.py +4 -3
  98. endoreg_db/models/administration/person/patient/__init__.py +1 -1
  99. endoreg_db/models/administration/person/patient/patient.py +0 -1
  100. endoreg_db/models/administration/person/patient/patient_external_id.py +0 -1
  101. endoreg_db/models/administration/person/person.py +1 -1
  102. endoreg_db/models/administration/product/__init__.py +7 -6
  103. endoreg_db/models/administration/product/product.py +6 -2
  104. endoreg_db/models/administration/product/product_group.py +9 -7
  105. endoreg_db/models/administration/product/product_material.py +9 -2
  106. endoreg_db/models/administration/product/reference_product.py +64 -15
  107. endoreg_db/models/administration/qualification/qualification.py +3 -1
  108. endoreg_db/models/administration/shift/shift.py +3 -1
  109. endoreg_db/models/administration/shift/shift_type.py +12 -4
  110. endoreg_db/models/aidataset/__init__.py +5 -0
  111. endoreg_db/models/aidataset/aidataset.py +193 -0
  112. endoreg_db/models/label/__init__.py +1 -1
  113. endoreg_db/models/label/label.py +10 -2
  114. endoreg_db/models/label/label_set.py +3 -1
  115. endoreg_db/models/label/label_video_segment/_create_from_video.py +6 -2
  116. endoreg_db/models/label/label_video_segment/label_video_segment.py +148 -44
  117. endoreg_db/models/media/__init__.py +12 -5
  118. endoreg_db/models/media/frame/__init__.py +1 -1
  119. endoreg_db/models/media/frame/frame.py +34 -8
  120. endoreg_db/models/media/pdf/__init__.py +2 -1
  121. endoreg_db/models/media/pdf/raw_pdf.py +11 -4
  122. endoreg_db/models/media/pdf/report_file.py +6 -2
  123. endoreg_db/models/media/pdf/report_reader/__init__.py +3 -3
  124. endoreg_db/models/media/pdf/report_reader/report_reader_flag.py +15 -5
  125. endoreg_db/models/media/video/create_from_file.py +20 -41
  126. endoreg_db/models/media/video/pipe_1.py +75 -30
  127. endoreg_db/models/media/video/pipe_2.py +37 -12
  128. endoreg_db/models/media/video/video_file.py +36 -24
  129. endoreg_db/models/media/video/video_file_ai.py +235 -70
  130. endoreg_db/models/media/video/video_file_anonymize.py +240 -65
  131. endoreg_db/models/media/video/video_file_frames/_bulk_create_frames.py +6 -1
  132. endoreg_db/models/media/video/video_file_frames/_create_frame_object.py +3 -1
  133. endoreg_db/models/media/video/video_file_frames/_delete_frames.py +30 -9
  134. endoreg_db/models/media/video/video_file_frames/_extract_frames.py +95 -29
  135. endoreg_db/models/media/video/video_file_frames/_get_frame.py +13 -3
  136. endoreg_db/models/media/video/video_file_frames/_get_frame_path.py +4 -1
  137. endoreg_db/models/media/video/video_file_frames/_get_frame_paths.py +15 -3
  138. endoreg_db/models/media/video/video_file_frames/_get_frame_range.py +15 -3
  139. endoreg_db/models/media/video/video_file_frames/_get_frames.py +7 -2
  140. endoreg_db/models/media/video/video_file_frames/_initialize_frames.py +109 -23
  141. endoreg_db/models/media/video/video_file_frames/_manage_frame_range.py +111 -27
  142. endoreg_db/models/media/video/video_file_frames/_mark_frames_extracted_status.py +46 -13
  143. endoreg_db/models/media/video/video_file_io.py +85 -33
  144. endoreg_db/models/media/video/video_file_meta/__init__.py +6 -6
  145. endoreg_db/models/media/video/video_file_meta/get_crop_template.py +17 -4
  146. endoreg_db/models/media/video/video_file_meta/get_endo_roi.py +28 -7
  147. endoreg_db/models/media/video/video_file_meta/get_fps.py +46 -13
  148. endoreg_db/models/media/video/video_file_meta/initialize_video_specs.py +81 -20
  149. endoreg_db/models/media/video/video_file_meta/text_meta.py +61 -20
  150. endoreg_db/models/media/video/video_file_meta/video_meta.py +40 -12
  151. endoreg_db/models/media/video/video_file_segments.py +118 -27
  152. endoreg_db/models/media/video/video_metadata.py +25 -6
  153. endoreg_db/models/media/video/video_processing.py +54 -15
  154. endoreg_db/models/medical/__init__.py +3 -13
  155. endoreg_db/models/medical/contraindication/__init__.py +3 -1
  156. endoreg_db/models/medical/disease.py +18 -6
  157. endoreg_db/models/medical/event.py +6 -2
  158. endoreg_db/models/medical/examination/__init__.py +5 -1
  159. endoreg_db/models/medical/examination/examination.py +22 -6
  160. endoreg_db/models/medical/examination/examination_indication.py +23 -7
  161. endoreg_db/models/medical/examination/examination_time.py +6 -2
  162. endoreg_db/models/medical/finding/__init__.py +3 -1
  163. endoreg_db/models/medical/finding/finding.py +37 -12
  164. endoreg_db/models/medical/finding/finding_classification.py +27 -8
  165. endoreg_db/models/medical/finding/finding_intervention.py +19 -6
  166. endoreg_db/models/medical/finding/finding_type.py +3 -1
  167. endoreg_db/models/medical/hardware/__init__.py +1 -1
  168. endoreg_db/models/medical/hardware/endoscope.py +14 -2
  169. endoreg_db/models/medical/laboratory/__init__.py +1 -1
  170. endoreg_db/models/medical/laboratory/lab_value.py +139 -39
  171. endoreg_db/models/medical/medication/__init__.py +7 -3
  172. endoreg_db/models/medical/medication/medication.py +3 -1
  173. endoreg_db/models/medical/medication/medication_indication.py +3 -1
  174. endoreg_db/models/medical/medication/medication_indication_type.py +11 -3
  175. endoreg_db/models/medical/medication/medication_intake_time.py +3 -1
  176. endoreg_db/models/medical/medication/medication_schedule.py +3 -1
  177. endoreg_db/models/medical/patient/__init__.py +2 -10
  178. endoreg_db/models/medical/patient/medication_examples.py +3 -14
  179. endoreg_db/models/medical/patient/patient_disease.py +17 -5
  180. endoreg_db/models/medical/patient/patient_event.py +12 -4
  181. endoreg_db/models/medical/patient/patient_examination.py +52 -15
  182. endoreg_db/models/medical/patient/patient_examination_indication.py +15 -4
  183. endoreg_db/models/medical/patient/patient_finding.py +105 -29
  184. endoreg_db/models/medical/patient/patient_finding_classification.py +41 -12
  185. endoreg_db/models/medical/patient/patient_finding_intervention.py +11 -3
  186. endoreg_db/models/medical/patient/patient_lab_sample.py +6 -2
  187. endoreg_db/models/medical/patient/patient_lab_value.py +42 -10
  188. endoreg_db/models/medical/patient/patient_medication.py +25 -7
  189. endoreg_db/models/medical/patient/patient_medication_schedule.py +34 -10
  190. endoreg_db/models/metadata/model_meta.py +40 -12
  191. endoreg_db/models/metadata/model_meta_logic.py +51 -16
  192. endoreg_db/models/metadata/sensitive_meta.py +65 -28
  193. endoreg_db/models/metadata/sensitive_meta_logic.py +28 -26
  194. endoreg_db/models/metadata/video_meta.py +146 -39
  195. endoreg_db/models/metadata/video_prediction_logic.py +70 -21
  196. endoreg_db/models/metadata/video_prediction_meta.py +80 -27
  197. endoreg_db/models/operation_log.py +63 -0
  198. endoreg_db/models/other/__init__.py +10 -10
  199. endoreg_db/models/other/distribution/__init__.py +9 -7
  200. endoreg_db/models/other/distribution/base_value_distribution.py +3 -1
  201. endoreg_db/models/other/distribution/date_value_distribution.py +19 -5
  202. endoreg_db/models/other/distribution/multiple_categorical_value_distribution.py +3 -1
  203. endoreg_db/models/other/distribution/numeric_value_distribution.py +34 -9
  204. endoreg_db/models/other/emission/__init__.py +1 -1
  205. endoreg_db/models/other/emission/emission_factor.py +9 -3
  206. endoreg_db/models/other/information_source.py +15 -5
  207. endoreg_db/models/other/material.py +3 -1
  208. endoreg_db/models/other/transport_route.py +3 -1
  209. endoreg_db/models/other/unit.py +6 -2
  210. endoreg_db/models/report/report.py +0 -1
  211. endoreg_db/models/requirement/requirement.py +84 -27
  212. endoreg_db/models/requirement/requirement_error.py +5 -6
  213. endoreg_db/models/requirement/requirement_evaluation/__init__.py +1 -1
  214. endoreg_db/models/requirement/requirement_evaluation/evaluate_with_dependencies.py +8 -8
  215. endoreg_db/models/requirement/requirement_evaluation/get_values.py +3 -3
  216. endoreg_db/models/requirement/requirement_evaluation/requirement_type_parser.py +24 -8
  217. endoreg_db/models/requirement/requirement_operator.py +28 -8
  218. endoreg_db/models/requirement/requirement_set.py +34 -11
  219. endoreg_db/models/state/__init__.py +1 -0
  220. endoreg_db/models/state/audit_ledger.py +9 -2
  221. endoreg_db/models/{media → state}/processing_history/__init__.py +1 -3
  222. endoreg_db/models/state/processing_history/processing_history.py +136 -0
  223. endoreg_db/models/state/raw_pdf.py +0 -1
  224. endoreg_db/models/state/video.py +2 -4
  225. endoreg_db/models/utils.py +4 -2
  226. endoreg_db/queries/__init__.py +2 -6
  227. endoreg_db/queries/annotations/__init__.py +1 -3
  228. endoreg_db/queries/annotations/legacy.py +37 -26
  229. endoreg_db/root_urls.py +3 -4
  230. endoreg_db/schemas/examination_evaluation.py +3 -0
  231. endoreg_db/serializers/Frames_NICE_and_PARIS_classifications.py +249 -163
  232. endoreg_db/serializers/__init__.py +2 -8
  233. endoreg_db/serializers/administration/__init__.py +1 -2
  234. endoreg_db/serializers/administration/ai/__init__.py +0 -1
  235. endoreg_db/serializers/administration/ai/active_model.py +3 -1
  236. endoreg_db/serializers/administration/ai/ai_model.py +5 -3
  237. endoreg_db/serializers/administration/ai/model_type.py +3 -1
  238. endoreg_db/serializers/administration/center.py +7 -2
  239. endoreg_db/serializers/administration/gender.py +4 -2
  240. endoreg_db/serializers/anonymization.py +13 -13
  241. endoreg_db/serializers/evaluation/examination_evaluation.py +0 -1
  242. endoreg_db/serializers/examination/__init__.py +1 -1
  243. endoreg_db/serializers/examination/base.py +12 -13
  244. endoreg_db/serializers/examination/dropdown.py +6 -7
  245. endoreg_db/serializers/examination_serializer.py +3 -6
  246. endoreg_db/serializers/finding/__init__.py +1 -1
  247. endoreg_db/serializers/finding/finding.py +14 -7
  248. endoreg_db/serializers/finding_classification/__init__.py +3 -3
  249. endoreg_db/serializers/finding_classification/choice.py +3 -3
  250. endoreg_db/serializers/finding_classification/classification.py +2 -4
  251. endoreg_db/serializers/label_video_segment/__init__.py +5 -3
  252. endoreg_db/serializers/{label → label_video_segment}/image_classification_annotation.py +5 -5
  253. endoreg_db/serializers/label_video_segment/label/__init__.py +6 -0
  254. endoreg_db/serializers/{label → label_video_segment/label}/label.py +1 -1
  255. endoreg_db/serializers/label_video_segment/label_video_segment.py +338 -228
  256. endoreg_db/serializers/meta/__init__.py +1 -2
  257. endoreg_db/serializers/meta/sensitive_meta_detail.py +28 -13
  258. endoreg_db/serializers/meta/sensitive_meta_update.py +51 -46
  259. endoreg_db/serializers/meta/sensitive_meta_verification.py +19 -16
  260. endoreg_db/serializers/misc/__init__.py +2 -2
  261. endoreg_db/serializers/misc/file_overview.py +11 -7
  262. endoreg_db/serializers/misc/stats.py +10 -8
  263. endoreg_db/serializers/misc/translatable_field_mix_in.py +6 -6
  264. endoreg_db/serializers/misc/upload_job.py +32 -29
  265. endoreg_db/serializers/patient/__init__.py +2 -1
  266. endoreg_db/serializers/patient/patient.py +32 -15
  267. endoreg_db/serializers/patient/patient_dropdown.py +11 -3
  268. endoreg_db/serializers/patient_examination/__init__.py +1 -1
  269. endoreg_db/serializers/patient_examination/patient_examination.py +67 -40
  270. endoreg_db/serializers/patient_finding/__init__.py +1 -1
  271. endoreg_db/serializers/patient_finding/patient_finding.py +2 -1
  272. endoreg_db/serializers/patient_finding/patient_finding_classification.py +17 -9
  273. endoreg_db/serializers/patient_finding/patient_finding_detail.py +26 -17
  274. endoreg_db/serializers/patient_finding/patient_finding_intervention.py +7 -5
  275. endoreg_db/serializers/patient_finding/patient_finding_list.py +10 -11
  276. endoreg_db/serializers/patient_finding/patient_finding_write.py +36 -27
  277. endoreg_db/serializers/pdf/__init__.py +1 -3
  278. endoreg_db/serializers/requirements/requirement_schema.py +1 -6
  279. endoreg_db/serializers/sensitive_meta_serializer.py +100 -81
  280. endoreg_db/serializers/video/__init__.py +2 -2
  281. endoreg_db/serializers/video/{segmentation.py → video_file.py} +66 -47
  282. endoreg_db/serializers/video/video_file_brief.py +6 -2
  283. endoreg_db/serializers/video/video_file_detail.py +36 -23
  284. endoreg_db/serializers/video/video_file_list.py +4 -2
  285. endoreg_db/serializers/video/video_processing_history.py +54 -50
  286. endoreg_db/services/__init__.py +1 -1
  287. endoreg_db/services/anonymization.py +2 -2
  288. endoreg_db/services/examination_evaluation.py +40 -17
  289. endoreg_db/services/model_meta_from_hf.py +76 -0
  290. endoreg_db/services/polling_coordinator.py +101 -70
  291. endoreg_db/services/pseudonym_service.py +27 -22
  292. endoreg_db/services/report_import.py +6 -3
  293. endoreg_db/services/segment_sync.py +75 -59
  294. endoreg_db/services/video_import.py +6 -7
  295. endoreg_db/urls/__init__.py +2 -2
  296. endoreg_db/urls/ai.py +7 -25
  297. endoreg_db/urls/anonymization.py +61 -15
  298. endoreg_db/urls/auth.py +4 -4
  299. endoreg_db/urls/classification.py +4 -9
  300. endoreg_db/urls/examination.py +27 -18
  301. endoreg_db/urls/media.py +27 -34
  302. endoreg_db/urls/patient.py +11 -7
  303. endoreg_db/urls/requirements.py +3 -1
  304. endoreg_db/urls/root_urls.py +2 -3
  305. endoreg_db/urls/stats.py +24 -16
  306. endoreg_db/urls/upload.py +3 -11
  307. endoreg_db/utils/__init__.py +14 -15
  308. endoreg_db/utils/ai/__init__.py +1 -1
  309. endoreg_db/utils/ai/data_loader_for_model_input.py +262 -0
  310. endoreg_db/utils/ai/data_loader_for_model_training.py +262 -0
  311. endoreg_db/utils/ai/get.py +2 -1
  312. endoreg_db/utils/ai/inference_dataset.py +14 -15
  313. endoreg_db/utils/ai/model_training/config.py +117 -0
  314. endoreg_db/utils/ai/model_training/dataset.py +74 -0
  315. endoreg_db/utils/ai/model_training/losses.py +68 -0
  316. endoreg_db/utils/ai/model_training/metrics.py +78 -0
  317. endoreg_db/utils/ai/model_training/model_backbones.py +155 -0
  318. endoreg_db/utils/ai/model_training/model_gastronet_resnet.py +118 -0
  319. endoreg_db/utils/ai/model_training/trainer_gastronet_multilabel.py +771 -0
  320. endoreg_db/utils/ai/multilabel_classification_net.py +21 -6
  321. endoreg_db/utils/ai/predict.py +4 -4
  322. endoreg_db/utils/ai/preprocess.py +19 -11
  323. endoreg_db/utils/calc_duration_seconds.py +4 -4
  324. endoreg_db/utils/case_generator/lab_sample_factory.py +3 -4
  325. endoreg_db/utils/check_video_files.py +74 -47
  326. endoreg_db/utils/cropping.py +10 -9
  327. endoreg_db/utils/dataloader.py +11 -3
  328. endoreg_db/utils/dates.py +3 -4
  329. endoreg_db/utils/defaults/set_default_center.py +7 -6
  330. endoreg_db/utils/env.py +6 -2
  331. endoreg_db/utils/extract_specific_frames.py +24 -9
  332. endoreg_db/utils/file_operations.py +30 -18
  333. endoreg_db/utils/fix_video_path_direct.py +57 -41
  334. endoreg_db/utils/frame_anonymization_utils.py +157 -157
  335. endoreg_db/utils/hashs.py +3 -18
  336. endoreg_db/utils/links/requirement_link.py +96 -52
  337. endoreg_db/utils/ocr.py +30 -25
  338. endoreg_db/utils/operation_log.py +61 -0
  339. endoreg_db/utils/parse_and_generate_yaml.py +12 -13
  340. endoreg_db/utils/paths.py +6 -6
  341. endoreg_db/utils/permissions.py +40 -24
  342. endoreg_db/utils/pipelines/process_video_dir.py +50 -26
  343. endoreg_db/utils/product/sum_emissions.py +5 -3
  344. endoreg_db/utils/product/sum_weights.py +4 -2
  345. endoreg_db/utils/pydantic_models/__init__.py +3 -4
  346. endoreg_db/utils/requirement_operator_logic/_old/lab_value_operators.py +207 -107
  347. endoreg_db/utils/requirement_operator_logic/_old/model_evaluators.py +252 -65
  348. endoreg_db/utils/requirement_operator_logic/new_operator_logic.py +27 -10
  349. endoreg_db/utils/setup_config.py +21 -5
  350. endoreg_db/utils/storage.py +3 -1
  351. endoreg_db/utils/translation.py +19 -15
  352. endoreg_db/utils/uuid.py +1 -0
  353. endoreg_db/utils/validate_endo_roi.py +12 -4
  354. endoreg_db/utils/validate_subcategory_dict.py +26 -24
  355. endoreg_db/utils/validate_video_detailed.py +207 -149
  356. endoreg_db/utils/video/__init__.py +7 -3
  357. endoreg_db/utils/video/extract_frames.py +30 -18
  358. endoreg_db/utils/video/names.py +11 -6
  359. endoreg_db/utils/video/streaming_processor.py +175 -101
  360. endoreg_db/utils/video/video_splitter.py +30 -19
  361. endoreg_db/views/Frames_NICE_and_PARIS_classifications_views.py +59 -50
  362. endoreg_db/views/__init__.py +0 -20
  363. endoreg_db/views/anonymization/__init__.py +6 -2
  364. endoreg_db/views/anonymization/media_management.py +2 -6
  365. endoreg_db/views/anonymization/overview.py +34 -1
  366. endoreg_db/views/anonymization/validate.py +79 -18
  367. endoreg_db/views/auth/__init__.py +1 -1
  368. endoreg_db/views/auth/keycloak.py +16 -14
  369. endoreg_db/views/examination/__init__.py +12 -15
  370. endoreg_db/views/examination/examination.py +5 -5
  371. endoreg_db/views/examination/examination_manifest_cache.py +5 -5
  372. endoreg_db/views/examination/get_finding_classification_choices.py +8 -5
  373. endoreg_db/views/examination/get_finding_classifications.py +9 -7
  374. endoreg_db/views/examination/get_findings.py +8 -10
  375. endoreg_db/views/examination/get_instruments.py +3 -2
  376. endoreg_db/views/examination/get_interventions.py +1 -1
  377. endoreg_db/views/finding/__init__.py +2 -2
  378. endoreg_db/views/finding/finding.py +58 -54
  379. endoreg_db/views/finding/get_classifications.py +1 -1
  380. endoreg_db/views/finding/get_interventions.py +1 -1
  381. endoreg_db/views/finding_classification/__init__.py +5 -5
  382. endoreg_db/views/finding_classification/finding_classification.py +5 -6
  383. endoreg_db/views/finding_classification/get_classification_choices.py +3 -4
  384. endoreg_db/views/media/__init__.py +13 -13
  385. endoreg_db/views/media/pdf_media.py +9 -9
  386. endoreg_db/views/media/sensitive_metadata.py +10 -7
  387. endoreg_db/views/media/video_media.py +4 -4
  388. endoreg_db/views/meta/__init__.py +1 -1
  389. endoreg_db/views/meta/sensitive_meta_list.py +20 -22
  390. endoreg_db/views/meta/sensitive_meta_verification.py +14 -11
  391. endoreg_db/views/misc/__init__.py +6 -34
  392. endoreg_db/views/misc/center.py +2 -1
  393. endoreg_db/views/misc/csrf.py +2 -1
  394. endoreg_db/views/misc/gender.py +2 -1
  395. endoreg_db/views/misc/stats.py +141 -106
  396. endoreg_db/views/patient/__init__.py +1 -3
  397. endoreg_db/views/patient/patient.py +141 -99
  398. endoreg_db/views/patient_examination/__init__.py +5 -5
  399. endoreg_db/views/patient_examination/patient_examination.py +43 -42
  400. endoreg_db/views/patient_examination/patient_examination_create.py +10 -15
  401. endoreg_db/views/patient_examination/patient_examination_detail.py +12 -15
  402. endoreg_db/views/patient_examination/patient_examination_list.py +21 -17
  403. endoreg_db/views/patient_examination/video.py +114 -80
  404. endoreg_db/views/patient_finding/__init__.py +1 -1
  405. endoreg_db/views/patient_finding/patient_finding.py +17 -10
  406. endoreg_db/views/patient_finding/patient_finding_optimized.py +127 -95
  407. endoreg_db/views/patient_finding_classification/__init__.py +1 -1
  408. endoreg_db/views/patient_finding_classification/pfc_create.py +35 -27
  409. endoreg_db/views/report/reimport.py +1 -1
  410. endoreg_db/views/report/report_stream.py +5 -8
  411. endoreg_db/views/requirement/__init__.py +2 -1
  412. endoreg_db/views/requirement/evaluate.py +7 -9
  413. endoreg_db/views/requirement/lookup.py +2 -3
  414. endoreg_db/views/requirement/lookup_store.py +0 -1
  415. endoreg_db/views/requirement/requirement_utils.py +2 -4
  416. endoreg_db/views/stats/__init__.py +4 -4
  417. endoreg_db/views/stats/stats_views.py +152 -115
  418. endoreg_db/views/video/__init__.py +18 -27
  419. endoreg_db/views/{ai → video/ai}/__init__.py +2 -2
  420. endoreg_db/views/{ai → video/ai}/label.py +20 -16
  421. endoreg_db/views/video/correction.py +5 -6
  422. endoreg_db/views/video/reimport.py +134 -99
  423. endoreg_db/views/video/segments_crud.py +134 -44
  424. endoreg_db/views/video/video_apply_mask.py +13 -12
  425. endoreg_db/views/video/video_correction.py +2 -1
  426. endoreg_db/views/video/video_download_processed.py +15 -15
  427. endoreg_db/views/video/video_meta_stats.py +7 -6
  428. endoreg_db/views/video/video_processing_history.py +3 -2
  429. endoreg_db/views/video/video_remove_frames.py +13 -12
  430. endoreg_db/views/video/video_stream.py +110 -82
  431. {endoreg_db-0.8.9.2.dist-info → endoreg_db-0.8.9.10.dist-info}/METADATA +9 -3
  432. {endoreg_db-0.8.9.2.dist-info → endoreg_db-0.8.9.10.dist-info}/RECORD +434 -431
  433. endoreg_db/management/commands/import_fallback_video.py +0 -203
  434. endoreg_db/management/commands/import_video.py +0 -422
  435. endoreg_db/management/commands/import_video_with_classification.py +0 -367
  436. endoreg_db/models/media/processing_history/processing_history.py +0 -96
  437. endoreg_db/serializers/label/__init__.py +0 -7
  438. endoreg_db/serializers/label_video_segment/_lvs_create.py +0 -149
  439. endoreg_db/serializers/label_video_segment/_lvs_update.py +0 -138
  440. endoreg_db/serializers/label_video_segment/_lvs_validate.py +0 -149
  441. endoreg_db/serializers/label_video_segment/label_video_segment_annotation.py +0 -99
  442. endoreg_db/serializers/label_video_segment/label_video_segment_update.py +0 -163
  443. endoreg_db/services/__old/pdf_import.py +0 -1487
  444. endoreg_db/services/__old/video_import.py +0 -1306
  445. endoreg_db/tasks/upload_tasks.py +0 -216
  446. endoreg_db/tasks/video_ingest.py +0 -161
  447. endoreg_db/tasks/video_processing_tasks.py +0 -327
  448. endoreg_db/views/misc/translation.py +0 -182
  449. {endoreg_db-0.8.9.2.dist-info → endoreg_db-0.8.9.10.dist-info}/WHEEL +0 -0
  450. {endoreg_db-0.8.9.2.dist-info → endoreg_db-0.8.9.10.dist-info}/licenses/LICENSE +0 -0
@@ -172,7 +172,9 @@ class Command(BaseCommand):
172
172
  raise CommandError(str(exc)) from exc
173
173
 
174
174
  self.stdout.write(
175
- self.style.SUCCESS(f"ModelMeta ready: {model_meta.name} (v{model_meta.version}) for {model_meta.model.name}")
175
+ self.style.SUCCESS(
176
+ f"ModelMeta ready: {model_meta.name} (v{model_meta.version}) for {model_meta.model.name}"
177
+ )
176
178
  )
177
179
 
178
180
  def _create_from_local_file(self, options: Dict[str, Any]) -> ModelMeta:
@@ -213,15 +215,21 @@ class Command(BaseCommand):
213
215
 
214
216
  meta_name = fields.get("name") or options["model_name"]
215
217
  model_name = fields.get("model") or options["model_name"]
216
- labelset_name = fields.get("labelset") or options["image_classification_labelset_name"]
217
- labelset_version = fields.get("labelset_version", options.get("image_classification_labelset_version"))
218
+ labelset_name = (
219
+ fields.get("labelset") or options["image_classification_labelset_name"]
220
+ )
221
+ labelset_version = fields.get(
222
+ "labelset_version", options.get("image_classification_labelset_version")
223
+ )
218
224
 
219
225
  self._ensure_ai_model_exists(model_name)
220
226
  labelset = self._resolve_labelset(labelset_name, labelset_version)
221
227
 
222
228
  requested_version = options.get("model_meta_version") or fields.get("version")
223
229
  if not requested_version:
224
- raise CommandError("Provide --model_meta_version or include a 'version' in the template entry.")
230
+ raise CommandError(
231
+ "Provide --model_meta_version or include a 'version' in the template entry."
232
+ )
225
233
 
226
234
  hf_config = entry.get("setup_config", {}).get("huggingface_fallback", {})
227
235
  repo_id = hf_config.get("repo_id")
@@ -233,7 +241,9 @@ class Command(BaseCommand):
233
241
  )
234
242
 
235
243
  if not filename.endswith(".safetensors"):
236
- raise CommandError("Only .safetensors files are supported when downloading from Hugging Face.")
244
+ raise CommandError(
245
+ "Only .safetensors files are supported when downloading from Hugging Face."
246
+ )
237
247
 
238
248
  token = options.get("huggingface_token")
239
249
 
@@ -273,7 +283,9 @@ class Command(BaseCommand):
273
283
  elif template_name:
274
284
  resolved = (AI_MODEL_META_DATA_DIR / f"{template_name}.yaml").resolve()
275
285
  else: # pragma: no cover - guarded by caller
276
- raise CommandError("Template mode requires --template_path or --template_name.")
286
+ raise CommandError(
287
+ "Template mode requires --template_path or --template_name."
288
+ )
277
289
 
278
290
  if not resolved.exists():
279
291
  raise CommandError(f"Template file not found: {resolved}")
@@ -290,16 +302,22 @@ class Command(BaseCommand):
290
302
  if isinstance(data, list):
291
303
  return [entry for entry in data if isinstance(entry, dict)]
292
304
 
293
- raise CommandError(f"Template {template_path} must define a mapping or list of mappings.")
305
+ raise CommandError(
306
+ f"Template {template_path} must define a mapping or list of mappings."
307
+ )
294
308
 
295
- def _select_template_entry(self, entries: Iterable[Dict[str, Any]], options: Dict[str, Any]) -> Dict[str, Any]:
309
+ def _select_template_entry(
310
+ self, entries: Iterable[Dict[str, Any]], options: Dict[str, Any]
311
+ ) -> Dict[str, Any]:
296
312
  target = options.get("template_entry_name") or options.get("model_name")
297
313
 
298
314
  for entry in entries:
299
315
  fields = entry.get("fields", {})
300
316
  if not fields:
301
317
  continue
302
- if target and (fields.get("name") == target or fields.get("model") == target):
318
+ if target and (
319
+ fields.get("name") == target or fields.get("model") == target
320
+ ):
303
321
  return entry
304
322
 
305
323
  entries = list(entries)
@@ -325,20 +343,33 @@ class Command(BaseCommand):
325
343
  }
326
344
  )
327
345
 
328
- def _collect_template_kwargs(self, fields: Dict[str, Any], options: Dict[str, Any]) -> Dict[str, Any]:
346
+ def _collect_template_kwargs(
347
+ self, fields: Dict[str, Any], options: Dict[str, Any]
348
+ ) -> Dict[str, Any]:
329
349
  def numeric(value):
330
350
  return int(value) if value is not None else value
331
351
 
332
352
  return self._filter_none(
333
353
  {
334
- "activation": fields.get("activation") or options.get("activation_function_name"),
335
- "mean": self._normalise_sequence(fields.get("mean")) or options.get("mean"),
336
- "std": self._normalise_sequence(fields.get("std")) or options.get("std"),
337
- "size_x": numeric(fields.get("size_x")) if fields.get("size_x") is not None else options.get("size_x"),
338
- "size_y": numeric(fields.get("size_y")) if fields.get("size_y") is not None else options.get("size_y"),
354
+ "activation": fields.get("activation")
355
+ or options.get("activation_function_name"),
356
+ "mean": self._normalise_sequence(fields.get("mean"))
357
+ or options.get("mean"),
358
+ "std": self._normalise_sequence(fields.get("std"))
359
+ or options.get("std"),
360
+ "size_x": numeric(fields.get("size_x"))
361
+ if fields.get("size_x") is not None
362
+ else options.get("size_x"),
363
+ "size_y": numeric(fields.get("size_y"))
364
+ if fields.get("size_y") is not None
365
+ else options.get("size_y"),
339
366
  "axes": fields.get("axes") or options.get("axes"),
340
- "batchsize": numeric(fields.get("batchsize")) if fields.get("batchsize") is not None else options.get("batchsize"),
341
- "num_workers": numeric(fields.get("num_workers")) if fields.get("num_workers") is not None else options.get("num_workers"),
367
+ "batchsize": numeric(fields.get("batchsize"))
368
+ if fields.get("batchsize") is not None
369
+ else options.get("batchsize"),
370
+ "num_workers": numeric(fields.get("num_workers"))
371
+ if fields.get("num_workers") is not None
372
+ else options.get("num_workers"),
342
373
  "description": fields.get("description") or options.get("description"),
343
374
  }
344
375
  )
@@ -367,7 +398,9 @@ class Command(BaseCommand):
367
398
  @staticmethod
368
399
  def _ensure_ai_model_exists(model_name: str) -> None:
369
400
  if not AiModel.objects.filter(name=model_name).exists():
370
- raise CommandError(f"AiModel not found: {model_name}. Load ai model data before running this command.")
401
+ raise CommandError(
402
+ f"AiModel not found: {model_name}. Load ai model data before running this command."
403
+ )
371
404
 
372
405
  @staticmethod
373
406
  def _resolve_labelset(name: str, version: Any) -> LabelSet:
@@ -379,6 +412,8 @@ class Command(BaseCommand):
379
412
  labelset = queryset.filter(version=version).first()
380
413
 
381
414
  if not labelset:
382
- raise CommandError(f"LabelSet not found for name='{name}' and version='{version}'.")
415
+ raise CommandError(
416
+ f"LabelSet not found for name='{name}' and version='{version}'."
417
+ )
383
418
 
384
419
  return labelset
@@ -3,10 +3,12 @@ Management command to fix missing patient data in existing videos.
3
3
  Fills in default values for videos that have incomplete SensitiveMeta.
4
4
  """
5
5
 
6
+ from datetime import date
7
+
6
8
  from django.core.management.base import BaseCommand
7
9
  from django.db import transaction
8
- from datetime import date
9
- from endoreg_db.models import VideoFile, SensitiveMeta
10
+
11
+ from endoreg_db.models import SensitiveMeta, VideoFile
10
12
 
11
13
 
12
14
  class Command(BaseCommand):
@@ -20,128 +22,152 @@ class Command(BaseCommand):
20
22
 
21
23
  def add_arguments(self, parser):
22
24
  parser.add_argument(
23
- '--dry-run',
24
- action='store_true',
25
- help='Show what would be changed without making actual changes',
25
+ "--dry-run",
26
+ action="store_true",
27
+ help="Show what would be changed without making actual changes",
26
28
  )
27
29
  parser.add_argument(
28
- '--verbose',
29
- action='store_true',
30
- help='Show detailed output',
30
+ "--verbose",
31
+ action="store_true",
32
+ help="Show detailed output",
31
33
  )
32
34
 
33
35
  def handle(self, *args, **options):
34
- dry_run = options['dry_run']
35
- verbose = options['verbose']
36
-
36
+ dry_run = options["dry_run"]
37
+ verbose = options["verbose"]
38
+
37
39
  self.stdout.write(self.style.SUCCESS("Starting patient data repair..."))
38
-
40
+
39
41
  if dry_run:
40
- self.stdout.write(self.style.WARNING("DRY RUN MODE - No changes will be made"))
41
-
42
+ self.stdout.write(
43
+ self.style.WARNING("DRY RUN MODE - No changes will be made")
44
+ )
45
+
42
46
  # Find videos without SensitiveMeta
43
47
  videos_without_meta = VideoFile.objects.filter(sensitive_meta__isnull=True)
44
48
  count_without_meta = videos_without_meta.count()
45
-
49
+
46
50
  # Find videos with incomplete SensitiveMeta
47
- videos_with_incomplete_meta = VideoFile.objects.filter(
48
- sensitive_meta__isnull=False
49
- ).filter(
50
- # At least one of these fields is missing
51
- sensitive_meta__patient_first_name__isnull=True
52
- ) | VideoFile.objects.filter(
53
- sensitive_meta__isnull=False,
54
- sensitive_meta__patient_last_name__isnull=True
55
- ) | VideoFile.objects.filter(
56
- sensitive_meta__isnull=False,
57
- sensitive_meta__patient_dob__isnull=True
58
- ) | VideoFile.objects.filter(
59
- sensitive_meta__isnull=False,
60
- sensitive_meta__examination_date__isnull=True
61
- ) | VideoFile.objects.filter(
62
- sensitive_meta__isnull=False,
63
- sensitive_meta__patient_first_name__exact=''
64
- ) | VideoFile.objects.filter(
65
- sensitive_meta__isnull=False,
66
- sensitive_meta__patient_last_name__exact=''
51
+ videos_with_incomplete_meta = (
52
+ VideoFile.objects.filter(sensitive_meta__isnull=False).filter(
53
+ # At least one of these fields is missing
54
+ sensitive_meta__patient_first_name__isnull=True
55
+ )
56
+ | VideoFile.objects.filter(
57
+ sensitive_meta__isnull=False,
58
+ sensitive_meta__patient_last_name__isnull=True,
59
+ )
60
+ | VideoFile.objects.filter(
61
+ sensitive_meta__isnull=False, sensitive_meta__patient_dob__isnull=True
62
+ )
63
+ | VideoFile.objects.filter(
64
+ sensitive_meta__isnull=False,
65
+ sensitive_meta__examination_date__isnull=True,
66
+ )
67
+ | VideoFile.objects.filter(
68
+ sensitive_meta__isnull=False,
69
+ sensitive_meta__patient_first_name__exact="",
70
+ )
71
+ | VideoFile.objects.filter(
72
+ sensitive_meta__isnull=False,
73
+ sensitive_meta__patient_last_name__exact="",
74
+ )
67
75
  )
68
-
76
+
69
77
  count_incomplete = videos_with_incomplete_meta.count()
70
-
78
+
71
79
  self.stdout.write(f"Found {count_without_meta} videos without SensitiveMeta")
72
- self.stdout.write(f"Found {count_incomplete} videos with incomplete SensitiveMeta")
73
-
80
+ self.stdout.write(
81
+ f"Found {count_incomplete} videos with incomplete SensitiveMeta"
82
+ )
83
+
74
84
  if count_without_meta == 0 and count_incomplete == 0:
75
- self.stdout.write(self.style.SUCCESS("No repairs needed - all videos have complete patient data!"))
85
+ self.stdout.write(
86
+ self.style.SUCCESS(
87
+ "No repairs needed - all videos have complete patient data!"
88
+ )
89
+ )
76
90
  return
77
-
91
+
78
92
  fixed_count = 0
79
93
  created_count = 0
80
-
94
+
81
95
  # Process videos without SensitiveMeta
82
96
  if count_without_meta > 0:
83
- self.stdout.write(f"\nProcessing {count_without_meta} videos without SensitiveMeta...")
84
-
97
+ self.stdout.write(
98
+ f"\nProcessing {count_without_meta} videos without SensitiveMeta..."
99
+ )
100
+
85
101
  for video in videos_without_meta:
86
102
  if verbose:
87
- self.stdout.write(f"Creating SensitiveMeta for video {video.uuid}")
88
-
103
+ self.stdout.write(
104
+ f"Creating SensitiveMeta for video {video.video_hash}"
105
+ )
106
+
89
107
  if not dry_run:
90
108
  try:
91
109
  with transaction.atomic():
92
110
  default_data = {
93
111
  "patient_first_name": "Patient",
94
- "patient_last_name": "Unknown",
112
+ "patient_last_name": "Unknown",
95
113
  "patient_dob": date(1990, 1, 1),
96
114
  "examination_date": date.today(),
97
- "center_name": video.center.name if video.center else "university_hospital_wuerzburg"
115
+ "center_name": video.center.name
116
+ if video.center
117
+ else "university_hospital_wuerzburg",
98
118
  }
99
-
100
- sensitive_meta = SensitiveMeta.create_from_dict(default_data)
119
+
120
+ sensitive_meta = SensitiveMeta.create_from_dict(
121
+ default_data
122
+ )
101
123
  video.sensitive_meta = sensitive_meta
102
- video.save(update_fields=['sensitive_meta'])
124
+ video.save(update_fields=["sensitive_meta"])
103
125
  created_count += 1
104
-
126
+
105
127
  except Exception as e:
106
128
  self.stdout.write(
107
- self.style.ERROR(f"Failed to create SensitiveMeta for video {video.uuid}: {e}")
129
+ self.style.ERROR(
130
+ f"Failed to create SensitiveMeta for video {video.video_hash}: {e}"
131
+ )
108
132
  )
109
133
  else:
110
134
  created_count += 1
111
-
135
+
112
136
  # Process videos with incomplete SensitiveMeta
113
137
  if count_incomplete > 0:
114
- self.stdout.write(f"\nProcessing {count_incomplete} videos with incomplete SensitiveMeta...")
115
-
138
+ self.stdout.write(
139
+ f"\nProcessing {count_incomplete} videos with incomplete SensitiveMeta..."
140
+ )
141
+
116
142
  for video in videos_with_incomplete_meta:
117
143
  if not video.sensitive_meta:
118
144
  continue # Skip if somehow None (already handled above)
119
-
145
+
120
146
  update_data = {}
121
147
  missing_fields = []
122
-
148
+
123
149
  if not video.sensitive_meta.patient_first_name:
124
150
  update_data["patient_first_name"] = "Patient"
125
151
  missing_fields.append("first_name")
126
-
152
+
127
153
  if not video.sensitive_meta.patient_last_name:
128
154
  update_data["patient_last_name"] = "Unknown"
129
155
  missing_fields.append("last_name")
130
-
156
+
131
157
  if not video.sensitive_meta.patient_dob:
132
158
  update_data["patient_dob"] = date(1990, 1, 1)
133
159
  missing_fields.append("dob")
134
-
160
+
135
161
  if not video.sensitive_meta.examination_date:
136
162
  update_data["examination_date"] = date.today()
137
163
  missing_fields.append("examination_date")
138
-
164
+
139
165
  if update_data:
140
166
  if verbose:
141
167
  self.stdout.write(
142
- f"Updating video {video.uuid} - missing fields: {', '.join(missing_fields)}"
168
+ f"Updating video {video.video_hash} - missing fields: {', '.join(missing_fields)}"
143
169
  )
144
-
170
+
145
171
  if not dry_run:
146
172
  try:
147
173
  with transaction.atomic():
@@ -149,24 +175,32 @@ class Command(BaseCommand):
149
175
  fixed_count += 1
150
176
  except Exception as e:
151
177
  self.stdout.write(
152
- self.style.ERROR(f"Failed to update SensitiveMeta for video {video.uuid}: {e}")
178
+ self.style.ERROR(
179
+ f"Failed to update SensitiveMeta for video {video.video_hash}: {e}"
180
+ )
153
181
  )
154
182
  else:
155
183
  fixed_count += 1
156
-
184
+
157
185
  # Summary
158
- self.stdout.write("\n" + "="*50)
186
+ self.stdout.write("\n" + "=" * 50)
159
187
  if dry_run:
160
188
  self.stdout.write(self.style.SUCCESS("DRY RUN SUMMARY:"))
161
189
  self.stdout.write(f"Would create SensitiveMeta for: {created_count} videos")
162
190
  self.stdout.write(f"Would update incomplete data for: {fixed_count} videos")
163
- self.stdout.write(f"Total videos that would be fixed: {created_count + fixed_count}")
191
+ self.stdout.write(
192
+ f"Total videos that would be fixed: {created_count + fixed_count}"
193
+ )
164
194
  else:
165
195
  self.stdout.write(self.style.SUCCESS("REPAIR COMPLETED:"))
166
196
  self.stdout.write(f"Created SensitiveMeta for: {created_count} videos")
167
197
  self.stdout.write(f"Updated incomplete data for: {fixed_count} videos")
168
198
  self.stdout.write(f"Total videos fixed: {created_count + fixed_count}")
169
-
199
+
170
200
  if not dry_run and (created_count > 0 or fixed_count > 0):
171
- self.stdout.write(self.style.SUCCESS("\n✅ Patient data repair completed successfully!"))
172
- self.stdout.write("All videos now have the minimum required patient data for annotation.")
201
+ self.stdout.write(
202
+ self.style.SUCCESS("\n✅ Patient data repair completed successfully!")
203
+ )
204
+ self.stdout.write(
205
+ "All videos now have the minimum required patient data for annotation."
206
+ )
@@ -1,77 +1,82 @@
1
1
  """
2
2
  Django management command to fix video file paths in the database.
3
3
  """
4
+
5
+ import logging
6
+ import os
7
+ from pathlib import Path
8
+
4
9
  from django.core.management.base import BaseCommand
5
10
  from django.db import transaction
11
+
6
12
  from endoreg_db.models import VideoFile
7
- from pathlib import Path
8
- import logging
9
- import os
10
13
 
11
14
  logger = logging.getLogger(__name__)
12
15
 
13
16
 
14
17
  class Command(BaseCommand):
15
- help = 'Fix video file paths in the database to match actual file locations'
18
+ help = "Fix video file paths in the database to match actual file locations"
16
19
 
17
20
  def add_arguments(self, parser):
18
21
  parser.add_argument(
19
- '--video-id',
22
+ "--video-id",
20
23
  type=int,
21
- help='Fix specific video ID only',
24
+ help="Fix specific video ID only",
22
25
  )
23
26
  parser.add_argument(
24
- '--dry-run',
25
- action='store_true',
26
- help='Show what would be changed without making changes',
27
+ "--dry-run",
28
+ action="store_true",
29
+ help="Show what would be changed without making changes",
27
30
  )
28
31
  parser.add_argument(
29
- '--verbose',
30
- action='store_true',
31
- help='Enable verbose output',
32
+ "--verbose",
33
+ action="store_true",
34
+ help="Enable verbose output",
32
35
  )
33
36
  parser.add_argument(
34
- '--storage-dir',
37
+ "--storage-dir",
35
38
  type=str,
36
39
  default=None,
37
- help='Path to the storage directory (default: $ENDOREG_STORAGE_DIR or ./storage)'
40
+ help="Path to the storage directory (default: $ENDOREG_STORAGE_DIR or ./storage)",
38
41
  )
39
42
 
40
43
  def handle(self, *args, **options):
41
44
  """
42
45
  Synchronizes video file paths in the database with actual files on disk, updating broken or missing paths as needed.
43
-
46
+
44
47
  Scans the specified storage directory for video files, matches them to database records by UUID, and updates the `raw_file` field for videos whose stored path is missing or incorrect. Supports dry-run and verbose modes, and can process all videos or a specific video by ID.
45
48
  """
46
- dry_run = options['dry_run']
47
- verbose = options['verbose']
48
- video_id = options.get('video_id')
49
+ dry_run = options["dry_run"]
50
+ verbose = options["verbose"]
51
+ video_id = options.get("video_id")
49
52
 
50
53
  # Determine storage_dir from argument, env, or fallback
51
- storage_dir = options.get('storage_dir') or \
52
- os.environ.get('ENDOREG_STORAGE_DIR') or \
53
- './storage'
54
+ storage_dir = (
55
+ options.get("storage_dir")
56
+ or os.environ.get("ENDOREG_STORAGE_DIR")
57
+ or "./storage"
58
+ )
54
59
  storage_dir = Path(storage_dir)
55
-
60
+
56
61
  # Find all actual video files
57
62
  actual_files = {}
58
- for pattern in ['**/*.mp4', '**/*.avi', '**/*.mov', '**/*.mkv']:
63
+ for pattern in ["**/*.mp4", "**/*.avi", "**/*.mov", "**/*.mkv"]:
59
64
  for file_path in storage_dir.glob(pattern):
60
65
  if file_path.is_file() and file_path.stat().st_size > 0:
61
66
  # Extract UUID from filename
62
67
  filename = file_path.name
63
68
  # UUID is typically the first part before underscore or the whole name
64
- if '_' in filename:
65
- uuid_part = filename.split('_')[0]
69
+ if "_" in filename:
70
+ uuid_part = filename.split("_")[0]
66
71
  else:
67
- uuid_part = filename.split('.')[0]
68
-
72
+ uuid_part = filename.split(".")[0]
73
+
69
74
  # Store relative path from storage directory
70
75
  relative_path = file_path.relative_to(storage_dir)
71
76
  actual_files[uuid_part] = {
72
- 'absolute_path': file_path,
73
- 'relative_path': relative_path,
74
- 'size_mb': file_path.stat().st_size / (1024*1024)
77
+ "absolute_path": file_path,
78
+ "relative_path": relative_path,
79
+ "size_mb": file_path.stat().st_size / (1024 * 1024),
75
80
  }
76
81
 
77
82
  self.stdout.write(f"Found {len(actual_files)} video files in storage")
@@ -82,7 +87,9 @@ class Command(BaseCommand):
82
87
  videos = [VideoFile.objects.get(pk=video_id)]
83
88
  self.stdout.write(f"Processing specific video ID: {video_id}")
84
89
  except VideoFile.DoesNotExist:
85
- self.stdout.write(self.style.ERROR(f"Video with ID {video_id} not found"))
90
+ self.stdout.write(
91
+ self.style.ERROR(f"Video with ID {video_id} not found")
92
+ )
86
93
  return
87
94
  else:
88
95
  videos = VideoFile.objects.all()
@@ -94,52 +101,64 @@ class Command(BaseCommand):
94
101
 
95
102
  for video in videos:
96
103
  try:
97
- uuid_str = str(video.uuid)
98
-
104
+ uuid_str = str(video.video_hash)
105
+
99
106
  # Check if we have a matching file
100
107
  if uuid_str in actual_files:
101
108
  file_info = actual_files[uuid_str]
102
-
109
+
103
110
  # Check current file path
104
111
  current_path_exists = False
105
112
  current_path = None
106
-
107
- if hasattr(video, 'raw_file') and video.raw_file:
113
+
114
+ if hasattr(video, "raw_file") and video.raw_file:
108
115
  try:
109
116
  current_path = Path(video.raw_file.path)
110
117
  current_path_exists = current_path.exists()
111
118
  except (ValueError, AttributeError, OSError):
112
119
  current_path_exists = False
113
-
120
+
114
121
  if not current_path_exists:
115
122
  # File path is broken, fix it
116
123
  if verbose:
117
124
  self.stdout.write(f"Video {video.id} ({uuid_str}):")
118
- self.stdout.write(f" Current: {current_path or 'None'} (broken)")
119
- self.stdout.write(f" Found: {file_info['absolute_path']} ({file_info['size_mb']:.1f} MB)")
120
-
125
+ self.stdout.write(
126
+ f" Current: {current_path or 'None'} (broken)"
127
+ )
128
+ self.stdout.write(
129
+ f" Found: {file_info['absolute_path']} ({file_info['size_mb']:.1f} MB)"
130
+ )
131
+
121
132
  if not dry_run:
122
133
  with transaction.atomic():
123
134
  # Update the raw_file path
124
- video.raw_file.name = str(file_info['relative_path'])
125
- video.save(update_fields=['raw_file'])
126
-
135
+ video.raw_file.name = str(file_info["relative_path"])
136
+ video.save(update_fields=["raw_file"])
137
+
127
138
  self.stdout.write(
128
- self.style.SUCCESS(f"✅ Fixed video {video.id}: {file_info['relative_path']}")
139
+ self.style.SUCCESS(
140
+ f"✅ Fixed video {video.id}: {file_info['relative_path']}"
141
+ )
129
142
  )
130
143
  else:
131
144
  self.stdout.write(
132
- self.style.WARNING(f"🔄 Would fix video {video.id}: {file_info['relative_path']}")
145
+ self.style.WARNING(
146
+ f"🔄 Would fix video {video.id}: {file_info['relative_path']}"
147
+ )
133
148
  )
134
-
149
+
135
150
  fixed_count += 1
136
151
  else:
137
152
  if verbose:
138
- self.stdout.write(f"✅ Video {video.id} ({uuid_str}) already has correct path")
153
+ self.stdout.write(
154
+ f"✅ Video {video.id} ({uuid_str}) already has correct path"
155
+ )
139
156
  skipped_count += 1
140
157
  else:
141
158
  if verbose:
142
- self.stdout.write(f"❌ Video {video.id} ({uuid_str}): No matching file found")
159
+ self.stdout.write(
160
+ f"❌ Video {video.id} ({uuid_str}): No matching file found"
161
+ )
143
162
  error_count += 1
144
163
 
145
164
  except Exception as e:
@@ -149,17 +168,19 @@ class Command(BaseCommand):
149
168
  error_count += 1
150
169
 
151
170
  # Summary
152
- self.stdout.write(f"\n{'='*50}")
171
+ self.stdout.write(f"\n{'=' * 50}")
153
172
  self.stdout.write(self.style.SUCCESS("SUMMARY"))
154
- self.stdout.write(f"{'='*50}")
155
-
173
+ self.stdout.write(f"{'=' * 50}")
174
+
156
175
  action_word = "Would fix" if dry_run else "Fixed"
157
176
  self.stdout.write(f"🔧 {action_word}: {fixed_count} videos")
158
177
  self.stdout.write(f"✅ Already correct: {skipped_count} videos")
159
178
  self.stdout.write(f"❌ Errors/Missing files: {error_count} videos")
160
-
179
+
161
180
  if dry_run and fixed_count > 0:
162
181
  self.stdout.write("\n💡 Run without --dry-run to apply changes")
163
182
  elif not dry_run and fixed_count > 0:
164
- self.stdout.write(f"\n🎉 Successfully fixed {fixed_count} video file paths!")
165
- self.stdout.write("🔄 Restart your Django server to reload file paths")
183
+ self.stdout.write(
184
+ f"\n🎉 Successfully fixed {fixed_count} video file paths!"
185
+ )
186
+ self.stdout.write("🔄 Restart your Django server to reload file paths")