endoreg-db 0.8.8.9__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 (453) 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 +146 -83
  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/migrations/__init__.py +0 -0
  82. endoreg_db/models/__init__.py +8 -0
  83. endoreg_db/models/administration/ai/active_model.py +5 -5
  84. endoreg_db/models/administration/ai/ai_model.py +41 -18
  85. endoreg_db/models/administration/ai/model_type.py +1 -0
  86. endoreg_db/models/administration/case/case.py +22 -22
  87. endoreg_db/models/administration/center/__init__.py +5 -5
  88. endoreg_db/models/administration/center/center.py +6 -2
  89. endoreg_db/models/administration/center/center_resource.py +18 -4
  90. endoreg_db/models/administration/center/center_shift.py +3 -1
  91. endoreg_db/models/administration/center/center_waste.py +6 -2
  92. endoreg_db/models/administration/person/__init__.py +1 -1
  93. endoreg_db/models/administration/person/employee/__init__.py +1 -1
  94. endoreg_db/models/administration/person/employee/employee_type.py +3 -1
  95. endoreg_db/models/administration/person/examiner/__init__.py +1 -1
  96. endoreg_db/models/administration/person/examiner/examiner.py +10 -2
  97. endoreg_db/models/administration/person/names/first_name.py +6 -4
  98. endoreg_db/models/administration/person/names/last_name.py +4 -3
  99. endoreg_db/models/administration/person/patient/__init__.py +1 -1
  100. endoreg_db/models/administration/person/patient/patient.py +0 -1
  101. endoreg_db/models/administration/person/patient/patient_external_id.py +0 -1
  102. endoreg_db/models/administration/person/person.py +1 -1
  103. endoreg_db/models/administration/product/__init__.py +7 -6
  104. endoreg_db/models/administration/product/product.py +6 -2
  105. endoreg_db/models/administration/product/product_group.py +9 -7
  106. endoreg_db/models/administration/product/product_material.py +9 -2
  107. endoreg_db/models/administration/product/reference_product.py +64 -15
  108. endoreg_db/models/administration/qualification/qualification.py +3 -1
  109. endoreg_db/models/administration/shift/shift.py +3 -1
  110. endoreg_db/models/administration/shift/shift_type.py +12 -4
  111. endoreg_db/models/aidataset/__init__.py +5 -0
  112. endoreg_db/models/aidataset/aidataset.py +193 -0
  113. endoreg_db/models/label/__init__.py +1 -1
  114. endoreg_db/models/label/label.py +10 -2
  115. endoreg_db/models/label/label_set.py +3 -1
  116. endoreg_db/models/label/label_video_segment/_create_from_video.py +6 -2
  117. endoreg_db/models/label/label_video_segment/label_video_segment.py +148 -44
  118. endoreg_db/models/media/__init__.py +12 -5
  119. endoreg_db/models/media/frame/__init__.py +1 -1
  120. endoreg_db/models/media/frame/frame.py +34 -8
  121. endoreg_db/models/media/pdf/__init__.py +2 -1
  122. endoreg_db/models/media/pdf/raw_pdf.py +11 -4
  123. endoreg_db/models/media/pdf/report_file.py +6 -2
  124. endoreg_db/models/media/pdf/report_reader/__init__.py +3 -3
  125. endoreg_db/models/media/pdf/report_reader/report_reader_flag.py +15 -5
  126. endoreg_db/models/media/video/create_from_file.py +20 -41
  127. endoreg_db/models/media/video/pipe_1.py +75 -30
  128. endoreg_db/models/media/video/pipe_2.py +37 -12
  129. endoreg_db/models/media/video/video_file.py +36 -24
  130. endoreg_db/models/media/video/video_file_ai.py +235 -70
  131. endoreg_db/models/media/video/video_file_anonymize.py +240 -65
  132. endoreg_db/models/media/video/video_file_frames/_bulk_create_frames.py +6 -1
  133. endoreg_db/models/media/video/video_file_frames/_create_frame_object.py +3 -1
  134. endoreg_db/models/media/video/video_file_frames/_delete_frames.py +30 -9
  135. endoreg_db/models/media/video/video_file_frames/_extract_frames.py +95 -29
  136. endoreg_db/models/media/video/video_file_frames/_get_frame.py +13 -3
  137. endoreg_db/models/media/video/video_file_frames/_get_frame_path.py +4 -1
  138. endoreg_db/models/media/video/video_file_frames/_get_frame_paths.py +15 -3
  139. endoreg_db/models/media/video/video_file_frames/_get_frame_range.py +15 -3
  140. endoreg_db/models/media/video/video_file_frames/_get_frames.py +7 -2
  141. endoreg_db/models/media/video/video_file_frames/_initialize_frames.py +109 -23
  142. endoreg_db/models/media/video/video_file_frames/_manage_frame_range.py +111 -27
  143. endoreg_db/models/media/video/video_file_frames/_mark_frames_extracted_status.py +46 -13
  144. endoreg_db/models/media/video/video_file_io.py +85 -33
  145. endoreg_db/models/media/video/video_file_meta/__init__.py +6 -6
  146. endoreg_db/models/media/video/video_file_meta/get_crop_template.py +17 -4
  147. endoreg_db/models/media/video/video_file_meta/get_endo_roi.py +28 -7
  148. endoreg_db/models/media/video/video_file_meta/get_fps.py +46 -13
  149. endoreg_db/models/media/video/video_file_meta/initialize_video_specs.py +81 -20
  150. endoreg_db/models/media/video/video_file_meta/text_meta.py +61 -20
  151. endoreg_db/models/media/video/video_file_meta/video_meta.py +40 -12
  152. endoreg_db/models/media/video/video_file_segments.py +118 -27
  153. endoreg_db/models/media/video/video_metadata.py +25 -6
  154. endoreg_db/models/media/video/video_processing.py +54 -15
  155. endoreg_db/models/medical/__init__.py +3 -13
  156. endoreg_db/models/medical/contraindication/__init__.py +3 -1
  157. endoreg_db/models/medical/disease.py +18 -6
  158. endoreg_db/models/medical/event.py +6 -2
  159. endoreg_db/models/medical/examination/__init__.py +5 -1
  160. endoreg_db/models/medical/examination/examination.py +22 -6
  161. endoreg_db/models/medical/examination/examination_indication.py +23 -7
  162. endoreg_db/models/medical/examination/examination_time.py +6 -2
  163. endoreg_db/models/medical/finding/__init__.py +3 -1
  164. endoreg_db/models/medical/finding/finding.py +37 -12
  165. endoreg_db/models/medical/finding/finding_classification.py +27 -8
  166. endoreg_db/models/medical/finding/finding_intervention.py +19 -6
  167. endoreg_db/models/medical/finding/finding_type.py +3 -1
  168. endoreg_db/models/medical/hardware/__init__.py +1 -1
  169. endoreg_db/models/medical/hardware/endoscope.py +14 -2
  170. endoreg_db/models/medical/laboratory/__init__.py +1 -1
  171. endoreg_db/models/medical/laboratory/lab_value.py +139 -39
  172. endoreg_db/models/medical/medication/__init__.py +7 -3
  173. endoreg_db/models/medical/medication/medication.py +3 -1
  174. endoreg_db/models/medical/medication/medication_indication.py +3 -1
  175. endoreg_db/models/medical/medication/medication_indication_type.py +11 -3
  176. endoreg_db/models/medical/medication/medication_intake_time.py +3 -1
  177. endoreg_db/models/medical/medication/medication_schedule.py +3 -1
  178. endoreg_db/models/medical/patient/__init__.py +2 -10
  179. endoreg_db/models/medical/patient/medication_examples.py +3 -14
  180. endoreg_db/models/medical/patient/patient_disease.py +17 -5
  181. endoreg_db/models/medical/patient/patient_event.py +12 -4
  182. endoreg_db/models/medical/patient/patient_examination.py +52 -15
  183. endoreg_db/models/medical/patient/patient_examination_indication.py +15 -4
  184. endoreg_db/models/medical/patient/patient_finding.py +105 -29
  185. endoreg_db/models/medical/patient/patient_finding_classification.py +41 -12
  186. endoreg_db/models/medical/patient/patient_finding_intervention.py +11 -3
  187. endoreg_db/models/medical/patient/patient_lab_sample.py +6 -2
  188. endoreg_db/models/medical/patient/patient_lab_value.py +42 -10
  189. endoreg_db/models/medical/patient/patient_medication.py +25 -7
  190. endoreg_db/models/medical/patient/patient_medication_schedule.py +34 -10
  191. endoreg_db/models/metadata/model_meta.py +40 -12
  192. endoreg_db/models/metadata/model_meta_logic.py +51 -16
  193. endoreg_db/models/metadata/sensitive_meta.py +65 -28
  194. endoreg_db/models/metadata/sensitive_meta_logic.py +28 -26
  195. endoreg_db/models/metadata/video_meta.py +146 -39
  196. endoreg_db/models/metadata/video_prediction_logic.py +70 -21
  197. endoreg_db/models/metadata/video_prediction_meta.py +80 -27
  198. endoreg_db/models/operation_log.py +63 -0
  199. endoreg_db/models/other/__init__.py +10 -10
  200. endoreg_db/models/other/distribution/__init__.py +9 -7
  201. endoreg_db/models/other/distribution/base_value_distribution.py +3 -1
  202. endoreg_db/models/other/distribution/date_value_distribution.py +19 -5
  203. endoreg_db/models/other/distribution/multiple_categorical_value_distribution.py +3 -1
  204. endoreg_db/models/other/distribution/numeric_value_distribution.py +34 -9
  205. endoreg_db/models/other/emission/__init__.py +1 -1
  206. endoreg_db/models/other/emission/emission_factor.py +9 -3
  207. endoreg_db/models/other/information_source.py +15 -5
  208. endoreg_db/models/other/material.py +3 -1
  209. endoreg_db/models/other/transport_route.py +3 -1
  210. endoreg_db/models/other/unit.py +6 -2
  211. endoreg_db/models/report/report.py +0 -1
  212. endoreg_db/models/requirement/requirement.py +84 -27
  213. endoreg_db/models/requirement/requirement_error.py +5 -6
  214. endoreg_db/models/requirement/requirement_evaluation/__init__.py +1 -1
  215. endoreg_db/models/requirement/requirement_evaluation/evaluate_with_dependencies.py +8 -8
  216. endoreg_db/models/requirement/requirement_evaluation/get_values.py +3 -3
  217. endoreg_db/models/requirement/requirement_evaluation/requirement_type_parser.py +24 -8
  218. endoreg_db/models/requirement/requirement_operator.py +28 -8
  219. endoreg_db/models/requirement/requirement_set.py +34 -11
  220. endoreg_db/models/state/__init__.py +1 -0
  221. endoreg_db/models/state/audit_ledger.py +9 -2
  222. endoreg_db/models/{media → state}/processing_history/__init__.py +1 -3
  223. endoreg_db/models/state/processing_history/processing_history.py +136 -0
  224. endoreg_db/models/state/raw_pdf.py +0 -1
  225. endoreg_db/models/state/video.py +2 -3
  226. endoreg_db/models/utils.py +4 -2
  227. endoreg_db/queries/__init__.py +2 -6
  228. endoreg_db/queries/annotations/__init__.py +1 -3
  229. endoreg_db/queries/annotations/legacy.py +37 -26
  230. endoreg_db/root_urls.py +3 -4
  231. endoreg_db/schemas/examination_evaluation.py +3 -0
  232. endoreg_db/serializers/Frames_NICE_and_PARIS_classifications.py +249 -163
  233. endoreg_db/serializers/__init__.py +2 -8
  234. endoreg_db/serializers/administration/__init__.py +1 -2
  235. endoreg_db/serializers/administration/ai/__init__.py +0 -1
  236. endoreg_db/serializers/administration/ai/active_model.py +3 -1
  237. endoreg_db/serializers/administration/ai/ai_model.py +5 -3
  238. endoreg_db/serializers/administration/ai/model_type.py +3 -1
  239. endoreg_db/serializers/administration/center.py +7 -2
  240. endoreg_db/serializers/administration/gender.py +4 -2
  241. endoreg_db/serializers/anonymization.py +13 -13
  242. endoreg_db/serializers/evaluation/examination_evaluation.py +0 -1
  243. endoreg_db/serializers/examination/__init__.py +1 -1
  244. endoreg_db/serializers/examination/base.py +12 -13
  245. endoreg_db/serializers/examination/dropdown.py +6 -7
  246. endoreg_db/serializers/examination_serializer.py +3 -6
  247. endoreg_db/serializers/finding/__init__.py +1 -1
  248. endoreg_db/serializers/finding/finding.py +14 -7
  249. endoreg_db/serializers/finding_classification/__init__.py +3 -3
  250. endoreg_db/serializers/finding_classification/choice.py +3 -3
  251. endoreg_db/serializers/finding_classification/classification.py +2 -4
  252. endoreg_db/serializers/label_video_segment/__init__.py +5 -3
  253. endoreg_db/serializers/{label → label_video_segment}/image_classification_annotation.py +5 -5
  254. endoreg_db/serializers/label_video_segment/label/__init__.py +6 -0
  255. endoreg_db/serializers/{label → label_video_segment/label}/label.py +1 -1
  256. endoreg_db/serializers/label_video_segment/label_video_segment.py +338 -228
  257. endoreg_db/serializers/meta/__init__.py +1 -2
  258. endoreg_db/serializers/meta/sensitive_meta_detail.py +28 -13
  259. endoreg_db/serializers/meta/sensitive_meta_update.py +51 -46
  260. endoreg_db/serializers/meta/sensitive_meta_verification.py +19 -16
  261. endoreg_db/serializers/misc/__init__.py +2 -2
  262. endoreg_db/serializers/misc/file_overview.py +11 -7
  263. endoreg_db/serializers/misc/stats.py +10 -8
  264. endoreg_db/serializers/misc/translatable_field_mix_in.py +6 -6
  265. endoreg_db/serializers/misc/upload_job.py +32 -29
  266. endoreg_db/serializers/patient/__init__.py +2 -1
  267. endoreg_db/serializers/patient/patient.py +32 -15
  268. endoreg_db/serializers/patient/patient_dropdown.py +11 -3
  269. endoreg_db/serializers/patient_examination/__init__.py +1 -1
  270. endoreg_db/serializers/patient_examination/patient_examination.py +67 -40
  271. endoreg_db/serializers/patient_finding/__init__.py +1 -1
  272. endoreg_db/serializers/patient_finding/patient_finding.py +2 -1
  273. endoreg_db/serializers/patient_finding/patient_finding_classification.py +17 -9
  274. endoreg_db/serializers/patient_finding/patient_finding_detail.py +26 -17
  275. endoreg_db/serializers/patient_finding/patient_finding_intervention.py +7 -5
  276. endoreg_db/serializers/patient_finding/patient_finding_list.py +10 -11
  277. endoreg_db/serializers/patient_finding/patient_finding_write.py +36 -27
  278. endoreg_db/serializers/pdf/__init__.py +1 -3
  279. endoreg_db/serializers/requirements/requirement_schema.py +1 -6
  280. endoreg_db/serializers/sensitive_meta_serializer.py +100 -81
  281. endoreg_db/serializers/video/__init__.py +2 -2
  282. endoreg_db/serializers/video/{segmentation.py → video_file.py} +66 -47
  283. endoreg_db/serializers/video/video_file_brief.py +6 -2
  284. endoreg_db/serializers/video/video_file_detail.py +36 -23
  285. endoreg_db/serializers/video/video_file_list.py +4 -2
  286. endoreg_db/serializers/video/video_processing_history.py +54 -50
  287. endoreg_db/services/__init__.py +1 -1
  288. endoreg_db/services/anonymization.py +2 -2
  289. endoreg_db/services/examination_evaluation.py +40 -17
  290. endoreg_db/services/model_meta_from_hf.py +76 -0
  291. endoreg_db/services/polling_coordinator.py +101 -70
  292. endoreg_db/services/pseudonym_service.py +27 -22
  293. endoreg_db/services/report_import.py +6 -3
  294. endoreg_db/services/segment_sync.py +75 -59
  295. endoreg_db/services/video_import.py +6 -7
  296. endoreg_db/urls/__init__.py +2 -2
  297. endoreg_db/urls/ai.py +7 -25
  298. endoreg_db/urls/anonymization.py +61 -15
  299. endoreg_db/urls/auth.py +4 -4
  300. endoreg_db/urls/classification.py +4 -9
  301. endoreg_db/urls/examination.py +27 -18
  302. endoreg_db/urls/media.py +27 -34
  303. endoreg_db/urls/patient.py +11 -7
  304. endoreg_db/urls/requirements.py +3 -1
  305. endoreg_db/urls/root_urls.py +2 -3
  306. endoreg_db/urls/stats.py +24 -16
  307. endoreg_db/urls/upload.py +3 -11
  308. endoreg_db/utils/__init__.py +14 -15
  309. endoreg_db/utils/ai/__init__.py +1 -1
  310. endoreg_db/utils/ai/data_loader_for_model_input.py +262 -0
  311. endoreg_db/utils/ai/data_loader_for_model_training.py +262 -0
  312. endoreg_db/utils/ai/get.py +2 -1
  313. endoreg_db/utils/ai/inference_dataset.py +14 -15
  314. endoreg_db/utils/ai/model_training/config.py +117 -0
  315. endoreg_db/utils/ai/model_training/dataset.py +74 -0
  316. endoreg_db/utils/ai/model_training/losses.py +68 -0
  317. endoreg_db/utils/ai/model_training/metrics.py +78 -0
  318. endoreg_db/utils/ai/model_training/model_backbones.py +155 -0
  319. endoreg_db/utils/ai/model_training/model_gastronet_resnet.py +118 -0
  320. endoreg_db/utils/ai/model_training/trainer_gastronet_multilabel.py +771 -0
  321. endoreg_db/utils/ai/multilabel_classification_net.py +21 -6
  322. endoreg_db/utils/ai/predict.py +4 -4
  323. endoreg_db/utils/ai/preprocess.py +19 -11
  324. endoreg_db/utils/calc_duration_seconds.py +4 -4
  325. endoreg_db/utils/case_generator/lab_sample_factory.py +3 -4
  326. endoreg_db/utils/check_video_files.py +74 -47
  327. endoreg_db/utils/cropping.py +10 -9
  328. endoreg_db/utils/dataloader.py +11 -3
  329. endoreg_db/utils/dates.py +3 -4
  330. endoreg_db/utils/defaults/set_default_center.py +7 -6
  331. endoreg_db/utils/env.py +6 -2
  332. endoreg_db/utils/extract_specific_frames.py +24 -9
  333. endoreg_db/utils/file_operations.py +30 -18
  334. endoreg_db/utils/fix_video_path_direct.py +57 -41
  335. endoreg_db/utils/frame_anonymization_utils.py +157 -157
  336. endoreg_db/utils/hashs.py +3 -18
  337. endoreg_db/utils/links/requirement_link.py +96 -52
  338. endoreg_db/utils/ocr.py +30 -25
  339. endoreg_db/utils/operation_log.py +61 -0
  340. endoreg_db/utils/parse_and_generate_yaml.py +12 -13
  341. endoreg_db/utils/paths.py +6 -6
  342. endoreg_db/utils/permissions.py +40 -24
  343. endoreg_db/utils/pipelines/process_video_dir.py +50 -26
  344. endoreg_db/utils/product/sum_emissions.py +5 -3
  345. endoreg_db/utils/product/sum_weights.py +4 -2
  346. endoreg_db/utils/pydantic_models/__init__.py +3 -4
  347. endoreg_db/utils/requirement_operator_logic/_old/lab_value_operators.py +207 -107
  348. endoreg_db/utils/requirement_operator_logic/_old/model_evaluators.py +252 -65
  349. endoreg_db/utils/requirement_operator_logic/new_operator_logic.py +27 -10
  350. endoreg_db/utils/setup_config.py +21 -5
  351. endoreg_db/utils/storage.py +3 -1
  352. endoreg_db/utils/translation.py +19 -15
  353. endoreg_db/utils/uuid.py +1 -0
  354. endoreg_db/utils/validate_endo_roi.py +12 -4
  355. endoreg_db/utils/validate_subcategory_dict.py +26 -24
  356. endoreg_db/utils/validate_video_detailed.py +207 -149
  357. endoreg_db/utils/video/__init__.py +7 -3
  358. endoreg_db/utils/video/extract_frames.py +30 -18
  359. endoreg_db/utils/video/ffmpeg_wrapper.py +217 -52
  360. endoreg_db/utils/video/names.py +11 -6
  361. endoreg_db/utils/video/streaming_processor.py +175 -101
  362. endoreg_db/utils/video/video_splitter.py +30 -19
  363. endoreg_db/views/Frames_NICE_and_PARIS_classifications_views.py +59 -50
  364. endoreg_db/views/__init__.py +0 -20
  365. endoreg_db/views/anonymization/__init__.py +6 -2
  366. endoreg_db/views/anonymization/media_management.py +2 -6
  367. endoreg_db/views/anonymization/overview.py +34 -1
  368. endoreg_db/views/anonymization/validate.py +79 -18
  369. endoreg_db/views/auth/__init__.py +1 -1
  370. endoreg_db/views/auth/keycloak.py +16 -14
  371. endoreg_db/views/examination/__init__.py +12 -15
  372. endoreg_db/views/examination/examination.py +5 -5
  373. endoreg_db/views/examination/examination_manifest_cache.py +5 -5
  374. endoreg_db/views/examination/get_finding_classification_choices.py +8 -5
  375. endoreg_db/views/examination/get_finding_classifications.py +9 -7
  376. endoreg_db/views/examination/get_findings.py +8 -10
  377. endoreg_db/views/examination/get_instruments.py +3 -2
  378. endoreg_db/views/examination/get_interventions.py +1 -1
  379. endoreg_db/views/finding/__init__.py +2 -2
  380. endoreg_db/views/finding/finding.py +58 -54
  381. endoreg_db/views/finding/get_classifications.py +1 -1
  382. endoreg_db/views/finding/get_interventions.py +1 -1
  383. endoreg_db/views/finding_classification/__init__.py +5 -5
  384. endoreg_db/views/finding_classification/finding_classification.py +5 -6
  385. endoreg_db/views/finding_classification/get_classification_choices.py +3 -4
  386. endoreg_db/views/media/__init__.py +13 -13
  387. endoreg_db/views/media/pdf_media.py +9 -9
  388. endoreg_db/views/media/sensitive_metadata.py +10 -7
  389. endoreg_db/views/media/video_media.py +4 -4
  390. endoreg_db/views/meta/__init__.py +1 -1
  391. endoreg_db/views/meta/sensitive_meta_list.py +20 -22
  392. endoreg_db/views/meta/sensitive_meta_verification.py +14 -11
  393. endoreg_db/views/misc/__init__.py +6 -34
  394. endoreg_db/views/misc/center.py +2 -1
  395. endoreg_db/views/misc/csrf.py +2 -1
  396. endoreg_db/views/misc/gender.py +2 -1
  397. endoreg_db/views/misc/stats.py +141 -106
  398. endoreg_db/views/patient/__init__.py +1 -3
  399. endoreg_db/views/patient/patient.py +141 -99
  400. endoreg_db/views/patient_examination/__init__.py +5 -5
  401. endoreg_db/views/patient_examination/patient_examination.py +43 -42
  402. endoreg_db/views/patient_examination/patient_examination_create.py +10 -15
  403. endoreg_db/views/patient_examination/patient_examination_detail.py +12 -15
  404. endoreg_db/views/patient_examination/patient_examination_list.py +21 -17
  405. endoreg_db/views/patient_examination/video.py +114 -80
  406. endoreg_db/views/patient_finding/__init__.py +1 -1
  407. endoreg_db/views/patient_finding/patient_finding.py +17 -10
  408. endoreg_db/views/patient_finding/patient_finding_optimized.py +127 -95
  409. endoreg_db/views/patient_finding_classification/__init__.py +1 -1
  410. endoreg_db/views/patient_finding_classification/pfc_create.py +35 -27
  411. endoreg_db/views/report/reimport.py +1 -1
  412. endoreg_db/views/report/report_stream.py +5 -8
  413. endoreg_db/views/requirement/__init__.py +2 -1
  414. endoreg_db/views/requirement/evaluate.py +7 -9
  415. endoreg_db/views/requirement/lookup.py +2 -3
  416. endoreg_db/views/requirement/lookup_store.py +0 -1
  417. endoreg_db/views/requirement/requirement_utils.py +2 -4
  418. endoreg_db/views/stats/__init__.py +4 -4
  419. endoreg_db/views/stats/stats_views.py +152 -115
  420. endoreg_db/views/video/__init__.py +18 -27
  421. endoreg_db/views/{ai → video/ai}/__init__.py +2 -2
  422. endoreg_db/views/{ai → video/ai}/label.py +20 -16
  423. endoreg_db/views/video/correction.py +5 -6
  424. endoreg_db/views/video/reimport.py +134 -99
  425. endoreg_db/views/video/segments_crud.py +134 -44
  426. endoreg_db/views/video/video_apply_mask.py +13 -12
  427. endoreg_db/views/video/video_correction.py +2 -1
  428. endoreg_db/views/video/video_download_processed.py +15 -15
  429. endoreg_db/views/video/video_meta_stats.py +7 -6
  430. endoreg_db/views/video/video_processing_history.py +3 -2
  431. endoreg_db/views/video/video_remove_frames.py +13 -12
  432. endoreg_db/views/video/video_stream.py +110 -82
  433. {endoreg_db-0.8.8.9.dist-info → endoreg_db-0.8.9.10.dist-info}/METADATA +9 -3
  434. {endoreg_db-0.8.8.9.dist-info → endoreg_db-0.8.9.10.dist-info}/RECORD +436 -433
  435. endoreg_db/import_files/processing/video_processing/video_cleanup_on_error.py +0 -119
  436. endoreg_db/management/commands/import_fallback_video.py +0 -203
  437. endoreg_db/management/commands/import_video.py +0 -422
  438. endoreg_db/management/commands/import_video_with_classification.py +0 -367
  439. endoreg_db/models/media/processing_history/processing_history.py +0 -96
  440. endoreg_db/serializers/label/__init__.py +0 -7
  441. endoreg_db/serializers/label_video_segment/_lvs_create.py +0 -149
  442. endoreg_db/serializers/label_video_segment/_lvs_update.py +0 -138
  443. endoreg_db/serializers/label_video_segment/_lvs_validate.py +0 -149
  444. endoreg_db/serializers/label_video_segment/label_video_segment_annotation.py +0 -99
  445. endoreg_db/serializers/label_video_segment/label_video_segment_update.py +0 -163
  446. endoreg_db/services/__old/pdf_import.py +0 -1487
  447. endoreg_db/services/__old/video_import.py +0 -1306
  448. endoreg_db/tasks/upload_tasks.py +0 -216
  449. endoreg_db/tasks/video_ingest.py +0 -161
  450. endoreg_db/tasks/video_processing_tasks.py +0 -327
  451. endoreg_db/views/misc/translation.py +0 -182
  452. {endoreg_db-0.8.8.9.dist-info → endoreg_db-0.8.9.10.dist-info}/WHEEL +0 -0
  453. {endoreg_db-0.8.8.9.dist-info → endoreg_db-0.8.9.10.dist-info}/licenses/LICENSE +0 -0
