endoreg-db 0.8.6.1__py3-none-any.whl → 0.8.8.0__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 (360) hide show
  1. endoreg_db/authz/auth.py +74 -0
  2. endoreg_db/authz/backends.py +168 -0
  3. endoreg_db/authz/management/commands/list_routes.py +18 -0
  4. endoreg_db/authz/middleware.py +83 -0
  5. endoreg_db/authz/permissions.py +127 -0
  6. endoreg_db/authz/policy.py +218 -0
  7. endoreg_db/authz/views_auth.py +66 -0
  8. endoreg_db/config/env.py +13 -8
  9. endoreg_db/data/__init__.py +8 -31
  10. endoreg_db/data/_examples/disease.yaml +55 -0
  11. endoreg_db/data/_examples/disease_classification.yaml +13 -0
  12. endoreg_db/data/_examples/disease_classification_choice.yaml +62 -0
  13. endoreg_db/data/_examples/event.yaml +64 -0
  14. endoreg_db/data/_examples/examination.yaml +72 -0
  15. endoreg_db/data/_examples/finding/anatomy_colon.yaml +128 -0
  16. endoreg_db/data/_examples/finding/colonoscopy.yaml +40 -0
  17. endoreg_db/data/_examples/finding/colonoscopy_bowel_prep.yaml +56 -0
  18. endoreg_db/data/_examples/finding/complication.yaml +16 -0
  19. endoreg_db/data/_examples/finding/data.yaml +105 -0
  20. endoreg_db/data/_examples/finding/examination_setting.yaml +16 -0
  21. endoreg_db/data/_examples/finding/medication_related.yaml +18 -0
  22. endoreg_db/data/_examples/finding/outcome.yaml +12 -0
  23. endoreg_db/data/_examples/finding_classification/colonoscopy_bowel_preparation.yaml +68 -0
  24. endoreg_db/data/_examples/finding_classification/colonoscopy_jnet.yaml +22 -0
  25. endoreg_db/data/_examples/finding_classification/colonoscopy_kudo.yaml +25 -0
  26. endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_circularity.yaml +20 -0
  27. endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_planarity.yaml +24 -0
  28. endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_size.yaml +68 -0
  29. endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_surface.yaml +20 -0
  30. endoreg_db/data/_examples/finding_classification/colonoscopy_location.yaml +80 -0
  31. endoreg_db/data/_examples/finding_classification/colonoscopy_lst.yaml +21 -0
  32. endoreg_db/data/_examples/finding_classification/colonoscopy_nice.yaml +20 -0
  33. endoreg_db/data/_examples/finding_classification/colonoscopy_paris.yaml +26 -0
  34. endoreg_db/data/_examples/finding_classification/colonoscopy_sano.yaml +22 -0
  35. endoreg_db/data/_examples/finding_classification/colonoscopy_summary.yaml +53 -0
  36. endoreg_db/data/_examples/finding_classification/complication_generic.yaml +25 -0
  37. endoreg_db/data/_examples/finding_classification/examination_setting_generic.yaml +40 -0
  38. endoreg_db/data/_examples/finding_classification/histology_colo.yaml +51 -0
  39. endoreg_db/data/_examples/finding_classification/intervention_required.yaml +26 -0
  40. endoreg_db/data/_examples/finding_classification/medication_related.yaml +23 -0
  41. endoreg_db/data/_examples/finding_classification/visualized.yaml +33 -0
  42. endoreg_db/data/_examples/finding_classification_choice/bowel_preparation.yaml +78 -0
  43. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_circularity_default.yaml +32 -0
  44. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_jnet.yaml +15 -0
  45. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_kudo.yaml +23 -0
  46. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_lst.yaml +15 -0
  47. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_nice.yaml +17 -0
  48. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_paris.yaml +57 -0
  49. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_planarity_default.yaml +49 -0
  50. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_sano.yaml +14 -0
  51. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_surface_intact_default.yaml +36 -0
  52. endoreg_db/data/_examples/finding_classification_choice/colonoscopy_location.yaml +229 -0
  53. endoreg_db/data/_examples/finding_classification_choice/colonoscopy_not_complete_reason.yaml +19 -0
  54. endoreg_db/data/_examples/finding_classification_choice/colonoscopy_size.yaml +82 -0
  55. endoreg_db/data/_examples/finding_classification_choice/colonoscopy_summary_worst_finding.yaml +15 -0
  56. endoreg_db/data/_examples/finding_classification_choice/complication_generic_types.yaml +15 -0
  57. endoreg_db/data/_examples/finding_classification_choice/examination_setting_generic_types.yaml +15 -0
  58. endoreg_db/data/_examples/finding_classification_choice/histology.yaml +24 -0
  59. endoreg_db/data/_examples/finding_classification_choice/histology_polyp.yaml +20 -0
  60. endoreg_db/data/_examples/finding_classification_choice/outcome.yaml +19 -0
  61. endoreg_db/data/_examples/finding_classification_choice/yes_no_na.yaml +11 -0
  62. endoreg_db/data/_examples/finding_classification_type/colonoscopy_basic.yaml +48 -0
  63. endoreg_db/data/_examples/finding_intervention/endoscopy.yaml +43 -0
  64. endoreg_db/data/_examples/finding_intervention/endoscopy_colonoscopy.yaml +168 -0
  65. endoreg_db/data/_examples/finding_intervention/endoscopy_egd.yaml +128 -0
  66. endoreg_db/data/_examples/finding_intervention/endoscopy_ercp.yaml +32 -0
  67. endoreg_db/data/_examples/finding_intervention/endoscopy_eus_lower.yaml +9 -0
  68. endoreg_db/data/_examples/finding_intervention/endoscopy_eus_upper.yaml +36 -0
  69. endoreg_db/data/_examples/finding_intervention_type/endoscopy.yaml +15 -0
  70. endoreg_db/data/_examples/finding_type/data.yaml +43 -0
  71. endoreg_db/data/_examples/requirement/age.yaml +26 -0
  72. endoreg_db/data/_examples/requirement/colonoscopy_baseline_austria.yaml +45 -0
  73. endoreg_db/data/_examples/requirement/disease_cardiovascular.yaml +79 -0
  74. endoreg_db/data/_examples/requirement/disease_classification_choice_cardiovascular.yaml +41 -0
  75. endoreg_db/data/_examples/requirement/disease_hepatology.yaml +12 -0
  76. endoreg_db/data/_examples/requirement/disease_misc.yaml +12 -0
  77. endoreg_db/data/_examples/requirement/disease_renal.yaml +96 -0
  78. endoreg_db/data/_examples/requirement/endoscopy_bleeding_risk.yaml +59 -0
  79. endoreg_db/data/_examples/requirement/event_cardiology.yaml +251 -0
  80. endoreg_db/data/_examples/requirement/event_requirements.yaml +145 -0
  81. endoreg_db/data/_examples/requirement/finding_colon_polyp.yaml +50 -0
  82. endoreg_db/data/_examples/requirement/gender.yaml +25 -0
  83. endoreg_db/data/_examples/requirement/lab_value.yaml +441 -0
  84. endoreg_db/data/_examples/requirement/medication.yaml +93 -0
  85. endoreg_db/data/_examples/requirement_operator/age.yaml +13 -0
  86. endoreg_db/data/_examples/requirement_operator/lab_operators.yaml +129 -0
  87. endoreg_db/data/_examples/requirement_operator/model_operators.yaml +96 -0
  88. endoreg_db/data/_examples/requirement_set/01_endoscopy_generic.yaml +48 -0
  89. endoreg_db/data/_examples/requirement_set/colonoscopy_austria_screening.yaml +57 -0
  90. endoreg_db/data/_examples/yaml_examples.xlsx +0 -0
  91. endoreg_db/data/ai_model_meta/default_multilabel_classification.yaml +4 -3
  92. endoreg_db/data/event_classification/data.yaml +4 -0
  93. endoreg_db/data/event_classification_choice/data.yaml +9 -0
  94. endoreg_db/data/finding_classification/colonoscopy_bowel_preparation.yaml +43 -70
  95. endoreg_db/data/finding_classification/colonoscopy_lesion_size.yaml +22 -52
  96. endoreg_db/data/finding_classification/colonoscopy_location.yaml +31 -62
  97. endoreg_db/data/finding_classification/histology_colo.yaml +28 -36
  98. endoreg_db/data/requirement/colon_polyp_intervention.yaml +49 -0
  99. endoreg_db/data/requirement/coloreg_colon_polyp.yaml +49 -0
  100. endoreg_db/data/requirement_set/01_endoscopy_generic.yaml +31 -12
  101. endoreg_db/data/requirement_set/01_laboratory.yaml +13 -0
  102. endoreg_db/data/requirement_set/02_endoscopy_bleeding_risk.yaml +46 -0
  103. endoreg_db/data/requirement_set/90_coloreg.yaml +178 -0
  104. endoreg_db/data/requirement_set/_old_ +109 -0
  105. endoreg_db/data/requirement_set_type/data.yaml +21 -0
  106. endoreg_db/data/setup_config.yaml +4 -4
  107. endoreg_db/data/tag/requirement_set_tags.yaml +21 -0
  108. endoreg_db/exceptions.py +5 -2
  109. endoreg_db/helpers/data_loader.py +1 -1
  110. endoreg_db/management/commands/create_model_meta_from_huggingface.py +21 -10
  111. endoreg_db/management/commands/create_multilabel_model_meta.py +299 -129
  112. endoreg_db/management/commands/import_video.py +9 -10
  113. endoreg_db/management/commands/import_video_with_classification.py +1 -1
  114. endoreg_db/management/commands/init_default_ai_model.py +1 -1
  115. endoreg_db/management/commands/list_routes.py +18 -0
  116. endoreg_db/management/commands/load_center_data.py +12 -12
  117. endoreg_db/management/commands/load_requirement_data.py +60 -31
  118. endoreg_db/management/commands/load_requirement_set_tags.py +95 -0
  119. endoreg_db/management/commands/setup_endoreg_db.py +3 -3
  120. endoreg_db/management/commands/storage_management.py +271 -203
  121. endoreg_db/migrations/0001_initial.py +1799 -1300
  122. endoreg_db/migrations/0002_requirementset_depends_on.py +18 -0
  123. endoreg_db/migrations/_old/0001_initial.py +1857 -0
  124. endoreg_db/migrations/_old/0004_employee_city_employee_post_code_employee_street_and_more.py +68 -0
  125. endoreg_db/migrations/_old/0004_remove_casetemplate_rules_and_more.py +77 -0
  126. endoreg_db/migrations/_old/0005_merge_20251111_1003.py +14 -0
  127. endoreg_db/migrations/_old/0006_sensitivemeta_anonymized_text_and_more.py +68 -0
  128. endoreg_db/migrations/_old/0007_remove_rule_attribute_dtype_remove_rule_rule_type_and_more.py +89 -0
  129. endoreg_db/migrations/_old/0008_remove_event_event_classification_and_more.py +27 -0
  130. endoreg_db/migrations/_old/0009_alter_modelmeta_options_and_more.py +21 -0
  131. endoreg_db/models/__init__.py +78 -123
  132. endoreg_db/models/administration/__init__.py +21 -42
  133. endoreg_db/models/administration/ai/active_model.py +2 -2
  134. endoreg_db/models/administration/ai/ai_model.py +7 -6
  135. endoreg_db/models/administration/case/__init__.py +1 -15
  136. endoreg_db/models/administration/case/case.py +3 -3
  137. endoreg_db/models/administration/case/case_template/__init__.py +2 -14
  138. endoreg_db/models/administration/case/case_template/case_template.py +2 -124
  139. endoreg_db/models/administration/case/case_template/case_template_rule.py +2 -268
  140. endoreg_db/models/administration/case/case_template/case_template_rule_value.py +2 -85
  141. endoreg_db/models/administration/case/case_template/case_template_type.py +2 -25
  142. endoreg_db/models/administration/center/center.py +33 -19
  143. endoreg_db/models/administration/center/center_product.py +12 -9
  144. endoreg_db/models/administration/center/center_resource.py +25 -19
  145. endoreg_db/models/administration/center/center_shift.py +21 -17
  146. endoreg_db/models/administration/center/center_waste.py +16 -8
  147. endoreg_db/models/administration/person/__init__.py +2 -0
  148. endoreg_db/models/administration/person/employee/employee.py +10 -5
  149. endoreg_db/models/administration/person/employee/employee_qualification.py +9 -4
  150. endoreg_db/models/administration/person/employee/employee_type.py +12 -6
  151. endoreg_db/models/administration/person/examiner/examiner.py +13 -11
  152. endoreg_db/models/administration/person/patient/__init__.py +2 -0
  153. endoreg_db/models/administration/person/patient/patient.py +103 -100
  154. endoreg_db/models/administration/person/patient/patient_external_id.py +37 -0
  155. endoreg_db/models/administration/person/person.py +4 -0
  156. endoreg_db/models/administration/person/profession/__init__.py +8 -4
  157. endoreg_db/models/administration/person/user/portal_user_information.py +11 -7
  158. endoreg_db/models/administration/product/product.py +20 -15
  159. endoreg_db/models/administration/product/product_material.py +17 -18
  160. endoreg_db/models/administration/product/product_weight.py +12 -8
  161. endoreg_db/models/administration/product/reference_product.py +23 -55
  162. endoreg_db/models/administration/qualification/qualification.py +7 -3
  163. endoreg_db/models/administration/qualification/qualification_type.py +7 -3
  164. endoreg_db/models/administration/shift/scheduled_days.py +8 -5
  165. endoreg_db/models/administration/shift/shift.py +16 -12
  166. endoreg_db/models/administration/shift/shift_type.py +23 -31
  167. endoreg_db/models/label/__init__.py +7 -8
  168. endoreg_db/models/label/annotation/image_classification.py +10 -9
  169. endoreg_db/models/label/annotation/video_segmentation_annotation.py +8 -5
  170. endoreg_db/models/label/label.py +15 -15
  171. endoreg_db/models/label/label_set.py +19 -6
  172. endoreg_db/models/label/label_type.py +1 -1
  173. endoreg_db/models/label/label_video_segment/_create_from_video.py +5 -8
  174. endoreg_db/models/label/label_video_segment/label_video_segment.py +76 -102
  175. endoreg_db/models/label/video_segmentation_label.py +4 -0
  176. endoreg_db/models/label/video_segmentation_labelset.py +4 -3
  177. endoreg_db/models/media/frame/frame.py +22 -22
  178. endoreg_db/models/media/pdf/raw_pdf.py +110 -182
  179. endoreg_db/models/media/pdf/report_file.py +25 -29
  180. endoreg_db/models/media/pdf/report_reader/report_reader_config.py +30 -46
  181. endoreg_db/models/media/pdf/report_reader/report_reader_flag.py +23 -7
  182. endoreg_db/models/media/video/__init__.py +1 -0
  183. endoreg_db/models/media/video/create_from_file.py +48 -56
  184. endoreg_db/models/media/video/pipe_2.py +8 -9
  185. endoreg_db/models/media/video/video_file.py +150 -108
  186. endoreg_db/models/media/video/video_file_ai.py +288 -74
  187. endoreg_db/models/media/video/video_file_anonymize.py +38 -38
  188. endoreg_db/models/media/video/video_file_frames/__init__.py +3 -1
  189. endoreg_db/models/media/video/video_file_frames/_bulk_create_frames.py +6 -8
  190. endoreg_db/models/media/video/video_file_frames/_create_frame_object.py +7 -9
  191. endoreg_db/models/media/video/video_file_frames/_delete_frames.py +9 -8
  192. endoreg_db/models/media/video/video_file_frames/_extract_frames.py +38 -45
  193. endoreg_db/models/media/video/video_file_frames/_get_frame.py +6 -8
  194. endoreg_db/models/media/video/video_file_frames/_get_frame_number.py +4 -18
  195. endoreg_db/models/media/video/video_file_frames/_get_frame_path.py +4 -3
  196. endoreg_db/models/media/video/video_file_frames/_get_frame_paths.py +7 -6
  197. endoreg_db/models/media/video/video_file_frames/_get_frame_range.py +6 -8
  198. endoreg_db/models/media/video/video_file_frames/_get_frames.py +6 -8
  199. endoreg_db/models/media/video/video_file_frames/_initialize_frames.py +15 -25
  200. endoreg_db/models/media/video/video_file_frames/_manage_frame_range.py +26 -23
  201. endoreg_db/models/media/video/video_file_frames/_mark_frames_extracted_status.py +23 -14
  202. endoreg_db/models/media/video/video_file_io.py +109 -62
  203. endoreg_db/models/media/video/video_file_meta/get_crop_template.py +3 -3
  204. endoreg_db/models/media/video/video_file_meta/get_endo_roi.py +5 -3
  205. endoreg_db/models/media/video/video_file_meta/get_fps.py +37 -34
  206. endoreg_db/models/media/video/video_file_meta/initialize_video_specs.py +19 -25
  207. endoreg_db/models/media/video/video_file_meta/text_meta.py +41 -38
  208. endoreg_db/models/media/video/video_file_meta/video_meta.py +14 -7
  209. endoreg_db/models/media/video/video_file_segments.py +24 -17
  210. endoreg_db/models/media/video/video_metadata.py +19 -35
  211. endoreg_db/models/media/video/video_processing.py +96 -95
  212. endoreg_db/models/medical/contraindication/__init__.py +13 -3
  213. endoreg_db/models/medical/disease.py +22 -16
  214. endoreg_db/models/medical/event.py +31 -18
  215. endoreg_db/models/medical/examination/__init__.py +13 -6
  216. endoreg_db/models/medical/examination/examination.py +17 -18
  217. endoreg_db/models/medical/examination/examination_indication.py +26 -25
  218. endoreg_db/models/medical/examination/examination_time.py +16 -6
  219. endoreg_db/models/medical/examination/examination_time_type.py +9 -6
  220. endoreg_db/models/medical/examination/examination_type.py +3 -4
  221. endoreg_db/models/medical/finding/finding.py +38 -39
  222. endoreg_db/models/medical/finding/finding_classification.py +37 -48
  223. endoreg_db/models/medical/finding/finding_intervention.py +27 -22
  224. endoreg_db/models/medical/finding/finding_type.py +13 -12
  225. endoreg_db/models/medical/hardware/endoscope.py +20 -26
  226. endoreg_db/models/medical/hardware/endoscopy_processor.py +2 -2
  227. endoreg_db/models/medical/laboratory/lab_value.py +62 -91
  228. endoreg_db/models/medical/medication/medication.py +22 -10
  229. endoreg_db/models/medical/medication/medication_indication.py +29 -3
  230. endoreg_db/models/medical/medication/medication_indication_type.py +25 -14
  231. endoreg_db/models/medical/medication/medication_intake_time.py +31 -19
  232. endoreg_db/models/medical/medication/medication_schedule.py +27 -16
  233. endoreg_db/models/medical/organ/__init__.py +15 -12
  234. endoreg_db/models/medical/patient/medication_examples.py +1 -5
  235. endoreg_db/models/medical/patient/patient_disease.py +20 -23
  236. endoreg_db/models/medical/patient/patient_event.py +19 -22
  237. endoreg_db/models/medical/patient/patient_examination.py +48 -54
  238. endoreg_db/models/medical/patient/patient_examination_indication.py +16 -14
  239. endoreg_db/models/medical/patient/patient_finding.py +122 -139
  240. endoreg_db/models/medical/patient/patient_finding_classification.py +44 -49
  241. endoreg_db/models/medical/patient/patient_finding_intervention.py +8 -19
  242. endoreg_db/models/medical/patient/patient_lab_sample.py +28 -23
  243. endoreg_db/models/medical/patient/patient_lab_value.py +82 -89
  244. endoreg_db/models/medical/patient/patient_medication.py +27 -38
  245. endoreg_db/models/medical/patient/patient_medication_schedule.py +28 -36
  246. endoreg_db/models/medical/risk/risk.py +7 -6
  247. endoreg_db/models/medical/risk/risk_type.py +8 -5
  248. endoreg_db/models/metadata/model_meta.py +60 -29
  249. endoreg_db/models/metadata/model_meta_logic.py +125 -18
  250. endoreg_db/models/metadata/pdf_meta.py +19 -24
  251. endoreg_db/models/metadata/sensitive_meta.py +102 -85
  252. endoreg_db/models/metadata/sensitive_meta_logic.py +192 -173
  253. endoreg_db/models/metadata/video_meta.py +51 -31
  254. endoreg_db/models/metadata/video_prediction_logic.py +16 -23
  255. endoreg_db/models/metadata/video_prediction_meta.py +29 -33
  256. endoreg_db/models/other/distribution/date_value_distribution.py +89 -29
  257. endoreg_db/models/other/distribution/multiple_categorical_value_distribution.py +21 -5
  258. endoreg_db/models/other/distribution/numeric_value_distribution.py +114 -53
  259. endoreg_db/models/other/distribution/single_categorical_value_distribution.py +4 -3
  260. endoreg_db/models/other/emission/emission_factor.py +18 -8
  261. endoreg_db/models/other/gender.py +10 -5
  262. endoreg_db/models/other/information_source.py +25 -25
  263. endoreg_db/models/other/material.py +9 -5
  264. endoreg_db/models/other/resource.py +6 -4
  265. endoreg_db/models/other/tag.py +10 -5
  266. endoreg_db/models/other/transport_route.py +13 -8
  267. endoreg_db/models/other/unit.py +10 -6
  268. endoreg_db/models/other/waste.py +6 -5
  269. endoreg_db/models/requirement/requirement.py +580 -272
  270. endoreg_db/models/requirement/requirement_error.py +85 -0
  271. endoreg_db/models/requirement/requirement_evaluation/evaluate_with_dependencies.py +268 -0
  272. endoreg_db/models/requirement/requirement_evaluation/operator_evaluation_models.py +3 -6
  273. endoreg_db/models/requirement/requirement_evaluation/requirement_type_parser.py +90 -64
  274. endoreg_db/models/requirement/requirement_operator.py +36 -33
  275. endoreg_db/models/requirement/requirement_set.py +74 -57
  276. endoreg_db/models/state/__init__.py +4 -4
  277. endoreg_db/models/state/abstract.py +2 -2
  278. endoreg_db/models/state/anonymization.py +12 -0
  279. endoreg_db/models/state/audit_ledger.py +46 -47
  280. endoreg_db/models/state/label_video_segment.py +9 -0
  281. endoreg_db/models/state/raw_pdf.py +40 -46
  282. endoreg_db/models/state/sensitive_meta.py +6 -2
  283. endoreg_db/models/state/video.py +58 -53
  284. endoreg_db/models/upload_job.py +32 -55
  285. endoreg_db/models/utils.py +1 -2
  286. endoreg_db/root_urls.py +21 -2
  287. endoreg_db/serializers/__init__.py +0 -2
  288. endoreg_db/serializers/anonymization.py +18 -10
  289. endoreg_db/serializers/meta/report_meta.py +1 -1
  290. endoreg_db/serializers/meta/sensitive_meta_detail.py +63 -118
  291. endoreg_db/serializers/misc/file_overview.py +11 -99
  292. endoreg_db/serializers/requirements/requirement_sets.py +92 -22
  293. endoreg_db/serializers/video/segmentation.py +2 -1
  294. endoreg_db/serializers/video/video_processing_history.py +20 -5
  295. endoreg_db/services/anonymization.py +75 -73
  296. endoreg_db/services/lookup_service.py +37 -24
  297. endoreg_db/services/pdf_import.py +166 -68
  298. endoreg_db/services/storage_aware_video_processor.py +140 -114
  299. endoreg_db/services/video_import.py +193 -283
  300. endoreg_db/urls/__init__.py +7 -20
  301. endoreg_db/urls/media.py +108 -67
  302. endoreg_db/urls/root_urls.py +29 -0
  303. endoreg_db/utils/__init__.py +15 -5
  304. endoreg_db/utils/ai/multilabel_classification_net.py +116 -20
  305. endoreg_db/utils/case_generator/__init__.py +3 -0
  306. endoreg_db/utils/dataloader.py +88 -16
  307. endoreg_db/utils/defaults/set_default_center.py +32 -0
  308. endoreg_db/utils/names.py +22 -16
  309. endoreg_db/utils/permissions.py +2 -1
  310. endoreg_db/utils/pipelines/process_video_dir.py +1 -1
  311. endoreg_db/utils/requirement_operator_logic/model_evaluators.py +414 -127
  312. endoreg_db/utils/setup_config.py +8 -5
  313. endoreg_db/utils/storage.py +115 -0
  314. endoreg_db/utils/validate_endo_roi.py +8 -2
  315. endoreg_db/utils/video/ffmpeg_wrapper.py +184 -188
  316. endoreg_db/views/__init__.py +0 -10
  317. endoreg_db/views/anonymization/media_management.py +198 -163
  318. endoreg_db/views/anonymization/overview.py +4 -1
  319. endoreg_db/views/anonymization/validate.py +174 -40
  320. endoreg_db/views/media/__init__.py +2 -0
  321. endoreg_db/views/media/pdf_media.py +131 -152
  322. endoreg_db/views/media/sensitive_metadata.py +46 -6
  323. endoreg_db/views/media/video_media.py +89 -82
  324. endoreg_db/views/media/video_segments.py +2 -3
  325. endoreg_db/views/meta/sensitive_meta_detail.py +0 -63
  326. endoreg_db/views/patient/patient.py +5 -4
  327. endoreg_db/views/pdf/pdf_stream.py +20 -21
  328. endoreg_db/views/pdf/reimport.py +11 -32
  329. endoreg_db/views/requirement/evaluate.py +188 -187
  330. endoreg_db/views/requirement/lookup.py +17 -3
  331. endoreg_db/views/requirement/requirement_utils.py +89 -0
  332. endoreg_db/views/video/__init__.py +0 -2
  333. endoreg_db/views/video/correction.py +2 -2
  334. {endoreg_db-0.8.6.1.dist-info → endoreg_db-0.8.8.0.dist-info}/METADATA +7 -3
  335. {endoreg_db-0.8.6.1.dist-info → endoreg_db-0.8.8.0.dist-info}/RECORD +341 -245
  336. endoreg_db/models/administration/permissions/__init__.py +0 -44
  337. endoreg_db/models/media/video/video_file_frames.py +0 -0
  338. endoreg_db/models/metadata/frame_ocr_result.py +0 -0
  339. endoreg_db/models/rule/__init__.py +0 -13
  340. endoreg_db/models/rule/rule.py +0 -27
  341. endoreg_db/models/rule/rule_applicator.py +0 -224
  342. endoreg_db/models/rule/rule_attribute_dtype.py +0 -17
  343. endoreg_db/models/rule/rule_type.py +0 -20
  344. endoreg_db/models/rule/ruleset.py +0 -17
  345. endoreg_db/serializers/video/video_metadata.py +0 -105
  346. endoreg_db/urls/report.py +0 -48
  347. endoreg_db/urls/video.py +0 -61
  348. endoreg_db/utils/case_generator/case_generator.py +0 -159
  349. endoreg_db/utils/case_generator/utils.py +0 -30
  350. endoreg_db/views/report/__init__.py +0 -9
  351. endoreg_db/views/report/report_list.py +0 -112
  352. endoreg_db/views/report/report_with_secure_url.py +0 -28
  353. endoreg_db/views/report/start_examination.py +0 -7
  354. endoreg_db/views.py +0 -0
  355. /endoreg_db/data/{requirement_set → _examples/requirement_set}/endoscopy_bleeding_risk.yaml +0 -0
  356. /endoreg_db/migrations/{0002_add_video_correction_models.py → _old/0002_add_video_correction_models.py} +0 -0
  357. /endoreg_db/migrations/{0003_add_center_display_name.py → _old/0003_add_center_display_name.py} +0 -0
  358. /endoreg_db/{models/media/video/refactor_plan.md → views/pdf/pdf_stream_views.py} +0 -0
  359. {endoreg_db-0.8.6.1.dist-info → endoreg_db-0.8.8.0.dist-info}/WHEEL +0 -0
  360. {endoreg_db-0.8.6.1.dist-info → endoreg_db-0.8.8.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,27 +1,27 @@
1
1
  import logging
2
2
  import shutil
3
- from pathlib import Path
4
- from typing import TYPE_CHECKING, List, Tuple, Dict, Optional, Set
5
3
  import uuid
6
- from django.db import transaction
4
+ from pathlib import Path
5
+ from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
6
+
7
7
  import cv2
8
+ from django.db import transaction
8
9
  from tqdm import tqdm
9
- from django.conf import settings
10
-
11
-
12
10
 
13
11
  from endoreg_db.utils.hashs import get_video_hash
14
- from endoreg_db.utils.validate_endo_roi import validate_endo_roi
15
12
  from endoreg_db.utils.paths import STORAGE_DIR
13
+ from endoreg_db.utils.validate_endo_roi import validate_endo_roi
14
+
16
15
  from ....utils.video.ffmpeg_wrapper import assemble_video_from_frames
17
16
  from ...utils import anonymize_frame # Import from models.utils
18
- from .video_file_segments import _get_outside_frames, _get_outside_frame_numbers
17
+ from .video_file_segments import _get_outside_frame_numbers, _get_outside_frames
19
18
 
20
19
  if TYPE_CHECKING:
21
- from .video_file import VideoFile
22
- from ..frame import Frame
23
20
  from django.db.models import QuerySet
24
21
 
22
+ from ..frame import Frame
23
+ from .video_file import VideoFile
24
+
25
25
  logger = logging.getLogger(__name__)
26
26
 
27
27
 
@@ -66,7 +66,9 @@ def _create_anonymized_frame_files(
66
66
  if not isinstance(source_path, Path):
67
67
  raise TypeError(f"Frame.file_path did not return a Path object for frame {frame_obj.frame_number}")
68
68
  except (AttributeError, TypeError, Exception) as path_err:
69
- logger.error("Could not determine source path for Frame %d (PK: %s) using frame_obj.file_path: %s", frame_obj.frame_number, frame_obj.pk, path_err)
69
+ logger.error(
70
+ "Could not determine source path for Frame %d (PK: %s) using frame_obj.file_path: %s", frame_obj.frame_number, frame_obj.pk, path_err
71
+ )
70
72
  raise RuntimeError(f"Failed to get source path for frame {frame_obj.frame_number}") from path_err
71
73
 
72
74
  if not source_path.exists():
@@ -78,13 +80,7 @@ def _create_anonymized_frame_files(
78
80
  logger.error(error_msg)
79
81
  raise FileNotFoundError(error_msg)
80
82
 
81
- anonymize_frame(
82
- raw_frame_path=source_path,
83
- target_frame_path=target_path,
84
- endo_roi=endo_roi,
85
- all_black=make_all_black,
86
- censor_color=censor_color
87
- )
83
+ anonymize_frame(raw_frame_path=source_path, target_frame_path=target_path, endo_roi=endo_roi, all_black=make_all_black, censor_color=censor_color)
88
84
 
89
85
  generated_paths.append(target_path)
90
86
  except (FileNotFoundError, IOError, ValueError, AttributeError, TypeError, Exception) as e:
@@ -146,15 +142,14 @@ def _censor_outside_frames(video: "VideoFile", outside_label_name: str = "outsid
146
142
  error_count += 1
147
143
 
148
144
  except Exception as e:
149
- logger.error("Error censoring frame %d (%s): %s",
150
- frame_obj.frame_number, getattr(frame_obj, 'relative_path', 'N/A'), e, exc_info=True)
145
+ logger.error("Error censoring frame %d (%s): %s", frame_obj.frame_number, getattr(frame_obj, "relative_path", "N/A"), e, exc_info=True)
151
146
  error_count += 1
152
147
 
153
148
  logger.info("Finished censoring for video %s. Censored: %d, Errors: %d", video.uuid, censored_count, error_count)
154
149
  return error_count == 0
155
150
 
156
151
 
157
- def _make_temporary_anonymized_frames(video: "VideoFile") -> Tuple[Path, List[Path]]:
152
+ def _make_temporary_anonymized_frames(video: "VideoFile", roi_processing = True) -> Tuple[Path, List[Path]]:
158
153
  """
159
154
  Creates temporary anonymized frames in a separate directory.
160
155
  Requires raw file and extracted frames. Raises ValueError or RuntimeError on failure.
@@ -171,10 +166,15 @@ def _make_temporary_anonymized_frames(video: "VideoFile") -> Tuple[Path, List[Pa
171
166
  temp_anonym_frame_dir = video.get_temp_anonymized_frame_dir()
172
167
  temp_anonym_frame_dir.mkdir(parents=True, exist_ok=True)
173
168
  logger.info("Creating temporary anonymized frames for video %s in %s", video.uuid, temp_anonym_frame_dir)
174
-
175
- endo_roi = video.get_endo_roi()
176
- if not validate_endo_roi(endo_roi_dict=endo_roi):
177
- raise ValueError(f"Endoscope ROI is not valid for video {video.uuid}")
169
+ if roi_processing:
170
+ endo_roi = video.get_endo_roi()
171
+ if not validate_endo_roi(endo_roi_dict=endo_roi):
172
+ raise ValueError(f"Endoscope ROI is not valid for video {video.uuid}")
173
+ else:
174
+ endo_roi = {"x": 0, "y": 0, "width": 0, "height": 0} # Dummy ROI to skip processing
175
+ assert endo_roi is not None # For type checker
176
+
177
+
178
178
 
179
179
  state = video.get_or_create_state()
180
180
  if not state.frames_extracted:
@@ -211,12 +211,12 @@ def _make_temporary_anonymized_frames(video: "VideoFile") -> Tuple[Path, List[Pa
211
211
  def _anonymize(video: "VideoFile", delete_original_raw: bool = True) -> bool:
212
212
  """
213
213
  Performs full anonymization of a video by censoring frames, assembling a processed video file, updating database records, and optionally deleting original raw assets.
214
-
214
+
215
215
  Raises:
216
216
  ValueError: If required preconditions are not met (e.g., frames not extracted, sensitive metadata not validated).
217
217
  FileNotFoundError: If the raw video file is missing.
218
218
  RuntimeError: If anonymization or video assembly fails.
219
-
219
+
220
220
  Returns:
221
221
  bool: True if anonymization completes successfully.
222
222
  """
@@ -233,8 +233,6 @@ def _anonymize(video: "VideoFile", delete_original_raw: bool = True) -> bool:
233
233
  raise ValueError(f"Sensitive metadata for video {video.uuid} is not validated. Cannot anonymize.")
234
234
  # outside_segments = video.get_outside_segments(only_validated=False)
235
235
  # unvalidated_outside = outside_segments.filter(state__is_validated=False)
236
-
237
-
238
236
 
239
237
  logger.info("Starting anonymization process for video %s", video.uuid)
240
238
 
@@ -281,17 +279,18 @@ def _anonymize(video: "VideoFile", delete_original_raw: bool = True) -> bool:
281
279
  original_raw_file_path_to_delete = video.get_raw_file_path()
282
280
  original_raw_frame_dir_to_delete = video.get_frame_dir_path()
283
281
 
284
- video.raw_file.name = None
282
+ video.raw_file.name = ""
285
283
 
286
284
  update_fields.extend(["raw_file", "video_hash"])
287
285
 
288
- transaction.on_commit(lambda: _cleanup_raw_assets(
289
- video_uuid=video.uuid,
290
- raw_file_path=original_raw_file_path_to_delete,
291
- raw_frame_dir=original_raw_frame_dir_to_delete
292
- ))
286
+ transaction.on_commit(
287
+ lambda: _cleanup_raw_assets(
288
+ video_uuid=video.uuid, raw_file_path=original_raw_file_path_to_delete, raw_frame_dir=original_raw_frame_dir_to_delete
289
+ )
290
+ )
293
291
 
294
292
  video.save(update_fields=update_fields)
293
+ assert video.state is not None # For type checker
295
294
  video.state.mark_anonymized(save=True)
296
295
  video.refresh_from_db()
297
296
  return True
@@ -309,7 +308,7 @@ def _anonymize(video: "VideoFile", delete_original_raw: bool = True) -> bool:
309
308
  shutil.rmtree(temp_anonym_frame_dir)
310
309
 
311
310
 
312
- def _cleanup_raw_assets(video_uuid: uuid.UUID, raw_file_path: Optional[Path]=None, raw_frame_dir: Optional[Path]=None):
311
+ def _cleanup_raw_assets(video_uuid: uuid.UUID, raw_file_path: Optional[Path] = None, raw_frame_dir: Optional[Path] = None):
313
312
  """
314
313
  Deletes the original raw video file and its extracted frames directory.
315
314
  Called via transaction.on_commit after successful anonymization.
@@ -318,9 +317,10 @@ def _cleanup_raw_assets(video_uuid: uuid.UUID, raw_file_path: Optional[Path]=Non
318
317
  - Sets state.frames_extracted=False.
319
318
  """
320
319
  from endoreg_db.models import VideoFile, VideoState
320
+
321
321
  logger.info("Performing post-commit cleanup of raw assets for video %s.", video_uuid)
322
322
  try:
323
- video_file = VideoFile.objects.select_related('state').filter(uuid=video_uuid).first()
323
+ video_file = VideoFile.objects.select_related("state").filter(uuid=video_uuid).first()
324
324
  if not video_file:
325
325
  logger.error("VideoFile %s not found during post-commit cleanup.", video_uuid)
326
326
  return
@@ -342,7 +342,7 @@ def _cleanup_raw_assets(video_uuid: uuid.UUID, raw_file_path: Optional[Path]=Non
342
342
 
343
343
  if video_file.state.frames_extracted:
344
344
  video_file.state.frames_extracted = False
345
- video_file.state.save(update_fields=['frames_extracted'])
345
+ video_file.state.save(update_fields=["frames_extracted"])
346
346
  logger.info("Set state.frames_extracted=False for video %s after raw asset cleanup.", video_uuid)
347
347
 
348
348
  except Exception as e:
@@ -19,18 +19,20 @@ Submodules:
19
19
  Usage:
20
20
  Import the required functions directly from this module to perform specific video frame operations.
21
21
  """
22
+
22
23
  from ._bulk_create_frames import _bulk_create_frames
23
24
  from ._create_frame_object import _create_frame_object
24
25
  from ._delete_frames import _delete_frames
25
26
  from ._extract_frames import _extract_frames
27
+ from ._get_frame import _get_frame
26
28
  from ._get_frame_number import _get_frame_number
27
29
  from ._get_frame_path import _get_frame_path
28
30
  from ._get_frame_paths import _get_frame_paths
29
31
  from ._get_frame_range import _get_frame_range
30
- from ._get_frame import _get_frame
31
32
  from ._get_frames import _get_frames
32
33
  from ._initialize_frames import _initialize_frames
33
34
  from ._mark_frames_extracted_status import _mark_frames_extracted_status
35
+
34
36
  __all__ = [
35
37
  "_bulk_create_frames",
36
38
  "_create_frame_object",
@@ -1,22 +1,20 @@
1
-
2
- from endoreg_db.models.media.video.video_file_io import _get_frame_dir_path, _get_temp_anonymized_frame_dir
3
-
4
- from typing import TYPE_CHECKING, List
5
-
6
1
  import logging
2
+ from typing import TYPE_CHECKING, List
7
3
 
8
4
  if TYPE_CHECKING:
9
- from endoreg_db.models import VideoFile, Frame
10
-
5
+ from endoreg_db.models import Frame, VideoFile
6
+
11
7
  logger = logging.getLogger(__name__)
12
8
 
9
+ __all__ = ["_bulk_create_frames"]
13
10
 
14
11
 
15
12
  def _bulk_create_frames(video: "VideoFile", frames_to_create: List["Frame"]):
16
13
  """Helper function to perform bulk_create with ignore_conflicts."""
17
14
  from endoreg_db.models import Frame
15
+
18
16
  try:
19
17
  Frame.objects.bulk_create(frames_to_create, ignore_conflicts=True)
20
18
  except Exception as e:
21
19
  logger.error("Error during bulk creation of frames for video %s: %s", video.uuid, e, exc_info=True)
22
- raise
20
+ raise
@@ -1,17 +1,15 @@
1
-
2
-
3
- from typing import TYPE_CHECKING
4
-
5
1
  import logging
2
+ from typing import TYPE_CHECKING
6
3
 
7
4
  if TYPE_CHECKING:
8
- from endoreg_db.models import VideoFile, Frame
9
-
5
+ from endoreg_db.models import Frame, VideoFile
6
+
10
7
  logger = logging.getLogger(__name__)
11
8
 
12
- def _create_frame_object(
13
- video: "VideoFile", frame_number: int, relative_path: str, extracted: bool = False
14
- ) -> "Frame":
9
+ __all__ = ["_create_frame_object"]
10
+
11
+
12
+ def _create_frame_object(video: "VideoFile", frame_number: int, relative_path: str, extracted: bool = False) -> "Frame":
15
13
  """Instantiates a Frame object (does not save it)."""
16
14
  from endoreg_db.models import Frame
17
15
 
@@ -1,17 +1,19 @@
1
-
2
- from endoreg_db.models.media.video.video_file_io import _get_frame_dir_path, _get_temp_anonymized_frame_dir
3
- from django.db import transaction
4
-
1
+ import logging
5
2
  import shutil
6
3
  from typing import TYPE_CHECKING
7
4
 
8
- import logging
5
+ from django.db import transaction
6
+
7
+ from endoreg_db.models.media.video.video_file_io import _get_frame_dir_path, _get_temp_anonymized_frame_dir
9
8
 
10
9
  if TYPE_CHECKING:
11
10
  from endoreg_db.models import VideoFile, VideoState
12
11
 
13
12
  logger = logging.getLogger(__name__)
14
13
 
14
+ __all__ = ["_delete_frames"]
15
+
16
+
15
17
  @transaction.atomic
16
18
  def _delete_frames(video: "VideoFile") -> str:
17
19
  """
@@ -21,6 +23,7 @@ def _delete_frames(video: "VideoFile") -> str:
21
23
  Raises RuntimeError if state update fails.
22
24
  """
23
25
  from endoreg_db.models.media.frame import Frame
26
+
24
27
  deleted_messages = []
25
28
  error_messages = []
26
29
  state_updated = False
@@ -44,7 +47,6 @@ def _delete_frames(video: "VideoFile") -> str:
44
47
  msg = f"Frame directory path not set for video {video.uuid}, cannot delete standard frames."
45
48
  logger.warning(msg)
46
49
 
47
-
48
50
  temp_anonym_frame_dir = None
49
51
  try:
50
52
  temp_anonym_frame_dir = _get_temp_anonymized_frame_dir(video)
@@ -58,13 +60,12 @@ def _delete_frames(video: "VideoFile") -> str:
58
60
  logger.error(msg, exc_info=True)
59
61
  error_messages.append(msg)
60
62
 
61
-
62
63
  try:
63
64
  state: "VideoState" = video.get_or_create_state()
64
65
  update_fields_state = []
65
66
  if state.frames_extracted:
66
67
  state.frames_extracted = False
67
- update_fields_state.append('frames_extracted')
68
+ update_fields_state.append("frames_extracted")
68
69
 
69
70
  if update_fields_state:
70
71
  state.save(update_fields=update_fields_state)
@@ -1,53 +1,62 @@
1
1
  import logging
2
+ from typing import TYPE_CHECKING
3
+
2
4
  from endoreg_db.models.media.video.video_file_io import _get_frame_dir_path
3
5
  from endoreg_db.utils.video.ffmpeg_wrapper import extract_frames as ffmpeg_extract_frames
4
- from typing import TYPE_CHECKING
5
6
 
6
7
  if TYPE_CHECKING:
7
8
  from endoreg_db.models import VideoFile
8
9
 
9
- from django.db import transaction
10
+ import shutil
10
11
 
12
+ from django.db import transaction
11
13
 
12
- import shutil
13
14
  logger = logging.getLogger(__name__)
14
15
 
16
+
15
17
  def _extract_frames(
16
18
  video: "VideoFile",
17
19
  quality: int = 2,
18
20
  overwrite: bool = False,
19
21
  ext="jpg",
20
22
  verbose=False,
23
+ from_processed: bool = False,
21
24
  ) -> bool:
22
25
  """
23
26
  Extract frames from a raw video file, update frame extraction status in the database, and manage related file system operations.
24
-
27
+
25
28
  This function checks for existing extracted frames and skips extraction if appropriate, unless overwriting is requested. It handles deletion of existing frames when overwriting, invokes ffmpeg to extract frames, parses extracted frame numbers, updates corresponding database records, and manages video extraction state. Robust error handling ensures cleanup and state rollback on failure.
26
-
29
+
27
30
  Parameters:
28
31
  video (VideoFile): The video object from which frames are to be extracted.
29
32
  quality (int, optional): Quality parameter for ffmpeg extraction. Defaults to 2.
30
33
  overwrite (bool, optional): Whether to overwrite existing extracted frames. Defaults to False.
31
34
  ext (str, optional): File extension for extracted frames. Defaults to "jpg".
32
-
35
+
33
36
  Returns:
34
37
  bool: True if extraction and updates succeed.
35
-
38
+
36
39
  Raises:
37
40
  FileNotFoundError: If the raw video file is missing.
38
41
  RuntimeError: If extraction or database update fails.
39
42
  ValueError: If the frame directory path cannot be determined.
40
43
  """
41
- from ._delete_frames import _delete_frames
42
44
  from endoreg_db.models.media.frame import Frame
43
45
 
44
- # Pre-validation checks (outside any transaction)
45
- if not video.has_raw:
46
- raise FileNotFoundError(f"Raw video file not available for {video.uuid}. Cannot extract frames.")
46
+ from ._delete_frames import _delete_frames
47
+
48
+ if from_processed:
49
+ raw_file_path = video.get_processed_file_path()
50
+ if not raw_file_path or not raw_file_path.exists():
51
+ raise FileNotFoundError(f"Processed video file not found at {raw_file_path} for video {video.uuid}. Cannot extract frames.")
52
+ else:
53
+ # Pre-validation checks (outside any transaction)
54
+ if not video.has_raw:
55
+ raise FileNotFoundError(f"Raw video file not available for {video.uuid}. Cannot extract frames.")
47
56
 
48
- raw_file_path = video.get_raw_file_path()
49
- if not raw_file_path or not raw_file_path.exists():
50
- raise FileNotFoundError(f"Raw video file not found at {raw_file_path} for video {video.uuid}. Cannot extract frames.")
57
+ raw_file_path = video.get_raw_file_path()
58
+ if not raw_file_path or not raw_file_path.exists():
59
+ raise FileNotFoundError(f"Raw video file not found at {raw_file_path} for video {video.uuid}. Cannot extract frames.")
51
60
 
52
61
  frame_dir = _get_frame_dir_path(video)
53
62
  if not frame_dir:
@@ -63,33 +72,22 @@ def _extract_frames(
63
72
  "Frames already extracted or files exist for video %s, and overwrite=False. Skipping extraction.",
64
73
  video.uuid,
65
74
  )
66
- if state.frames_extracted and frames_exist_in_db:
67
- with transaction.atomic():
68
- updated_count = Frame.objects.filter(video=video, is_extracted=False).update(is_extracted=True)
69
- if updated_count > 0:
70
- logger.info(
71
- "Marked %d existing Frame objects as extracted for video %s based on state.",
72
- updated_count,
73
- video.uuid,
74
- )
75
- elif not state.frames_extracted and files_exist_on_disk:
76
- logger.warning(
77
- "Files exist on disk for video %s but state.frames_extracted is False. Correcting state to match disk.",
78
- video.uuid,
79
- )
80
- # Fix inconsistent state: files exist but state.frames_extracted is False
81
- state.frames_extracted = True
82
- state.save(update_fields=['frames_extracted'])
83
-
84
- # Also update Frame objects to be consistent
85
- with transaction.atomic():
75
+ with transaction.atomic():
76
+ state.refresh_from_db()
77
+ if frames_exist_in_db:
86
78
  updated_count = Frame.objects.filter(video=video, is_extracted=False).update(is_extracted=True)
87
79
  if updated_count > 0:
88
80
  logger.info(
89
- "Marked %d existing Frame objects as extracted for video %s after state correction.",
81
+ "Marked %d existing Frame objects as extracted for video %s based on current records.",
90
82
  updated_count,
91
83
  video.uuid,
92
84
  )
85
+ if files_exist_on_disk and not state.frames_extracted:
86
+ logger.warning(
87
+ "Files exist on disk for video %s but state.frames_extracted is False. Persisting corrected state.",
88
+ video.uuid,
89
+ )
90
+ state.mark_frames_extracted(save=True)
93
91
  return True
94
92
 
95
93
  # Overwrite: delete existing frames/files before re-extraction.
@@ -122,16 +120,14 @@ def _extract_frames(
122
120
  video.uuid,
123
121
  )
124
122
  if video.frame_count is not None and video.frame_count > 0:
125
- raise RuntimeError(
126
- f"ffmpeg_extract_frames returned no paths for video {video.uuid}, but {video.frame_count} frames were expected."
127
- )
123
+ raise RuntimeError(f"ffmpeg_extract_frames returned no paths for video {video.uuid}, but {video.frame_count} frames were expected.")
128
124
 
129
125
  logger.info("Successfully extracted %d frames using ffmpeg for video %s.", len(extracted_paths), video.uuid)
130
126
 
131
127
  extracted_frame_numbers = []
132
128
  for frame_path in extracted_paths:
133
129
  try:
134
- frame_number = int(frame_path.stem.split('_')[-1])
130
+ frame_number = int(frame_path.stem.split("_")[-1])
135
131
  extracted_frame_numbers.append(frame_number)
136
132
  except (ValueError, IndexError) as e:
137
133
  logger.warning("Could not parse frame number from extracted file %s: %s", frame_path.name, e)
@@ -140,9 +136,7 @@ def _extract_frames(
140
136
  with transaction.atomic():
141
137
  if extracted_frame_numbers:
142
138
  try:
143
- update_count = Frame.objects.filter(
144
- video=video, frame_number__in=extracted_frame_numbers
145
- ).update(is_extracted=True)
139
+ update_count = Frame.objects.filter(video=video, frame_number__in=extracted_frame_numbers).update(is_extracted=True)
146
140
  logger.info("Marked %d Frame objects as is_extracted=True for video %s.", update_count, video.uuid)
147
141
  if update_count != len(extracted_frame_numbers):
148
142
  logger.warning(
@@ -153,9 +147,8 @@ def _extract_frames(
153
147
  )
154
148
  except Exception as update_e:
155
149
  logger.error("Failed to update is_extracted flag for frames of video %s: %s", video.uuid, update_e, exc_info=True)
156
- if extracted_paths:
157
- state.refresh_from_db()
158
- state.mark_frames_extracted()
150
+ state.refresh_from_db()
151
+ state.mark_frames_extracted()
159
152
  return True
160
153
 
161
154
  except Exception as e:
@@ -1,18 +1,16 @@
1
- from typing import TYPE_CHECKING
2
-
3
1
  import logging
2
+ from typing import TYPE_CHECKING
4
3
 
5
4
  if TYPE_CHECKING:
6
- from endoreg_db.models import VideoFile, Frame
7
-
8
- logger = logging.getLogger(__name__)
9
-
5
+ from endoreg_db.models import Frame, VideoFile
10
6
 
7
+ logger = logging.getLogger(__name__)
11
8
 
12
9
 
13
10
  def _get_frame(video: "VideoFile", frame_number: int) -> "Frame":
14
11
  """Gets a specific Frame object by its frame number."""
15
12
  from endoreg_db.models import Frame
13
+
16
14
  try:
17
15
  # Access related manager directly
18
16
  return video.frames.get(frame_number=frame_number)
@@ -22,7 +20,7 @@ def _get_frame(video: "VideoFile", frame_number: int) -> "Frame":
22
20
  return Frame.objects.get(video_file=video, frame_number=frame_number)
23
21
  except Frame.DoesNotExist:
24
22
  logger.error("Frame %d not found for video %s.", frame_number, video.uuid)
25
- raise # Re-raise DoesNotExist
23
+ raise # Re-raise DoesNotExist
26
24
  except Exception as e:
27
25
  logger.error("Error getting frame %d for video %s: %s", frame_number, video.uuid, e, exc_info=True)
28
- raise # Re-raise other exceptions
26
+ raise # Re-raise other exceptions
@@ -1,27 +1,13 @@
1
-
2
- from typing import TYPE_CHECKING
3
-
4
1
  import logging
2
+ from typing import TYPE_CHECKING
5
3
 
6
4
  if TYPE_CHECKING:
7
5
  from endoreg_db.models import VideoFile
8
-
9
- logger = logging.getLogger(__name__)
10
-
11
6
 
7
+ logger = logging.getLogger(__name__)
12
8
 
13
9
 
14
10
  def _get_frame_number(video: "VideoFile") -> int:
15
11
  """Counts the number of associated Frame objects in the database."""
16
- try:
17
- # Access related manager directly
18
- return video.frames.count()
19
- except AttributeError:
20
- logger.error("Could not access frame count for video %s. 'frames' related manager not found.", str(video))
21
- # Fallback query (less efficient)
22
- frame_model = video.get_frame_model()
23
- return frame_model.objects.filter(video_file=video).count()
24
- except Exception as e:
25
- logger.error("Error counting frames for video %s: %s", video.uuid, e, exc_info=True)
26
- return 0
27
-
12
+ # Access related manager directly
13
+ return video.frames.count()
@@ -1,20 +1,21 @@
1
1
  # --- Frame Creation/Deletion ---
2
2
  import logging
3
- from typing import TYPE_CHECKING, Optional
4
3
  from pathlib import Path
4
+ from typing import TYPE_CHECKING, Optional
5
5
 
6
6
  if TYPE_CHECKING:
7
7
  from endoreg_db.models import VideoFile
8
8
 
9
9
  logger = logging.getLogger(__name__)
10
10
 
11
+
11
12
  def _get_frame_path(video: "VideoFile", frame_number: int) -> Optional[Path]:
12
13
  """Constructs the expected path for a given frame number."""
13
- target_dir = video.get_frame_dir_path() # Use IO helper
14
+ target_dir = video.get_frame_dir_path() # Use IO helper
14
15
  if not target_dir:
15
16
  logger.warning("Cannot get frame path for video %s: Frame directory not set.", video.uuid)
16
17
  return None
17
18
 
18
19
  frame_filename = f"frame_{frame_number:07d}.jpg"
19
20
  path = target_dir / frame_filename
20
- return path
21
+ return path
@@ -1,14 +1,15 @@
1
- from endoreg_db.models.media.video.video_file_io import _get_frame_dir_path
2
-
3
- from pathlib import Path
4
1
  import logging
5
-
2
+ from pathlib import Path
6
3
  from typing import TYPE_CHECKING, List
4
+
5
+ from endoreg_db.models.media.video.video_file_io import _get_frame_dir_path
6
+
7
7
  if TYPE_CHECKING:
8
8
  from endoreg_db.models import VideoFile
9
9
 
10
10
  logger = logging.getLogger(__name__)
11
11
 
12
+
12
13
  def _get_frame_paths(video: "VideoFile") -> List[Path]:
13
14
  """Returns a sorted list of Path objects for extracted frame image files."""
14
15
  frame_dir = _get_frame_dir_path(video)
@@ -16,10 +17,10 @@ def _get_frame_paths(video: "VideoFile") -> List[Path]:
16
17
  logger.warning("Frame directory %s does not exist for video %s.", frame_dir, video.uuid)
17
18
  return []
18
19
 
19
- frame_paths = list(frame_dir.glob('frame_*.jpg'))
20
+ frame_paths = list(frame_dir.glob("frame_*.jpg"))
20
21
 
21
22
  try:
22
- frame_paths.sort(key=lambda p: int(p.stem.split('_')[-1]))
23
+ frame_paths.sort(key=lambda p: int(p.stem.split("_")[-1]))
23
24
  except (ValueError, IndexError) as e:
24
25
  logger.error("Error sorting frame paths in %s: %s. Found paths: %s", frame_dir, e, [p.name for p in frame_paths], exc_info=True)
25
26
  logger.warning("Falling back to unsorted frame paths to preserve available data.")
@@ -1,20 +1,18 @@
1
-
1
+ import logging
2
2
  from typing import TYPE_CHECKING
3
+
3
4
  from django.db.models import QuerySet
4
- import logging
5
5
 
6
6
  if TYPE_CHECKING:
7
- from endoreg_db.models import VideoFile, Frame
8
-
9
- logger = logging.getLogger(__name__)
10
-
11
-
7
+ from endoreg_db.models import Frame, VideoFile
12
8
 
9
+ logger = logging.getLogger(__name__)
13
10
 
14
11
 
15
12
  def _get_frame_range(video: "VideoFile", start_frame_number: int, end_frame_number: int) -> "QuerySet[Frame]":
16
13
  """Gets a QuerySet of Frame objects within a specific range, ordered by frame number."""
17
14
  from endoreg_db.models import Frame
15
+
18
16
  try:
19
17
  # Access related manager directly
20
18
  return video.frames.filter(
@@ -31,4 +29,4 @@ def _get_frame_range(video: "VideoFile", start_frame_number: int, end_frame_numb
31
29
  ).order_by("frame_number")
32
30
  except Exception as e:
33
31
  logger.error("Error getting frame range (%d-%d) for video %s: %s", start_frame_number, end_frame_number, video.uuid, e, exc_info=True)
34
- return Frame.objects.none() # Return empty queryset on error
32
+ return Frame.objects.none() # Return empty queryset on error
@@ -1,20 +1,18 @@
1
- from django.db.models import QuerySet
2
-
1
+ import logging
3
2
  from typing import TYPE_CHECKING
4
3
 
5
- import logging
4
+ from django.db.models import QuerySet
6
5
 
7
6
  if TYPE_CHECKING:
8
- from endoreg_db.models import VideoFile, Frame
9
-
10
- logger = logging.getLogger(__name__)
11
-
7
+ from endoreg_db.models import Frame, VideoFile
12
8
 
9
+ logger = logging.getLogger(__name__)
13
10
 
14
11
 
15
12
  def _get_frames(video: "VideoFile") -> "QuerySet[Frame]":
16
13
  """Gets a QuerySet of all associated Frame objects, ordered by frame number."""
17
14
  from endoreg_db.models import Frame
15
+
18
16
  try:
19
17
  # Access related manager directly
20
18
  return video.frames.order_by("frame_number")
@@ -24,4 +22,4 @@ def _get_frames(video: "VideoFile") -> "QuerySet[Frame]":
24
22
  return Frame.objects.filter(video_file=video).order_by("frame_number")
25
23
  except Exception as e:
26
24
  logger.error("Error getting frames for video %s: %s", video.uuid, e, exc_info=True)
27
- return Frame.objects.none() # Return empty queryset on error
25
+ return Frame.objects.none() # Return empty queryset on error