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
@@ -4,15 +4,18 @@ import logging
4
4
  import os
5
5
  import uuid
6
6
  from pathlib import Path
7
- from typing import TYPE_CHECKING, Optional, Union, cast
7
+ from typing import TYPE_CHECKING, Optional, Self, Union, cast
8
8
 
9
9
  from django.core.files import File
10
10
  from django.core.validators import FileExtensionValidator
11
11
  from django.db import models
12
12
  from django.db.models import F
13
13
  from django.db.models.fields.files import FieldFile
14
+ from librosa import frames_to_samples
15
+ from pandas.core import frame
14
16
 
15
17
  from endoreg_db.utils.calc_duration_seconds import _calc_duration_vf
18
+ from endoreg_db.utils.video.ffmpeg_wrapper import assemble_video_from_frames
16
19
 
17
20
  from ...label import Label, LabelVideoSegment
18
21
  from ...state import VideoState
@@ -23,11 +26,7 @@ from .create_from_file import _create_from_file
23
26
  from .pipe_1 import _pipe_1, _test_after_pipe_1
24
27
  from .pipe_2 import _pipe_2
25
28
  from .video_file_ai import _extract_text_from_video_frames, _predict_video_pipeline
26
- from .video_file_anonymize import (
27
- _anonymize,
28
- _cleanup_raw_assets,
29
- _create_anonymized_frame_files,
30
- )
29
+ from .video_file_anonymize import _anonymize, _censor_outside_frames, _cleanup_raw_assets, _create_anonymized_frame_files
31
30
  from .video_file_frames import (
32
31
  _bulk_create_frames,
33
32
  _create_frame_object,
@@ -72,6 +71,8 @@ from .video_file_meta import (
72
71
  logger = logging.getLogger(__name__) # Changed from "video_file"
73
72
 
74
73
  if TYPE_CHECKING:
74
+ from django.db.models.fields.files import FieldFile
75
+
75
76
  from endoreg_db.models import (
76
77
  Center,
77
78
  EndoscopyProcessor,
@@ -84,6 +85,7 @@ if TYPE_CHECKING:
84
85
  VideoImportMeta,
85
86
  VideoMeta,
86
87
  VideoState,
88
+ SensitiveMeta
87
89
  )
88
90
 
89
91
 
@@ -125,9 +127,7 @@ class VideoFile(models.Model):
125
127
  blank=True,
126
128
  )
127
129
 
128
- video_hash = models.CharField(
129
- max_length=255, unique=True, help_text="Hash of the raw video file."
130
- )
130
+ video_hash = models.CharField(max_length=255, unique=True, help_text="Hash of the raw video file.")
131
131
  processed_video_hash = models.CharField(
132
132
  max_length=255,
133
133
  unique=True,
@@ -142,45 +142,39 @@ class VideoFile(models.Model):
142
142
  null=True,
143
143
  blank=True,
144
144
  related_name="video_file",
145
- ) # type: ignore
146
- center = models.ForeignKey("Center", on_delete=models.PROTECT) # type: ignore
147
- processor = models.ForeignKey(
148
- "EndoscopyProcessor", on_delete=models.PROTECT, blank=True, null=True
149
- ) # type: ignore
145
+ )
146
+ center = models.ForeignKey("Center", on_delete=models.PROTECT)
147
+ processor = models.ForeignKey("EndoscopyProcessor", on_delete=models.PROTECT, blank=True, null=True)
150
148
  video_meta = models.OneToOneField(
151
149
  "VideoMeta",
152
150
  on_delete=models.SET_NULL,
153
151
  null=True,
154
152
  blank=True,
155
153
  related_name="video_file",
156
- ) # type: ignore
154
+ )
157
155
  examination = models.ForeignKey(
158
156
  "PatientExamination",
159
157
  on_delete=models.SET_NULL,
160
158
  blank=True,
161
159
  null=True,
162
160
  related_name="video_files",
163
- ) # type: ignore
161
+ )
164
162
  patient = models.ForeignKey(
165
163
  "Patient",
166
164
  on_delete=models.SET_NULL,
167
165
  blank=True,
168
166
  null=True,
169
167
  related_name="video_files",
170
- ) # type: ignore
171
- ai_model_meta = models.ForeignKey(
172
- "ModelMeta", on_delete=models.SET_NULL, blank=True, null=True
173
- ) # type: ignore
168
+ )
169
+ ai_model_meta = models.ForeignKey("ModelMeta", on_delete=models.SET_NULL, blank=True, null=True)
174
170
  state = models.OneToOneField(
175
171
  "VideoState",
176
172
  on_delete=models.SET_NULL,
177
173
  null=True,
178
174
  blank=True,
179
175
  related_name="video_file",
180
- ) # type: ignore
181
- import_meta = models.OneToOneField(
182
- "VideoImportMeta", on_delete=models.CASCADE, blank=True, null=True
183
- ) # type: ignore
176
+ )
177
+ import_meta = models.OneToOneField("VideoImportMeta", on_delete=models.CASCADE, blank=True, null=True)
184
178
 