@@ -34,7 +34,9 @@ class LabelVideoSegment(models.Model):
34
34
 
35
35
  start_frame_number = models.IntegerField()
36
36
  end_frame_number = models.IntegerField()
37
- source = models.ForeignKey("InformationSource", on_delete=models.SET_NULL, null=True)
37
+ source = models.ForeignKey(
38
+ "InformationSource", on_delete=models.SET_NULL, null=True
39
+ )
38
40
  label = models.ForeignKey("Label", on_delete=models.SET_NULL, null=True, blank=True)
39
41
 
40
42
  # Single ForeignKey to the unified VideoFile model
@@ -68,13 +70,18 @@ class LabelVideoSegment(models.Model):
68
70
  source: models.ForeignKey["InformationSource|None"]
69
71
  prediction_meta: models.ForeignKey["VideoPredictionMeta|None"]
70
72
 
71
- patient_findings = cast(models.manager.RelatedManager["PatientFinding"], patient_findings)
73
+ patient_findings = cast(
74
+ models.manager.RelatedManager["PatientFinding"], patient_findings
75
+ )
72
76
  model_meta: models.ForeignKey["ModelMeta|None"]
73
77
  state: models.OneToOneField["LabelVideoSegmentState"]
74
78
 
75
79
  class Meta:
