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,15 +1,17 @@
1
1
  import logging
2
- from typing import TYPE_CHECKING, Optional, Dict
3
- import cv2
4
2
  from pathlib import Path
3
+ from typing import TYPE_CHECKING
4
+
5
+ import cv2
5
6
 
6
7
  if TYPE_CHECKING:
7
8
  from ..video_file import VideoFile
8
9
 
10
+
9
11
  def _validate_video_path(video_path: Path):
10
12
  """
11
13
  Validates that the provided path is an existing video file.
12
-
14
+
13
15
  Raises:
14
16
  TypeError: If `video_path` is not a Path object.
15
17
  FileNotFoundError: If the file does not exist at the specified path.
@@ -24,45 +26,53 @@ def _validate_video_path(video_path: Path):
24
26
 
25
27
 
26
28
  logger = logging.getLogger(__name__)
29
+
30
+
27
31
  def _get_fps(video: "VideoFile") -> float:
28
32
  """
29
33
  Determine and return the frames per second (FPS) of a video associated with a VideoFile instance.
30
-
34
+
31
35
  Attempts to retrieve FPS from the instance itself, its linked VideoMeta, or by direct analysis of the raw video file using OpenCV. Updates and saves the FPS value to the instance if successfully determined. Raises a ValueError if FPS cannot be determined by any method.
32
-
36
+
33
37
  Returns:
34
38
  float: The frames per second (FPS) of the video.
35
-
39
+
36
40
  Raises:
37
41
  ValueError: If the FPS cannot be determined from any available source.
38
42
  """
39
43
  from .video_meta import _update_video_meta
44
+
40
45
  if video.fps is not None:
41
46
  return video.fps
42
47
 
43
48
  logger.debug("FPS not set on instance %s, checking VideoMeta.", video.uuid)
44
49
 
45
-
46
50
  if not video.video_meta:
47
51
  logger.info("VideoMeta not linked for %s, attempting update.", video.uuid)
48
52
 
49
- _update_video_meta(video, save_instance=True) # Call the helper function
53
+ _update_video_meta(video, save_instance=True) # Call the helper function
50
54
 
51
55
  # Check again after potential update
52
56
  if video.fps is not None:
53
57
  return video.fps
54
58
  elif video.video_meta and video.video_meta.fps is not None:
55
59
  logger.info("Retrieved FPS %.2f from VideoMeta for %s.", video.video_meta.fps, video.uuid)
56
- video.fps = video.video_meta.fps
60
+ _fps = video.video_meta.fps
61
+ try:
62
+ _fps = float(_fps)
63
+ except (TypeError, ValueError):
64
+ logger.warning("Invalid FPS value %.2f in VideoMeta for video %s.", video.video_meta.fps, video.uuid)
65
+ raise ValueError(f"Could not determine FPS for video {video.uuid} due to invalid VideoMeta FPS value.")
66
+ video.fps = _fps
57
67
  # Avoid saving if called from within the save method itself
58
- if not getattr(video, '_saving', False):
68
+ if not getattr(video, "_saving", False):
59
69
  video.save(update_fields=["fps"])
60
- return video.fps
70
+ return _fps
61
71
  else:
62
72
  logger.warning("Could not determine FPS from VideoMeta for video %s. Trying direct raw file access.", video.uuid)
63
73
  try:
64
74
  if video.has_raw:
65
- video_path = video.get_raw_file_path() # Use helper
75
+ video_path = video.get_raw_file_path() # Use helper
66
76
  if video_path and video_path.exists():
67
77
  cap = cv2.VideoCapture(video_path.as_posix())
68
78
  if not cap.isOpened():
@@ -77,11 +87,11 @@ def _get_fps(video: "VideoFile") -> float:
77
87
  finally:
78
88
  cap.release()
79
89
  if fps and fps > 0:
80
- video.fps = fps
81
- logger.info("Determined FPS %.2f directly from file for %s.", video.fps, video.uuid)
82
- if not getattr(video, '_saving', False):
83
- video.save(update_fields=["fps"])
84
- return video.fps
90
+ video.fps = fps
91
+ logger.info("Determined FPS %.2f directly from file for %s.", video.fps, video.uuid)
92
+ if not getattr(video, "_saving", False):
93
+ video.save(update_fields=["fps"])
94
+ return fps
85
95
  else:
86
96
  logger.warning("Could not determine a valid FPS for video file %s.", video_path)
87
97
  elif video_path:
@@ -92,40 +102,33 @@ def _get_fps(video: "VideoFile") -> float:
92
102
  logger.warning("Raw file not available for direct FPS check.")
93
103
 
94
104
  except Exception as e:
95
- logger.error("Error getting FPS directly from file %s: %s", video.raw_file.name if video.has_raw else 'N/A', e)
96
-
97
- raise ValueError(
98
- f"Could not determine FPS for video {video.uuid}. "
99
- "Ensure the video file is valid and accessible."
100
- )
105
+ logger.error("Error getting FPS directly from file %s: %s", video.raw_file.name if video.has_raw else "N/A", e)
101
106
 
107
+ raise ValueError(f"Could not determine FPS for video {video.uuid}. Ensure the video file is valid and accessible.")
102
108
 
103
109
 
104
110
  # TODO Refactor to utils / check if similar function exists in utils
105
- def _get_fps_from_property(cap) -> float:
111
+ def _get_fps_from_property(cap: cv2.VideoCapture) -> float:
106
112
  """