185
179
  original_file_name = models.CharField(max_length=255, blank=True, null=True)
186
180
  uploaded_at = models.DateTimeField(auto_now_add=True)
@@ -206,17 +200,25 @@ class VideoFile(models.Model):
206
200
  date_modified = models.DateTimeField(auto_now=True)
207
201
 
208
202
  if TYPE_CHECKING:
209
- label_video_segments: "models.QuerySet[LabelVideoSegment]"
210
- frames: "models.QuerySet[Frame]"
211
- center: "Center"
212
- processor: "EndoscopyProcessor"
213
- video_meta: "VideoMeta"
214
- examination: "PatientExamination"
215
- patient: "Patient"
216
- sensitive_meta: "SensitiveMeta"
217
- state: "VideoState"
218
- ai_model_meta: "ModelMeta"
219
- import_meta: "VideoImportMeta"
203
+ from django.db.models.manager import RelatedManager
204
+
205
+ @property
206
+ def label_video_segments(self) -> RelatedManager[LabelVideoSegment]: ...
207
+
208
+ @property
209
+ def frames(self) -> RelatedManager[Frame]: ...
210
+
211
+ center: models.ForeignKey["Center"]
212
+ processor: models.ForeignKey["EndoscopyProcessor | None"]
213
+ video_meta: models.OneToOneField["VideoMeta | None"]
214
+ examination: models.ForeignKey["PatientExamination | None"]
215
+ patient: models.ForeignKey["Patient | None"]
216
+ sensitive_meta: models.OneToOneField["SensitiveMeta | None"]
217
+ state: models.OneToOneField["VideoState | None"]
218
+ ai_model_meta: models.ForeignKey["ModelMeta | None"]
219
+ import_meta: models.OneToOneField["VideoImportMeta | None"]
220
+ raw_file = cast(FieldFile, raw_file)
221
+ processed_file = cast(FieldFile, processed_file)
220
222
 
221
223
  @property
222
224
  def ffmpeg_meta(self) -> "FFMpegMeta":
@@ -257,7 +259,10 @@ class VideoFile(models.Model):
257
259
  assert _file is not None, self.NO_ACTIVE_FILE
258
260
  if not _file or not _file.name:
259
261
  raise ValueError(self.NO_FILE_ASSOCIATED)
260
- return _file.url
262
+ url = getattr(_file, "url", None)
263
+ if not url:
264
+ raise ValueError("Active raw file URL could not be resolved.")
265
+ return str(url)
261
266
 
262
267
  # Pipeline Functions
263
268
  pipe_1 = _pipe_1
@@ -286,9 +291,7 @@ class VideoFile(models.Model):
286
291
  bulk_create_frames = _bulk_create_frames
287
292
 
288
293
  # Define new methods that call the helper functions
289
- def extract_specific_frame_range(
290
- self, start_frame: int, end_frame: int, overwrite: bool = False, **kwargs
291
- ) -> bool:
294
+ def extract_specific_frame_range(self, start_frame: int, end_frame: int, overwrite: bool = False, **kwargs) -> bool:
292
295
  """
293
296
  Extract frames from the video within the specified frame range.
294
297
 
@@ -311,13 +314,9 @@ class VideoFile(models.Model):
311
314
 
312
315
  # Log if unexpected kwargs are passed, beyond those used by the helper
313
316
  expected_helper_kwargs = {"quality", "ext", "verbose"}
314
- unexpected_kwargs = {
315
- k: v for k, v in kwargs.items() if k not in expected_helper_kwargs
316
- }
317
+ unexpected_kwargs = {k: v for k, v in kwargs.items() if k not in expected_helper_kwargs}
317
318
  if unexpected_kwargs:
318
- logger.warning(
319
- f"Unexpected keyword arguments for extract_specific_frame_range, will be ignored by helper: {unexpected_kwargs}"
320
- )
319
+ logger.warning(f"Unexpected keyword arguments for extract_specific_frame_range, will be ignored by helper: {unexpected_kwargs}")
321
320
 
322
321
  return _extract_frame_range_helper(
323
322
  video=self,
@@ -333,9 +332,7 @@ class VideoFile(models.Model):
333
332
  """