76
80
  constraints = [
77
- CheckConstraint(condition=Q(start_frame_number__lt=F("end_frame_number")), name="segment_start_lt_end"),
81
+ CheckConstraint(
82
+ condition=Q(start_frame_number__lt=F("end_frame_number")),
83
+ name="segment_start_lt_end",
84
+ ),
78
85
  ]
79
86
  indexes = [
80
87
  models.Index(fields=["video_file", "label", "start_frame_number"]),
@@ -125,13 +132,18 @@ class LabelVideoSegment(models.Model):
125
132
  return self.state.is_validated
126
133
  except ObjectDoesNotExist:
127
134
  # This might happen if the state wasn't created yet, though the save method tries to prevent this.
128
- logger.warning("LabelVideoSegmentState not found for LabelVideoSegment %s.", self.pk)
135
+ logger.warning(
136
+ "LabelVideoSegmentState not found for LabelVideoSegment %s.", self.pk
137
+ )
129
138
  return False
130
139
  except AttributeError:
131
140
  # Should not happen if self.state exists and has the is_validated attribute.
132
- logger.error("AttributeError accessing 'state.is_validated' for LabelVideoSegment %s.", self.pk)
141
+ logger.error(
142
+ "AttributeError accessing 'state.is_validated' for LabelVideoSegment %s.",
143
+ self.pk,
144
+ )
133
145
  return False
134
-
146
+
135
147
  def mark_validated(
136
148
  self,
137
149
  is_validated: bool = True,
@@ -163,7 +175,12 @@ class LabelVideoSegment(models.Model):
163
175
 
164
176
  if not isinstance(self.video_file, VideoFile):
165
177
  raise ValueError("Cannot extract frame files: No associated VideoFile.")
166
- return self.video_file.extract_specific_frame_range(start_frame=self.start_frame_number, end_frame=self.end_frame_number, overwrite=overwrite, **kwargs)
178
+ return self.video_file.extract_specific_frame_range(
179
+ start_frame=self.start_frame_number,
180
+ end_frame=self.end_frame_number,
181
+ overwrite=overwrite,
182
+ **kwargs,
183
+ )
167
184
 
168
185
  def delete_frame_files(self) -> None:
169
186
  """
@@ -176,10 +193,14 @@ class LabelVideoSegment(models.Model):
176
193
 
177
194
  if not isinstance(self.video_file, VideoFile):
178
195
  raise ValueError("Cannot delete frame files: No associated VideoFile.")
179
- self.video_file.delete_specific_frame_range(start_frame=self.start_frame_number, end_frame=self.end_frame_number)
196
+ self.video_file.delete_specific_frame_range(
197
+ start_frame=self.start_frame_number, end_frame=self.end_frame_number
198
+ )
180
199
 
181
200
  @classmethod
182
- def safe_create(cls, video_file, label, start_frame_number, end_frame_number, **kwargs):
201
+ def safe_create(
202
+ cls, video_file, label, start_frame_number, end_frame_number, **kwargs
203
+ ):
183
204
  """
184
205
  Create a new LabelVideoSegment instance after validating the frame range.
185
206
 
@@ -188,8 +209,16 @@ class LabelVideoSegment(models.Model):
188
209
  Returns:
189
210
  LabelVideoSegment: The newly created segment instance.
190
211
  """
191
- cls.validate_frame_range(start_frame_number, end_frame_number, video_file=video_file)
192
- return cls.objects.create(video_file=video_file, label=label, start_frame_number=start_frame_number, end_frame_number=end_frame_number, **kwargs)
212
+ cls.validate_frame_range(
213
+ start_frame_number, end_frame_number, video_file=video_file
214
+ )
215
+ return cls.objects.create(
216
+ video_file=video_file,
217
+ label=label,
218
+ start_frame_number=start_frame_number,
219
+ end_frame_number=end_frame_number,
220
+ **kwargs,
221
+ )
193
222
 
194
223
  def save(self, *args, **kwargs):
195
224
  """
@@ -232,7 +261,9 @@ class LabelVideoSegment(models.Model):
232
261
  """
233
262
  Create a LabelVideoSegment instance from a VideoFile.
234
263
  """
235
- return _create_from_video(cls, source, prediction_meta, label, start_frame_number, end_frame_number)
264
+ return _create_from_video(
265
+ cls, source, prediction_meta, label, start_frame_number, end_frame_number
266
+ )
236
267
 
237
268
  def get_video(self) -> "VideoFile":
238
269
  """Returns the associated VideoFile instance."""
@@ -243,15 +274,21 @@ class LabelVideoSegment(models.Model):
243
274
  return self.video_file
244
275
  except ObjectDoesNotExist:
245
276
  # This might occur if the related VideoFile was deleted unexpectedly.
246
- logger.error("Associated VideoFile not found for LabelVideoSegment %s.", self.pk)
247
- raise ValueError(f"LabelVideoSegment {self.pk} is not associated with a valid VideoFile.")
277
+ logger.error(
278
+ "Associated VideoFile not found for LabelVideoSegment %s.", self.pk
279
+ )
280
+ raise ValueError(
281
+ f"LabelVideoSegment {self.pk} is not associated with a valid VideoFile."
282
+ )
248
283
 
249
284
  def __str__(self):
250
285
  try:
251
286
  video_obj = self.get_video()
252
287
  label_name = self.label.name if self.label else "No Label"
253
288
  active_path = video_obj.active_file_path
254
- video_identifier = active_path.name if active_path else f"UUID {video_obj.uuid}"
289
+ video_identifier = (
290
+ active_path.name if active_path else f"UUID {video_obj.video_hash}"
291
+ )
255
292
 
256
293
  str_repr = f"{video_identifier} Label - {label_name} - {self.start_frame_number} - {self.end_frame_number}"
257
294
  except ObjectDoesNotExist: # More specific exception
@@ -259,7 +296,11 @@ class LabelVideoSegment(models.Model):
259
296
  except ValueError as e: # Catch specific error from get_video
260
297
  str_repr = f"Segment {self.pk} (Error: {e})"
261
298
  except Exception as e:
262
- logger.warning("Error generating string representation for LabelVideoSegment %s: %s", self.pk, e)
299
+ logger.warning(
300
+ "Error generating string representation for LabelVideoSegment %s: %s",
301
+ self.pk,
302
+ e,
303
+ )
263
304
  str_repr = f"Segment {self.pk} (Error: {e})"
264
305
 
265
306
  return str_repr
@@ -296,12 +337,20 @@ class LabelVideoSegment(models.Model):
296
337
 
297
338
  try:
298
339
  video_obj = self.get_video()
299
- return video_obj.frames.filter(frame_number__gte=self.start_frame_number, frame_number__lt=self.end_frame_number).order_by("frame_number")
340
+ return video_obj.frames.filter(
341
+ frame_number__gte=self.start_frame_number,
342
+ frame_number__lt=self.end_frame_number,
343
+ ).order_by("frame_number")
300
344
  except ValueError:
301
- logger.error("Cannot get frames for segment %s: No associated VideoFile.", self.pk)
345
+ logger.error(
346
+ "Cannot get frames for segment %s: No associated VideoFile.", self.pk
347
+ )
302
348
  return Frame.objects.none()
303
349
  except AttributeError:
304
- logger.error("Cannot get frames for segment %s: 'frames' related manager not found on VideoFile.", self.pk)
350
+ logger.error(
351
+ "Cannot get frames for segment %s: 'frames' related manager not found on VideoFile.",
352
+ self.pk,
353
+ )
305
354
  return Frame.objects.none()
306
355
 
307
356
  @property
@@ -323,7 +372,10 @@ class LabelVideoSegment(models.Model):
323
372
  label=self.label,
324
373
  )
325
374
  except ValueError:
326
- logger.error("Cannot get annotations for segment %s: No associated VideoFile.", self.pk)
375
+ logger.error(
376
+ "Cannot get annotations for segment %s: No associated VideoFile.",
377
+ self.pk,
378
+ )
327
379
  return ImageClassificationAnnotation.objects.none()
328
380
 
329
381
  @property
@@ -346,11 +398,16 @@ class LabelVideoSegment(models.Model):
346
398
  information_source__information_source_types__name="prediction",
347
399
  )