107
113
  Retrieve the frames per second (FPS) from an OpenCV video capture object using the appropriate property for the OpenCV version.
108
-
114
+
109
115
  Parameters:
110
116
  cap: An OpenCV video capture object.
111
-
117
+
112
118
  Returns:
113
119
  float: The FPS value obtained from the video capture properties, or 0.0 if unavailable.
114
120
  """
115
- if hasattr(cv2, 'CAP_PROP_FPS'):
116
- return cap.get(cv2.CAP_PROP_FPS)
117
- # For older OpenCV versions
118
- return cap.get(cv2.cv.CV_CAP_PROP_FPS) # type: ignore
121
+ return cap.get(cv2.CAP_PROP_FPS)
119
122
 
120
123
 
121
124
  def _calculate_fps_manually(cap, video_path: Path) -> float:
122
125
  """
123
126
  Estimate the frames per second (FPS) of a video by reading all frames and dividing the total frame count by the elapsed time.
124
-
127
+
125
128
  Parameters:
126
129
  cap: An OpenCV video capture object positioned at the start of the video.
127
130
  video_path (Path): Path to the video file, used for logging.
128
-
131
+
129
132
  Returns:
130
133
  float: The estimated FPS, or 0.0 if the duration is zero or calculation fails.
131
134
  """
@@ -142,6 +145,6 @@ def _calculate_fps_manually(cap, video_path: Path) -> float:
142
145
  seconds = (end_time - start_time) / cv2.getTickFrequency()
143
146
  if seconds > 0:
144
147
  return num_frames / seconds
145
-
148
+
146
149
  logger.error(f"Manual FPS calculation failed for {video_path} due to zero duration.")
147
- return 0.0
150
+ return 0.0
@@ -2,30 +2,33 @@
2
2
  import logging
3
3
  from pathlib import Path
4
4
  from typing import TYPE_CHECKING, Optional
5
+
5
6
  import cv2
7
+
6
8
  # --- End Add Imports ---
7
9
 
8
10
  if TYPE_CHECKING:
9
- from ..video_file import VideoFile # Correct import path
11
+ from ..video_file import VideoFile # Correct import path
10
12
 
11
13
  # --- Add Logger ---
12
14
  logger = logging.getLogger(__name__)
13
15
  # --- End Add Logger ---
14
16
 
17
+
15
18
  def _initialize_video_specs(video: "VideoFile", use_raw: bool = True) -> bool:
16
19
  """
17
20
  Initializes video specifications for a VideoFile object by reading from the video file.
18
-
21
+
19
22
  Attempts to populate missing values for fps, width, height, frame count, and duration using OpenCV. Selects the raw file if available and requested, otherwise uses the active file. Updates only unset fields if valid values are obtained. Returns True if successful or if no updates are needed. Raises FileNotFoundError if the video file cannot be found, or RuntimeError if the file cannot be opened or properties cannot be read.