334
333
  Deletes frame files for a specific range [start_frame, end_frame).
335
334
  """
336
- _delete_frame_range_helper(
337
- video=self, start_frame=start_frame, end_frame=end_frame
338
- )
335
+ _delete_frame_range_helper(video=self, start_frame=start_frame, end_frame=end_frame)
339
336
 
340
337
  delete_with_file = _delete_with_file
341
338
  get_base_frame_dir = _get_base_frame_dir
@@ -390,9 +387,7 @@ class VideoFile(models.Model):
390
387
  if isinstance(raw, FieldFile) and raw.name:
391
388
  return raw
392
389
 
393
- raise ValueError(
394
- "No active file available. VideoFile has neither raw nor processed file."
395
- )
390
+ raise ValueError("No active file available. VideoFile has neither raw nor processed file.")
396
391
 
397
392
  @property
398
393
  def active_file_path(self) -> Path:
@@ -411,18 +406,35 @@ class VideoFile(models.Model):
411
406
  elif active is self.raw_file:
412
407
  path = _get_raw_file_path(self)
413
408
  else:
414
- raise ValueError(
415
- "No active file path available. VideoFile has neither raw nor processed file."
416
- )
409
+ raise ValueError("No active file path available. VideoFile has neither raw nor processed file.")
417
410
 
418
411
  if path is None:
419
- raise ValueError("Active file path could not be resolved.")
412
+ raise ValueError("Active file path could not be resolved. VideoFile raw file is missing.")
420
413
  return path
421
414
 
415
+ @property
416
+ def active_file_url(self) -> str:
417
+ """Return the URL of the active video file, if available."""
418
+ file_obj = self.active_file
419
+ if not isinstance(file_obj, FieldFile):
420
+ raise ValueError("Active file is not a valid Django FieldFile instance.")
421
+ try:
422
+ url = getattr(file_obj, "url", None)
423
+ except Exception as exc: # storage backends may raise when missing
424
+ logger.warning(
425
+ "Active file URL unavailable for video %s: %s",
426
+ self.uuid,
427
+ exc,
428
+ )
429
+ raise ValueError("Active file URL could not be resolved for this VideoFile.") from exc
430
+
431
+ if not url:
432
+ raise ValueError("Active file URL is empty for this VideoFile.")
433
+
434
+ return str(url)
435
+
422
436
  @classmethod
423
- def create_from_file(
424
- cls, file_path: Union[str, Path], center_name: str, **kwargs
425
- ) -> Optional["VideoFile"]:
437
+ def create_from_file(cls, file_path: Union[str, Path], center_name: str, **kwargs) -> Optional["VideoFile"]:
426
438
  # Ensure file_path is a Path object
427
439
  if isinstance(file_path, str):
428
440
  file_path = Path(file_path)
@@ -431,9 +443,7 @@ class VideoFile(models.Model):
431
443
  try:
432
444
  center_name = os.environ["CENTER_NAME"]
433
445
  except KeyError:
434
- logger.error(
435
- "Center name must be provided to create VideoFile from file. You can set CENTER_NAME in environment variables."
436
- )
446
+ logger.error("Center name must be provided to create VideoFile from file. You can set CENTER_NAME in environment variables.")
437
447
  return None
438
448
  return _create_from_file(cls, file_path, center_name=center_name, **kwargs)
439
449
 
@@ -478,19 +488,22 @@ class VideoFile(models.Model):
478
488
  _delete_frames(self)
479
489
 
480
490
  # Call the original delete method to remove the instance from the database
481
- active_path = self.active_file_path
482
- logger.info(f"Deleting VideoFile: {self.uuid} - {active_path}")
491
+ try:
492
+ active_path = self.active_file_path
493
+ logger.info(f"Deleting VideoFile: {self.uuid} - {active_path}")
494
+
495
+ except ValueError:
496
+ logger.info(f"Deleting VideoFile: {self.uuid} - No active file path found.")
497
+ active_path = None
483
498
 
484
499
  # Delete associated files if they exist
485
- if active_path.exists():
500
+ if active_path and active_path.exists():
486
501
  active_path.unlink(missing_ok=True)
487
502
 
488
503
  # Delete file storage
489
504
  if self.raw_file and self.raw_file.storage.exists(self.raw_file.name):
490
505
  self.raw_file.storage.delete(self.raw_file.name)
491
- if self.processed_file and self.processed_file.storage.exists(
492
- self.processed_file.name
493
- ):
506
+ if self.processed_file and self.processed_file.storage.exists(self.processed_file.name):
494
507
  self.processed_file.storage.delete(self.processed_file.name)
495
508
 
496
509
  # Use proper database connection
@@ -517,9 +530,7 @@ class VideoFile(models.Model):
517
530
  logger.error(f"Error deleting VideoFile {self.uuid}: {e}")
518
531
  raise
519
532
 
520
- def validate_metadata_annotation(
521
- self, extracted_data_dict: Optional[dict] = None
522
- ) -> bool:
533
+ def validate_metadata_annotation(self, extracted_data_dict: Optional[dict] = None) -> bool:
523
534
  """