348
400
  except ValueError:
349
- logger.error("Cannot get predictions for segment %s: No associated VideoFile.", self.pk)
401
+ logger.error(
402
+ "Cannot get predictions for segment %s: No associated VideoFile.",
403
+ self.pk,
404
+ )
350
405
  return ImageClassificationAnnotation.objects.none()
351
406
 
352
407
  @property
353
- def manual_frame_annotations(self) -> models.QuerySet["ImageClassificationAnnotation"]:
408
+ def manual_frame_annotations(
409
+ self,
410
+ ) -> models.QuerySet["ImageClassificationAnnotation"]:
354
411
  """
355
412
  Return manual image classification annotations for frames within this segment and matching the segment's label.
356
413
 
@@ -369,7 +426,10 @@ class LabelVideoSegment(models.Model):
369
426
  information_source__information_source_types__name="manual_annotation",
370
427
  )
371
428
  except ValueError:
372
- logger.error("Cannot get manual annotations for segment %s: No associated VideoFile.", self.pk)
429
+ logger.error(
430
+ "Cannot get manual annotations for segment %s: No associated VideoFile.",
431
+ self.pk,
432
+ )
373
433
  return ImageClassificationAnnotation.objects.none()
374
434
 
375
435
  def get_segment_len_in_s(self) -> float:
@@ -383,14 +443,21 @@ class LabelVideoSegment(models.Model):
383
443
  video_obj = self.get_video()
384
444
  fps = video_obj.get_fps()
385
445
  if fps is None or fps <= 0:
386
- logger.warning("Could not determine valid FPS for %s. Cannot calculate segment length in seconds.", video_obj)
446
+ logger.warning(
447
+ "Could not determine valid FPS for %s. Cannot calculate segment length in seconds.",
448
+ video_obj,
449
+ )
387
450
  return 0.0
388
451
  return (self.end_frame_number - self.start_frame_number) / fps
389
452
  except ValueError as e: # Catch error from get_video
390
- logger.error("Cannot calculate segment length for segment %s: %s", self.pk, e)
453
+ logger.error(
454
+ "Cannot calculate segment length for segment %s: %s", self.pk, e
455
+ )
391
456
  return 0.0
392
457
 
393
- def get_frames_without_annotation(self, n_frames: int) -> Union[list["Frame"], list]:
458
+ def get_frames_without_annotation(
459
+ self, n_frames: int
460
+ ) -> Union[list["Frame"], list]:
394
461
  """