20
23
  """
21
24
  video_path: Optional[Path] = None
22
25
  target_file_name: Optional[str] = None
23
26
 
24
27
  if use_raw and video.has_raw:
25
- video_path = video.get_raw_file_path() # Use IO helper
28
+ video_path = video.get_raw_file_path() # Use IO helper
26
29
  target_file_name = video.raw_file.name
27
- elif video.active_file: # Fallback to active file if raw not requested or available
28
- video_path = video.active_file_path # Use property relying on IO helpers
30
+ elif video.active_file: # Fallback to active file if raw not requested or available
31
+ video_path = video.active_file_path # Use property relying on IO helpers
29
32
  target_file_name = video.active_file.name
30
33
  else:
31
34
  logger.error("No suitable video file found for spec initialization for %s.", video.uuid)
@@ -44,7 +47,7 @@ def _initialize_video_specs(video: "VideoFile", use_raw: bool = True) -> bool:
44
47
  video_cap = cv2.VideoCapture(video_path.as_posix())
45
48
  if not video_cap.isOpened():
46
49
  # Raise exception
47
- video_cap.release() # Ensure release
50
+ video_cap.release() # Ensure release
48
51
  raise RuntimeError(f"Could not open video file {video_path} with OpenCV for spec initialization (Video: {video.uuid}).")
49
52
 
50
53
  updated = False
@@ -67,14 +70,14 @@ def _initialize_video_specs(video: "VideoFile", use_raw: bool = True) -> bool:
67
70
  logger.error("Error getting properties from OpenCV for %s (Video: %s): %s", video_path, video.uuid, cv_err, exc_info=True)
68
71
  raise RuntimeError(f"OpenCV failed to get properties for {video_path}") from cv_err
69
72
  finally:
70
- video_cap.release() # Ensure release after getting props
73
+ video_cap.release() # Ensure release after getting props
71
74
 
72
75
  # --- Update FPS ---
73
76
  if current_fps is None and file_fps and file_fps > 0:
74
77
  video.fps = file_fps
75
78
  fields_to_update.append("fps")
76
79
  updated = True
77
- current_fps = file_fps # Update local var for duration calc
80
+ current_fps = file_fps # Update local var for duration calc
78
81
 
79
82
  # --- Update Width ---
80
83
  if current_width is None and file_width > 0:
@@ -93,20 +96,16 @@ def _initialize_video_specs(video: "VideoFile", use_raw: bool = True) -> bool:
93
96
  video.frame_count = file_frame_count
94
97
  fields_to_update.append("frame_count")
95
98
  updated = True
96
- elif file_frame_count is None or file_frame_count <= 0: # Log if not updated due to invalid file_frame_count
97
- logger.warning(
98
- "Invalid frame count (value: %s) obtained from OpenCV for %s. Video frame_count not updated.",
99
- file_frame_count, video_path
100
- )
99
+ elif file_frame_count is None or file_frame_count <= 0: # Log if not updated due to invalid file_frame_count
100
+ logger.warning("Invalid frame count (value: %s) obtained from OpenCV for %s. Video frame_count not updated.", file_frame_count, video_path)
101
101
 
102
102
  # --- Update Duration ---
103
- if current_duration is None: # Only if duration isn't already set
103
+ if current_duration is None: # Only if duration isn't already set
104
104
  # Use the potentially updated video.frame_count and current_fps (which reflects video.fps or file_fps)
105
105
  final_frame_count_for_duration = video.frame_count
106
- final_fps_for_duration = current_fps # This is video.fps after potential update from file_fps
106
+ final_fps_for_duration = current_fps # This is video.fps after potential update from file_fps
107
107
 
108
- if (final_frame_count_for_duration and final_frame_count_for_duration > 0 and
109
- final_fps_for_duration and final_fps_for_duration > 0):
108
+ if final_frame_count_for_duration and final_frame_count_for_duration > 0 and final_fps_for_duration and final_fps_for_duration > 0:
110
109
  video.duration = final_frame_count_for_duration / final_fps_for_duration
111
110
  fields_to_update.append("duration")
112
111
  updated = True
@@ -114,15 +113,10 @@ def _initialize_video_specs(video: "VideoFile", use_raw: bool = True) -> bool:
114
113
  # Log if duration could not be calculated, indicating which component was missing/invalid
115
114
  if not (final_frame_count_for_duration and final_frame_count_for_duration > 0):
116
115
  logger.warning(
117
- "Duration not calculated for %s: frame count is unavailable or invalid (value: %s).",
118
- video_path, final_frame_count_for_duration
116
+ "Duration not calculated for %s: frame count is unavailable or invalid (value: %s).", video_path, final_frame_count_for_duration
119
117
  )
120
118
  if not (final_fps_for_duration and final_fps_for_duration > 0):
121
- logger.warning(
122
- "Duration not calculated for %s: FPS is unavailable or invalid (value: %s).",
123
- video_path, final_fps_for_duration
124
- )
125
-
119
+ logger.warning("Duration not calculated for %s: FPS is unavailable or invalid (value: %s).", video_path, final_fps_for_duration)
126
120
 
127
121
  # --- Save if updated ---
128
122
  if updated:
@@ -137,7 +131,7 @@ def _initialize_video_specs(video: "VideoFile", use_raw: bool = True) -> bool:
137
131
  # Log and re-raise exception
138
132
  logger.error("Error initializing video specs for %s from file %s: %s", video.uuid, video_path, e, exc_info=True)
139
133
  # Ensure capture is released in case of unexpected error
140
- if 'video_cap' in locals() and video_cap.isOpened():
134
+ if "video_cap" in locals() and video_cap.isOpened():
141
135
  video_cap.release()
142
136
  # Re-raise as RuntimeError
143
137
  raise RuntimeError(f"Failed to initialize video specs for {video.uuid} from {video_path}") from e
@@ -1,10 +1,12 @@
1
1
  import logging
2
2
  from typing import TYPE_CHECKING, Optional
3
+
4
+ # --- End Fix ---
5
+ from django.db import transaction
6
+
3
7
  # --- Fix Imports ---
4
8
  from ....metadata import SensitiveMeta
5
9
  from ....metadata.sensitive_meta_logic import update_or_create_sensitive_meta_from_dict
6
- # --- End Fix ---
7
- from django.db import transaction
8
10
 
9
11
  if TYPE_CHECKING:
10
12
  from ..video_file import VideoFile
@@ -38,77 +40,78 @@ def _update_text_metadata(
38
40
  logger.warning(f"Frames not extracted for video {video.uuid}. Attempting automatic frame extraction...")
39
41
  try:
40
42
  success = video.extract_frames(overwrite=False)
41
- if success:
42
- # Refresh state after frame extraction
43
- state.refresh_from_db()
44
- if state.frames_extracted:
45
- logger.info(f"Successfully extracted frames for video {video.uuid}")
46
- else:
47
- # Force update the state if extraction was successful but state wasn't updated
48
- state.frames_extracted = True
49
- state.save(update_fields=['frames_extracted'])
50
- logger.info(f"Corrected frames_extracted state for video {video.uuid}")
51
- else:
52
- raise ValueError(f"Cannot update text metadata for video {video.uuid}: Frame extraction failed.")
53
- except Exception as e:
54
- logger.error(f"Failed to extract frames for video {video.uuid}: {e}")
55
- raise ValueError(f"Cannot update text metadata for video {video.uuid}: Frames not extracted and automatic extraction failed: {e}")
43
+ except (FileNotFoundError, RuntimeError, ValueError, OSError) as exc:
44
+ logger.error(
45
+ "Failed to extract frames for video %s: %s",
46
+ video.uuid,
47
+ exc,
48
+ exc_info=True,
49
+ )
50
+ raise ValueError(f"Cannot update text metadata for video {video.uuid}: Frames not extracted and automatic extraction failed") from exc
51
+
52
+ if not success:
53
+ raise ValueError(f"Cannot update text metadata for video {video.uuid}: Frame extraction returned False")
54
+
55
+ state.refresh_from_db()
56
+ if not state.frames_extracted:
57
+ raise ValueError(f"Cannot update text metadata for video {video.uuid}: Frame extraction completed but state was not updated")
58
+
59
+ logger.info(f"Successfully extracted frames for video {video.uuid}")
56
60
 
57
61
  if state.text_meta_extracted and not overwrite:
58
- logger.info("Text already extracted for video %s and overwrite=False. Skipping.", video.uuid) # Changed to info
59
- return video.sensitive_meta # Return existing meta if available
62
+ logger.info("Text already extracted for video %s and overwrite=False. Skipping.", video.uuid) # Changed to info
63
+ return video.sensitive_meta # Return existing meta if available
60
64
  # --- End Pre-condition Checks ---
61
65
 
62
66
  # Extract text using the AI helper function
63
67
  # _extract_text_from_video_frames raises ValueError on pre-condition failure
64
68
  try:
65
69
  if not extracted_data_dict:
66
- extracted_data_dict = video.extract_text_from_frames(
67
- frame_fraction=ocr_frame_fraction, cap=cap
68
- )
70
+ extracted_data_dict = video.extract_text_from_frames(frame_fraction=ocr_frame_fraction, cap=cap)
69
71
  except Exception as text_extract_e:
70
- logger.error("Failed during text extraction step for video %s: %s", video.uuid, text_extract_e, exc_info=True)
71
- raise RuntimeError(f"Text extraction failed for video {video.uuid}") from text_extract_e
72
+ logger.error("Failed during text extraction step for video %s: %s", video.uuid, text_extract_e, exc_info=True)
73
+ raise RuntimeError(f"Text extraction failed for video {video.uuid}") from text_extract_e
72
74
 
73
75
  # --- Atomic Update Block ---
74
76
  try:
75
77
  with transaction.atomic():
76
78
  # Refresh state in case it changed
77
79
  state.refresh_from_db()
78
- sensitive_meta_instance = video.sensitive_meta # Get current instance
80
+ sensitive_meta_instance = video.sensitive_meta # Get current instance
79
81
 
80
82
  if not extracted_data_dict:
81
83
  logger.warning("No text extracted for video %s; skipping SensitiveMeta update.", video.uuid)
82
84
  # Mark state as retrieved even if no data found, to avoid re-running unless overwrite=True
83
85
  if not state.text_meta_extracted:
84
86
  state.text_meta_extracted = True
85
- state.save(update_fields=['text_meta_extracted'])
86
- return sensitive_meta_instance # Return existing meta if available
87
+ state.save(update_fields=["text_meta_extracted"])
88
+ return sensitive_meta_instance # Return existing meta if available
87
89
 
88
90
  # Add center info if not already present in extracted data
89
- if 'center_name' not in extracted_data_dict and video.center:
90
- extracted_data_dict['center_name'] = video.center.name
91
+ if "center_name" not in extracted_data_dict and video.center:
92
+ extracted_data_dict["center_name"] = video.center.name
91
93
  logger.debug("Data for SensitiveMeta update for video %s: %s", video.uuid, extracted_data_dict)
92
94
 
93
95
  # Pass the Class, the data dict, and the current instance (or None)
94
96
  # This function might raise exceptions if data is invalid
95
97
  sensitive_meta, created = update_or_create_sensitive_meta_from_dict(
96
- SensitiveMeta, # Pass the class
98
+ SensitiveMeta, # Pass the class
97
99
  extracted_data_dict,
98
- instance=sensitive_meta_instance # Pass current instance via keyword
100
+ instance=sensitive_meta_instance, # Pass current instance via keyword
99
101
  )
100
102
 
101
103
  # Update VideoFile fields if necessary
102
104
  update_fields_video = []
103
- if created or sensitive_meta != sensitive_meta_instance: # Check if relation needs update
105
+ if created or sensitive_meta != sensitive_meta_instance: # Check if relation needs update
104
106
  video.sensitive_meta = sensitive_meta
105
- update_fields_video.append('sensitive_meta')
107
+ update_fields_video.append("sensitive_meta")
106
108
 
107
- if not video.date and sensitive_meta and extracted_data_dict.get('date'):
108
- extracted_date = extracted_data_dict.get('date')
109
- if extracted_date: # Ensure date is not None or empty
109
+ if not video.date and sensitive_meta and extracted_data_dict.get("date"):
110
+ extracted_date = extracted_data_dict.get("date")
111
+ if extracted_date: # Ensure date is not None or empty
110
112
  video.date = extracted_date
111
113
  update_fields_video.append("date")
114
+
112
115
 
113
116
  # Save VideoFile if fields changed
114
117
  if update_fields_video:
@@ -117,14 +120,14 @@ def _update_text_metadata(
117
120
  # Update state
118
121
  if not state.text_meta_extracted:
119
122
  state.text_meta_extracted = True
120
- state.save(update_fields=['text_meta_extracted'])
123
+ state.save(update_fields=["text_meta_extracted"])
121
124
 
122
125
  # Mark sensitive meta as processed when updated via text metadata
123
126
  if sensitive_meta:
124
127
  state.mark_sensitive_meta_processed(save=True)
125
128
  logger.info(f"Marked sensitive_meta_processed=True for video {video.uuid} after text metadata update")
126
129
 
127
- logger.info("Successfully updated/created SensitiveMeta and state for video %s.", video.uuid) # Changed to info
130
+ logger.info("Successfully updated/created SensitiveMeta and state for video %s.", video.uuid) # Changed to info
128
131
  return sensitive_meta
129
132
 
130
133
  except Exception as e:
@@ -6,31 +6,36 @@ if TYPE_CHECKING:
6
6
 
7
7
  logger = logging.getLogger(__name__)
8
8
 
9
+
9
10
  def _update_video_meta(video: "VideoFile", save_instance: bool = True):
10
11
  """