524
535
  Validate the metadata of the VideoFile instance.
525
536
 
@@ -531,57 +542,44 @@ class VideoFile(models.Model):
531
542
  **IMPORTANT:** Only the raw video is deleted. The processed (anonymized)
532
543
  video is preserved as the final validated output.
533
544
  """
534
- from datetime import date as dt_date
535
-
536
- from endoreg_db.models import SensitiveMeta
537
545
 
538
546
  if not self.sensitive_meta:
539
- # CRITICAL FIX: Use create_from_dict with default patient data
540
- default_data = {
541
- "patient_first_name": "Patient",
542
- "patient_last_name": "Unknown",
543
- "patient_dob": dt_date(1990, 1, 1),
544
- "examination_date": dt_date.today(),
545
- "center": self.center,
546
- }
547
- self.sensitive_meta = SensitiveMeta.create_from_dict(default_data)
548
-
547
+ # Ensure a SensitiveMeta exists so validation can proceed.
548
+ self.sensitive_meta = self.get_or_create_sensitive_meta()
549
549
  # CRITICAL FIX: Delete RAW video file, not the processed (anonymized) one
550
550
  # CRITICAL: Update metadata BEFORE deleting raw video
551
- # Metadata update may trigger frame extraction, which needs raw video
552
- sensitive_meta = _update_text_metadata(
553
- self, extracted_data_dict, overwrite=True
554
- )
551
+ if extracted_data_dict:
552
+ self.sensitive_meta.update_from_dict(extracted_data_dict)
553
+ else:
554
+ return False
555
555
 
556
556
  # After validation and metadata update, only the anonymized video should remain
557
557
  from .video_file_io import _get_raw_file_path
558
558
 
559
559
  raw_path = _get_raw_file_path(self)
560
+
560
561
  if raw_path and raw_path.exists():
561
562
  logger.info(f"Deleting raw video file after validation: {raw_path}")
562
563
  raw_path.unlink(missing_ok=True)
563
564
  # Clear the raw_file field in database (use delete() to avoid save issues)
564
565
  if self.raw_file:
565
566
  self.raw_file.delete(save=False)
566
- logger.info(
567
- f"Raw video deleted for {self.uuid}. Anonymized video preserved."
568
- )
567
+ logger.info(f"Raw video deleted for {self.uuid}. Anonymized video preserved.")
569
568
  else:
570
- logger.warning(f"Raw video file not found for deletion: {self.uuid}")
569
+ logger.warning(
570
+ "Raw video file not found for deletion during validation %s.",
571
+ self.uuid,
572
+ )
571
573
 
572
- if sensitive_meta:
574
+ if self.sensitive_meta:
573
575
  # Mark as processed after validation
574
- self.get_or_create_state().mark_sensitive_meta_processed(save=True)
576
+ self.get_or_create_state().mark_anonymization_validated(save=True)
575
577
  # Save the VideoFile instance to persist changes
576
578
  self.save()
577
- logger.info(
578
- f"Metadata annotation validated and saved for video {self.uuid}."
579
- )
579
+ logger.info(f"Metadata annotation validated and saved for video {self.uuid}.")
580
580
  return True
581
581
  else:
582
- logger.error(
583
- f"Failed to validate metadata annotation for video {self.uuid}."
584
- )
582
+ logger.error(f"Failed to validate metadata annotation for video {self.uuid}.")
585
583
  return False
586
584
 
587
585
  def initialize(self):
@@ -615,9 +613,7 @@ class VideoFile(models.Model):
615
613
  """