395
462
  Get up to n frames within the segment that do not have an ImageClassificationAnnotation
396
463
  for this segment's label.
@@ -402,14 +469,19 @@ class LabelVideoSegment(models.Model):
402
469
  return []
403
470
 
404
471
  if not self.label:
405
- logger.warning("Segment %s has no label. Cannot find frames without annotation.", self.pk)
472
+ logger.warning(
473
+ "Segment %s has no label. Cannot find frames without annotation.",
474
+ self.pk,
475
+ )
406
476
  return []
407
477
 
408
- annotated_frame_ids = ImageClassificationAnnotation.objects.filter(frame__in=frames_qs.values_list("id", flat=True), label=self.label).values_list(
409
- "frame_id", flat=True
410
- )
478
+ annotated_frame_ids = ImageClassificationAnnotation.objects.filter(
479
+ frame__in=frames_qs.values_list("id", flat=True), label=self.label
480
+ ).values_list("frame_id", flat=True)
411
481
 
412
- frames_without_annotation = list(frames_qs.exclude(id__in=annotated_frame_ids)[:n_frames])
482
+ frames_without_annotation = list(
483
+ frames_qs.exclude(id__in=annotated_frame_ids)[:n_frames]
484
+ )
413
485
  return frames_without_annotation
414
486
 