11
12
  Updates or creates the technical VideoMeta from the raw video file.
12
13
  Raises FileNotFoundError or ValueError on pre-condition failure, RuntimeError on processing failure.
13
14
  """
14
- from ....metadata import VideoMeta # Local import
15
+ from ....metadata import VideoMeta # Local import
15
16
 
16
17
  logger.debug("Updating technical VideoMeta for video %s (from raw file).", video.uuid)
17
18
 
18
19
  if not video.has_raw:
19
20
  # DEFENSIVE: Log warning and skip instead of crashing
20
- logger.warning(f"Raw video file path not available for {video.uuid}. Skipping VideoMeta update - this may indicate the video was processed and raw file moved.")
21
+ logger.warning(
22
+ f"Raw video file path not available for {video.uuid}. Skipping VideoMeta update - this may indicate the video was processed and raw file moved."
23
+ )
21
24
  return # Graceful skip instead of FileNotFoundError
22
25
 
23
- raw_video_path = video.get_raw_file_path() # Use helper
26
+ raw_video_path = video.get_raw_file_path() # Use helper
24
27
  if not raw_video_path or not raw_video_path.exists():
25
28
  # DEFENSIVE: Log warning and skip instead of crashing production pipeline
26
- logger.warning(f"Raw video file path {raw_video_path} does not exist for video {video.uuid}. Skipping VideoMeta update - this typically happens after video processing when raw files are moved to processed location.")
29
+ logger.warning(
30
+ f"Raw video file path {raw_video_path} does not exist for video {video.uuid}. Skipping VideoMeta update - this typically happens after video processing when raw files are moved to processed location."
31
+ )
27
32
  return # Graceful skip instead of FileNotFoundError that crashes production
28
33
 
29
34
  try:
30
35
  vm = video.video_meta
31
36
  if vm:
32
37
  logger.info("Updating existing VideoMeta (PK: %s) for video %s.", vm.pk, video.uuid)
33
- vm.update_meta(raw_video_path) # Assuming this method exists and raises on error
38
+ vm.update_meta(raw_video_path) # Assuming this method exists and raises on error
34
39
  vm.save()
35
40
  else:
36
41
  if not video.center or not video.processor:
@@ -43,9 +48,11 @@ def _update_video_meta(video: "VideoFile", save_instance: bool = True):
43
48
  video_path=raw_video_path,
44
49
  center=video.center,
45
50
  processor=video.processor,
46
- save_instance=True # Let create_from_file handle saving
51
+ save_instance=True, # Let create_from_file handle saving
47
52
  )
48
- logger.info("Created and linked VideoMeta (PK: %s) for video %s.", video.video_meta.pk, video.uuid)
53
+ vm = video.video_meta
54
+ assert vm is not None # For type checker
55
+ logger.info("Created and linked VideoMeta (PK: %s) for video %s.", vm.pk, video.uuid)
49
56
 
50
57
  # Save the VideoFile instance itself if requested and if video_meta was linked/updated
51
58
  if save_instance:
@@ -1,18 +1,21 @@
1
1
  import logging
2
- from typing import TYPE_CHECKING, List, Dict, Tuple, Set
3
- from icecream import ic
4
2
  from pathlib import Path
3
+ from typing import TYPE_CHECKING, Dict, List, Set, Tuple
4
+
5
5
  from django.db.models import Q # Import Q for complex queries
6
+ from icecream import ic
6
7
 
7
8
  if TYPE_CHECKING:
8
- from .video_file import VideoFile
9
+ from django.db.models import QuerySet
10
+
9
11
  from ...label import LabelVideoSegment
10
12
  from ...metadata import VideoPredictionMeta
11
13
  from ..frame import Frame
12
- from django.db.models import QuerySet
14
+ from .video_file import VideoFile
13
15
 
14
16
  logger = logging.getLogger(__name__)
15
17
 
18
+
16
19
  def _convert_sequences_to_db_segments(
17
20
  video: "VideoFile",
18
21
  sequences: Dict[str, List[Tuple[int, int]]],
@@ -72,14 +75,14 @@ def _convert_sequences_to_db_segments(
72
75
  logger.error("Error bulk creating segments for label '%s': %s", label_name, e, exc_info=True)
73
76
  error_count += len(segments_to_create)
74
77
 
75
- newly_created_segments = LabelVideoSegment.objects.filter(
76
- video_file=video,
77
- prediction_meta=video_prediction_meta,
78
- label__name__in=processed_labels
79
- )
78
+ newly_created_segments = LabelVideoSegment.objects.filter(video_file=video, prediction_meta=video_prediction_meta, label__name__in=processed_labels)
80
79
 
81
- logger.info("Attempting to create state objects for %d potentially new segments (Video: %s, PredictionMeta: %s)",
82
- newly_created_segments.count(), video.uuid, video_prediction_meta.pk)
80
+ logger.info(
81
+ "Attempting to create state objects for %d potentially new segments (Video: %s, PredictionMeta: %s)",
82
+ newly_created_segments.count(),
83
+ video.uuid,
84
+ video_prediction_meta.pk,
85
+ )
83
86
 
84
87
  for segment in newly_created_segments:
85
88
  try:
@@ -92,7 +95,12 @@ def _convert_sequences_to_db_segments(
92
95
 
93
96
  logger.info(
94
97
  "LabelVideoSegment conversion finished for video %s. Segments Created: %d, Skipped: %d, Errors: %d. States Created: %d, State Errors: %d",
95
- video.uuid, created_count, skipped_count, error_count, state_created_count, state_error_count
98
+ video.uuid,
99
+ created_count,
100
+ skipped_count,
101
+ error_count,
102
+ state_created_count,
103
+ state_error_count,
96
104
  )
97
105
 
98
106
 
@@ -160,15 +168,14 @@ def _get_outside_frames(video: "VideoFile", outside_label_name: str = "outside")
160
168
 
161
169
  q_objects: Q | None = None
162
170
  for segment in outside_segments:
163
- clause = Q(frame_number__gte=segment.start_frame_number,
164
- frame_number__lt=segment.end_frame_number)
171
+ clause = Q(frame_number__gte=segment.start_frame_number, frame_number__lt=segment.end_frame_number)
165
172
  q_objects = clause if q_objects is None else q_objects | clause
166
173
 
167
174
  if q_objects is None:
168
175
  return Frame.objects.none()
169
176
 
170
177
  try:
171
- return video.frames.filter(q_objects).distinct().order_by('frame_number')
178
+ return video.frames.filter(q_objects).distinct().order_by("frame_number")
172
179
  except Exception as e:
173
180
  logger.error("Error filtering outside frames for video %s: %s", video.uuid, e, exc_info=True)
174
181
  return Frame.objects.none()
@@ -177,11 +184,12 @@ def _get_outside_frames(video: "VideoFile", outside_label_name: str = "outside")
177
184
  def _get_outside_frame_paths(video: "VideoFile", outside_label_name: str = "outside") -> List["Path"]:
178
185
  """Gets the file paths of frames that fall within 'outside' segments."""
179
186
  from pathlib import Path # Local import
187
+
180
188
  frames = _get_outside_frames(video, outside_label_name=outside_label_name)
181
189
  frame_paths = []
182
190
  for frame in frames:
183
191
  try:
184
- frame_paths.append(Path(frame.image.path))
192
+ frame_paths.append(Path(frame.relative_path))
185
193
  except Exception as e:
186
194
  logger.warning("Could not get path for frame %s (Number: %d): %s", frame.pk, frame.frame_number, e)
187
195
  ic(f"Could not get path for frame {frame.pk}: {e}")
@@ -206,4 +214,3 @@ def _label_segments_to_frame_annotations(video: "VideoFile"):
206
214
  logger.info("Processed %d segments for frame annotations for video %s", processed_count, video.uuid)
207
215
  except AttributeError:
208
216
  logger.error("Could not generate frame annotations for video %s. 'label_video_segments' related manager not found.", video.uuid)
209
-
@@ -4,59 +4,43 @@ Video Metadata Model
4
4
  Stores analysis results for videos (sensitive frames, detection statistics).
5
5
  Created as part of Phase 1.1: Video Correction API Endpoints.
6
6
  """