616
614
  active_path = self.active_file_path
617
615
  file_name = active_path.name if active_path else "No file"
618
- state = (
619
- "Processed" if self.is_processed else ("Raw" if self.has_raw else "No File")
620
- )
616
+ state = "Processed" if self.is_processed else ("Raw" if self.has_raw else "No File")
621
617
  return f"VideoFile ({state}): {file_name} (UUID: {self.uuid})"
622
618
 
623
619
  # --- Convenience state/meta helpers used in tests and admin workflows ---
@@ -734,9 +730,7 @@ class VideoFile(models.Model):
734
730
  # Do not mark state as processed here; it will be set after extraction/validation steps
735
731
  return self.sensitive_meta
736
732
 
737
- def get_outside_segments(
738
- self, only_validated: bool = False
739
- ) -> models.QuerySet["LabelVideoSegment"]:
733
+ def get_outside_segments(self, only_validated: bool = False) -> models.QuerySet["LabelVideoSegment"]:
740
734
  """
741
735
  Return all video segments labeled as "outside" for this video.
742
736
 
@@ -767,6 +761,56 @@ class VideoFile(models.Model):
767
761
  )
768
762
  return self.label_video_segments.none()
769
763
 
764
+ @classmethod
765
+ def create_video_without_outside_frames(cls, instance: "VideoFile", only_validated: bool = False) -> bool:
766
+ """
767
+ Creates a new video by excluding frames that belong to 'outside' segments.
768
+
769
+ Parameters:
770
+ only_validated (bool): If True, only validated segments are considered for frame exclusion.
771
+
772
+ Returns:
773
+ VideoFile: A new VideoFile instance with the frames excluding those labeled as 'outside'.
774
+ """
775
+ video = instance
776
+
777
+ if not video:
778
+ logger.warning("No processed video file available for VideoFile %s.", cls.uuid)
779
+ return False
780
+ try:
781
+ extracted = video.extract_frames(quality=2, overwrite=False, ext="jpg", verbose=False, from_processed=True)
782
+ assert extracted is True
783
+ except AssertionError:
784
+ # Use default anonymization here
785
+ video.anonymize
786
+ extracted = video.extract_frames(quality=2, overwrite=False, ext="jpg", verbose=False, from_processed=True)
787
+ assert extracted is True
788
+ try:
789
+ # Step 1: Get the "outside" labeled frames
790
+ censored = _censor_outside_frames(video)
791
+ frames = [instance.get_frame_dir_path()]
792
+ assert len(frames) != 0
793
+ fps = video.fps if video.fps else 120.0 # Default to 30 FPS if fps is not set
794
+ assert fps is not None
795
+ assert video.width is not None
796
+ assert video.height is not None
797
+
798
+ # Step 2: Reassemble the video with frames excluding the 'outside' labeled frames
799
+ output_video_path = Path(f"/path/to/output/{cls.uuid}_filtered.mp4")
800
+ fps = cls.fps if cls.fps else 30.0 # Default to 30 FPS if fps is not set
801
+ new_video_file = assemble_video_from_frames(frames, output_video_path, fps, width=video.width, height=video.height)
802
+ video.processed_file = new_video_file
803
+ return True
804
+ except AssertionError as ae:
805
+ logger.error(f"Assertion error while creating video without 'outside' frames for VideoFile {cls.uuid}: {ae}", exc_info=True)
806
+ return False
807
+ except Label.DoesNotExist:
808
+ logger.warning("Outside label not found in the database.")
809
+ return False
810
+ except Exception as e:
811
+ logger.error(f"Error creating video without 'outside' frames for VideoFile {cls.uuid}: {e}", exc_info=True)
812
+ return False
813
+
770
814
  @classmethod
771
815
  def get_all_videos(cls) -> models.QuerySet["VideoFile"]:
772
816
  """
@@ -784,9 +828,7 @@ class VideoFile(models.Model):
784
828
  int: The count of VideoFile records, excluding this instance, where the modification timestamp matches the creation timestamp.
785
829
  """
786
830
  return (
787
- VideoFile.objects.filter(
788
- date_modified=F("date_created")
789
- ) # compare the two fields in SQL
831
+ VideoFile.objects.filter(date_modified=F("date_created")) # compare the two fields in SQL
790
832
  .exclude(pk=self.pk) # exclude this instance
791
833
  .count() # run a fast COUNT(*) on the filtered set
792
834
  )