415
487
  def generate_annotations(self):
@@ -419,25 +491,35 @@ class LabelVideoSegment(models.Model):
419
491
  Annotations are generated only if the segment has associated prediction metadata, model metadata, and label. Existing annotations for the same frame, label, model, and information source are not duplicated. Uses bulk creation for efficiency.
420
492
  """
421
493
  if not self.prediction_meta:
422
- logger.info("Skipping annotation generation for segment %s: Requires linked VideoPredictionMeta.", self.pk)
494
+ logger.info(
495
+ "Skipping annotation generation for segment %s: Requires linked VideoPredictionMeta.",
496
+ self.pk,
497
+ )
423
498
  return
424
499
 
425
500
  from endoreg_db.models import ImageClassificationAnnotation, InformationSource
426
501
 
427
502
  information_source = self.source
428
503
  if not information_source:
429
- information_source, _ = InformationSource.objects.get_or_create(name="prediction")
504
+ information_source, _ = InformationSource.objects.get_or_create(
505
+ name="prediction"
506
+ )
430
507
 
431
508
  model_meta = self.get_model_meta()
432
509
  label = self.label
433
510
 
434
511
  if not model_meta or not label:
435
- logger.warning("Missing model_meta or label for segment %s. Skipping annotation generation.", self.pk)
512
+ logger.warning(
513
+ "Missing model_meta or label for segment %s. Skipping annotation generation.",
514
+ self.pk,
515
+ )
436
516
  return
437
517
 
438
518
  frames_queryset = self.get_frames().only("id")
439
519
  if not isinstance(frames_queryset, models.QuerySet):
440
- logger.error("Could not get frame queryset for segment %s. Skipping.", self.pk)
520
+ logger.error(
521
+ "Could not get frame queryset for segment %s. Skipping.", self.pk
522
+ )
441
523
  return
442
524
 
443
525
  existing_annotation_frame_ids = set(
@@ -450,9 +532,15 @@ class LabelVideoSegment(models.Model):
450
532
  )
451
533
 
452
534
  annotations_to_create = []
453
- frames_to_annotate = frames_queryset.exclude(id__in=existing_annotation_frame_ids)
535
+ frames_to_annotate = frames_queryset.exclude(
536
+ id__in=existing_annotation_frame_ids
537
+ )
454
538
 
455
- for frame in tqdm(frames_to_annotate.iterator(), total=frames_to_annotate.count(), desc=f"Preparing annotations for segment {self.pk} ({label.name})"):
539
+ for frame in tqdm(
540
+ frames_to_annotate.iterator(),
541
+ total=frames_to_annotate.count(),
542
+ desc=f"Preparing annotations for segment {self.pk} ({label.name})",
543
+ ):
456
544
  annotations_to_create.append(
457
545
  ImageClassificationAnnotation(
458
546
  frame=frame,
@@ -464,8 +552,14 @@ class LabelVideoSegment(models.Model):
464
552
  )
465
553
 
466
554
  if annotations_to_create:
467
- logger.info("Bulk creating %d annotations for segment %s...", len(annotations_to_create), self.pk)
468
- ImageClassificationAnnotation.objects.bulk_create(annotations_to_create, ignore_conflicts=True)
555
+ logger.info(
556
+ "Bulk creating %d annotations for segment %s...",
557
+ len(annotations_to_create),
558
+ self.pk,
559
+ )
560
+ ImageClassificationAnnotation.objects.bulk_create(
561
+ annotations_to_create, ignore_conflicts=True
562
+ )
469
563
  logger.info("Bulk creation complete.")
470
564
  else:
471
565
  logger.info("No new annotations needed for segment %s.", self.pk)
@@ -483,7 +577,9 @@ class LabelVideoSegment(models.Model):
483
577
  return video_obj.get_fps()
484
578
 
485
579
  @staticmethod
486
- def validate_frame_range(start_frame_number: int, end_frame_number: int, video_file=None):
580
+ def validate_frame_range(
581
+ start_frame_number: int, end_frame_number: int, video_file=None
582
+ ):
487
583
  """