7
+
7
8
  from django.db import models
9
+
8
10
  from .video_file import VideoFile
9
11
 
12
+
10
13
  class VideoMetadata(models.Model):
11
14
  """
12
15
  Stores analysis results for videos after sensitive frame detection.
13
-
16
+
14
17
  This model holds the output of frame analysis operations (MiniCPM, OCR+LLM)
15
18
  and provides metrics for the correction UI.
16
19
  """
17
- video = models.OneToOneField(
18
- VideoFile,
19
- on_delete=models.CASCADE,
20
- related_name='metadata',
21
- help_text="Video file this metadata belongs to"
22
- )
23
-
20
+
21
+ video = models.OneToOneField(VideoFile, on_delete=models.CASCADE, related_name="metadata", help_text="Video file this metadata belongs to")
22
+
24
23
  # Analysis Results
25
- sensitive_frame_count = models.IntegerField(
26
- null=True,
27
- blank=True,
28
- help_text="Number of frames detected as containing sensitive information"
29
- )
30
- sensitive_ratio = models.FloatField(
31
- null=True,
32
- blank=True,
33
- help_text="Ratio of sensitive frames to total frames (0.0-1.0)"
34
- )
35
- sensitive_frame_ids = models.TextField(
36
- null=True,
37
- blank=True,
38
- help_text="JSON array of sensitive frame indices (0-based)"
39
- )
40
-
24
+ sensitive_frame_count = models.IntegerField(null=True, blank=True, help_text="Number of frames detected as containing sensitive information")
25
+ sensitive_ratio = models.FloatField(null=True, blank=True, help_text="Ratio of sensitive frames to total frames (0.0-1.0)")
26
+ sensitive_frame_ids = models.TextField(null=True, blank=True, help_text="JSON array of sensitive frame indices (0-based)")
27
+
41
28
  # Metadata
42
- analyzed_at = models.DateTimeField(
43
- auto_now=True,
44
- help_text="Timestamp of last analysis"
45
- )
46
-
29
+ analyzed_at = models.DateTimeField(auto_now=True, help_text="Timestamp of last analysis")
30
+
47
31
  class Meta:
48
- db_table = 'video_metadata'
49
- verbose_name = 'Video Metadata'
50
- verbose_name_plural = 'Video Metadata'
51
-
32
+ db_table = "video_metadata"
33
+ verbose_name = "Video Metadata"
34
+ verbose_name_plural = "Video Metadata"
35
+
52
36
  def __str__(self):
53
37
  return f"Metadata for {self.video.uuid} ({self.sensitive_frame_count or 0} sensitive frames)"
54
-
38
+
55
39
  @property
56
40
  def has_analysis(self) -> bool:
57
41
  """Check if this video has been analyzed."""
58
42
  return self.sensitive_frame_count is not None
59
-
43
+
60
44
  @property
61
45
  def sensitive_percentage(self) -> float:
62
46
  """Get sensitivity as percentage (0-100)."""