488
584
  Validate that the provided frame numbers define a valid segment range, optionally checking against a video's frame count.
489
585
 
@@ -495,13 +591,21 @@ class LabelVideoSegment(models.Model):
495
591
  Raises:
496
592
  ValueError: If frame numbers are not integers, are negative, are out of order, or exceed the video's frame count.
497
593
  """
498
- if not isinstance(start_frame_number, int) or not isinstance(end_frame_number, int):
499
- raise ValueError("start_frame_number and end_frame_number must be integers.")
594
+ if not isinstance(start_frame_number, int) or not isinstance(
595
+ end_frame_number, int
596
+ ):
597
+ raise ValueError(
598
+ "start_frame_number and end_frame_number must be integers."
599
+ )
500
600
  if start_frame_number < 0:
501
601
  raise ValueError("start_frame_number must be non-negative.")
502
602
  if end_frame_number < start_frame_number:
503
- raise ValueError("end_frame_number must be equal or greater than start_frame_number.")
603
+ raise ValueError(
604
+ "end_frame_number must be equal or greater than start_frame_number."
605
+ )
504
606
  if video_file is not None:
505
607
  frame_count = getattr(video_file, "frame_count", None)
506
608
  if frame_count is not None and end_frame_number > frame_count:
507
- raise ValueError(f"end_frame_number ({end_frame_number}) exceeds video frame count ({frame_count}).")
609
+ raise ValueError(
610
+ f"end_frame_number ({end_frame_number}) exceeds video frame count ({frame_count})."
611
+ )
@@ -1,6 +1,13 @@
1
1
  from .video import VideoFile, VideoMetadata, VideoProcessingHistory
2
2
  from .frame import Frame
3
- from .pdf import RawPdfFile, DocumentType, AnonymExaminationReport, ReportReaderConfig, ReportReaderFlag, AnonymHistologyReport
3
+ from .pdf import (
4
+ RawPdfFile,
5
+ DocumentType,
6
+ AnonymExaminationReport,
7
+ ReportReaderConfig,
8
+ ReportReaderFlag,
9
+ AnonymHistologyReport,
10
+ )
4
11
 
5
12
  __all__ = [
6
13
  "VideoFile",
@@ -9,8 +16,8 @@ __all__ = [
9
16
  "DocumentType",
10
17
  "AnonymExaminationReport",
11
18
  "AnonymHistologyReport",
12
- 'ReportReaderConfig',
13
- 'ReportReaderFlag',
14
- 'VideoMetadata',
15
- 'VideoProcessingHistory',
19
+ "ReportReaderConfig",
20
+ "ReportReaderFlag",
21
+ "VideoMetadata",
22
+ "VideoProcessingHistory",
16
23
  ]
@@ -1,3 +1,3 @@
1
1
  from .frame import Frame
2
2
 
3
- __all__ = ["Frame"]
3
+ __all__ = ["Frame"]
@@ -24,10 +24,13 @@ class Frame(models.Model):
24
24
  frame_number = models.PositiveIntegerField()
25
25
  relative_path = models.CharField(max_length=512)
26
26
  timestamp = models.FloatField(null=True, blank=True)
27
+
27
28
  is_extracted = models.BooleanField(default=False)
28
29
 
29
30
  if TYPE_CHECKING:
30
- image_classification_annotations: models.QuerySet["ImageClassificationAnnotation"]
31
+ image_classification_annotations: models.QuerySet[
32
+ "ImageClassificationAnnotation"
33
+ ]
31
34
  video: models.ForeignKey["VideoFile"]
32
35
 
33
36
  class Meta:
@@ -54,7 +57,9 @@ class Frame(models.Model):
54
57
  Returns:
55
58
  QuerySet: A queryset of related ImageClassificationAnnotation objects filtered to those whose information source type is "prediction".
56
59
  """
57
- return self.image_classification_annotations.filter(information_source__information_source_types__name="prediction")
60
+ return self.image_classification_annotations.filter(
61
+ information_source__information_source_types__name="prediction"
62
+ )
58
63
 
59
64
  @property
60
65
  def manual_annotations(self) -> models.QuerySet["ImageClassificationAnnotation"]:
@@ -64,7 +69,9 @@ class Frame(models.Model):
64
69
  Returns:
65
70
  QuerySet: A queryset of related ImageClassificationAnnotation objects whose information source type is "manual_annotation".
66
71
  """
67
- return self.image_classification_annotations.filter(information_source__information_source_types__name="manual_annotation")
72
+ return self.image_classification_annotations.filter(
73
+ information_source__information_source_types__name="manual_annotation"
74
+ )
68
75
 
69
76
  @property
70
77
  def has_predictions(self) -> bool:
@@ -93,19 +100,38 @@ class Frame(models.Model):
93
100
  """
94
101
  frame_path = self.file_path
95
102
  if not frame_path.exists():
96
- logger.warning("Frame file not found at %s for Frame %s (Video %s)", frame_path, self.pk, self.video.uuid)
103
+ logger.warning(
104
+ "Frame file not found at %s for Frame %s (Video %s)",
105
+ frame_path,
106
+ self.pk,
107
+ self.video.video_hash,
108
+ )
97
109
  return None
98
110
  try:
99
111
  image = cv2.imread(str(frame_path))
100
112
  if image is None:
101
- logger.warning("cv2.imread returned None for frame file %s (Frame %s, Video %s)", frame_path, self.pk, self.video.uuid)
113
+ logger.warning(
114
+ "cv2.imread returned None for frame file %s (Frame %s, Video %s)",
115
+ frame_path,
116
+ self.pk,
117
+ self.video.video_hash,
118
+ )
102
119
  return image
103
120
  except Exception as e:
104
- logger.error("Error reading frame file %s (Frame %s, Video %s): %s", frame_path, self.pk, self.video.uuid, e, exc_info=True)
121
+ logger.error(
122
+ "Error reading frame file %s (Frame %s, Video %s): %s",
123
+ frame_path,
124
+ self.pk,
125
+ self.video.video_hash,
126
+ e,
127
+ exc_info=True,
128
+ )
105
129
  return None
106
130
 
107
131
  def __str__(self):
108
- return f"Frame {self.frame_number} of Video {self.video.uuid}"
132
+ return f"Frame {self.frame_number} of Video {self.video.video_hash}"
109
133
 
110
- def get_classification_annotations(self) -> models.QuerySet["ImageClassificationAnnotation"]:
134
+ def get_classification_annotations(
135
+ self,
136
+ ) -> models.QuerySet["ImageClassificationAnnotation"]:
111
137
  return self.image_classification_annotations.all()
@@ -1,6 +1,7 @@
1
1
  from .raw_pdf import RawPdfFile
2
2
  from .report_file import DocumentType, AnonymExaminationReport, AnonymHistologyReport
3
3
  from .report_reader import ReportReaderConfig, ReportReaderFlag
4
+
4
5
  __all__ = [
5
6
  "RawPdfFile",
6
7
  "DocumentType",
@@ -8,4 +9,4 @@ __all__ = [
8
9
  "AnonymHistologyReport",
9
10
  "ReportReaderConfig",
10
11
  "ReportReaderFlag",
11
- ]
12
+ ]
@@ -11,7 +11,7 @@ from django.core.files import File
11
11
  from django.core.validators import FileExtensionValidator
12
12
  from django.db import models
13
13
 
14
- from endoreg_db.utils.file_operations import get_uuid_filename
14
+ from endoreg_db.utils.file_operations import get_content_hash_filename
15
15
  from endoreg_db.utils.hashs import get_pdf_hash
16
16
  from endoreg_db.utils.paths import (
17
17
  ANONYM_REPORT_DIR,
@@ -515,7 +515,7 @@ class RawPdfFile(models.Model):
515
515
  raise
516
516
 
517
517
  # Generate a unique filename (e.g., using UUID)
518
- new_file_name, _uuid = get_uuid_filename(file_path)
518
+ new_file_name, _uuid = get_content_hash_filename(file_path)
519
519
  logger.info(f"Generated new filename: {new_file_name}")
520
520
 
521
521
  # Create model instance via manager so creation can be intercepted/mocked during tests
@@ -750,8 +750,15 @@ class RawPdfFile(models.Model):
750
750
  return settings_dict
751
751
 
752
752
  @staticmethod
753
- def get_pdf_by_pk(pk: int) -> "RawPdfFile":
753
+ def get_report_by_pk(pk: int) -> "RawPdfFile":
754
754
  try:
755
755
  return RawPdfFile.objects.get(pk=pk)
756
756
  except RawPdfFile.DoesNotExist:
757
- raise ValueError(f"report with ID {pdf_id} does not exist.")
757
+ raise ValueError(f"report with ID {pk} does not exist.")
758
+
759
+ @staticmethod
760
+ def get_report_by_hash(hash: str) -> "RawPdfFile":
761
+ try:
762
+ return RawPdfFile.objects.get(pdf_hash=hash)
763
+ except RawPdfFile.DoesNotExist:
764
+ raise ValueError(f"report with ID {hash} does not exist.")
@@ -87,7 +87,9 @@ class AbstractExaminationReport(AbstractDocument):
87
87
  Abstract base class for examination reports.
88
88
  """
89
89
 
90
- patient = models.ForeignKey("Patient", on_delete=models.DO_NOTHING, blank=True, null=True)
90
+ patient = models.ForeignKey(
91
+ "Patient", on_delete=models.DO_NOTHING, blank=True, null=True
92
+ )
91
93
 
92
94
  patient_examination = models.ForeignKey(
93
95
  "PatientExamination",
@@ -101,7 +103,9 @@ class AbstractExaminationReport(AbstractDocument):
101
103
  blank=True,
102
104
  )
103
105
 
104
- sensitive_meta = models.ForeignKey("SensitiveMeta", on_delete=models.SET_NULL, null=True, blank=True)
106
+ sensitive_meta = models.ForeignKey(
107
+ "SensitiveMeta", on_delete=models.SET_NULL, null=True, blank=True
108
+ )
105
109
 
106
110
  if TYPE_CHECKING:
107
111
  center: models.ForeignKey["Center|None"]
@@ -2,6 +2,6 @@ from .report_reader_config import ReportReaderConfig
2
2
  from .report_reader_flag import ReportReaderFlag
3
3
 
4
4
  __all__ = [
5
- 'ReportReaderConfig',
6
- 'ReportReaderFlag',
7
- ]
5
+ "ReportReaderConfig",
6
+ "ReportReaderFlag",
7
+ ]
@@ -19,15 +19,25 @@ class ReportReaderFlag(models.Model):
19
19
  if TYPE_CHECKING:
20
20
 
21
21
  @property
22
- def report_reader_configs_patient_info_line(self) -> models.QuerySet["ReportReaderConfig"]: ...
22
+ def report_reader_configs_patient_info_line(
23
+ self,
24
+ ) -> models.QuerySet["ReportReaderConfig"]: ...
23
25
  @property
24
- def report_reader_configs_endoscope_info_line(self) -> models.QuerySet["ReportReaderConfig"]: ...
26
+ def report_reader_configs_endoscope_info_line(
27
+ self,
28
+ ) -> models.QuerySet["ReportReaderConfig"]: ...
25
29
  @property
26
- def report_reader_configs_examiner_info_line(self) -> models.QuerySet["ReportReaderConfig"]: ...
30
+ def report_reader_configs_examiner_info_line(
31
+ self,
32
+ ) -> models.QuerySet["ReportReaderConfig"]: ...
27
33
  @property
28
- def report_reader_configs_cut_off_below(self) -> models.QuerySet["ReportReaderConfig"]: ...
34
+ def report_reader_configs_cut_off_below(
35
+ self,
36
+ ) -> models.QuerySet["ReportReaderConfig"]: ...
29
37
  @property
30
- def report_reader_configs_cut_off_above(self) -> models.QuerySet["ReportReaderConfig"]: ...
38
+ def report_reader_configs_cut_off_above(
39
+ self,
40
+ ) -> models.QuerySet["ReportReaderConfig"]: ...
31
41
 
32
42
  def natural_key(self):
33
43
  return (self.name,)