endoreg-db 0.8.4.4__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 (372) 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_ai_model_data.py +2 -1
  117. endoreg_db/management/commands/load_center_data.py +12 -12
  118. endoreg_db/management/commands/load_requirement_data.py +60 -31
  119. endoreg_db/management/commands/load_requirement_set_tags.py +95 -0
  120. endoreg_db/management/commands/setup_endoreg_db.py +14 -10
  121. endoreg_db/management/commands/storage_management.py +271 -203
  122. endoreg_db/migrations/0001_initial.py +1799 -1300
  123. endoreg_db/migrations/0002_requirementset_depends_on.py +18 -0
  124. endoreg_db/migrations/_old/0001_initial.py +1857 -0
  125. endoreg_db/migrations/_old/0004_employee_city_employee_post_code_employee_street_and_more.py +68 -0
  126. endoreg_db/migrations/_old/0004_remove_casetemplate_rules_and_more.py +77 -0
  127. endoreg_db/migrations/_old/0005_merge_20251111_1003.py +14 -0
  128. endoreg_db/migrations/_old/0006_sensitivemeta_anonymized_text_and_more.py +68 -0
  129. endoreg_db/migrations/_old/0007_remove_rule_attribute_dtype_remove_rule_rule_type_and_more.py +89 -0
  130. endoreg_db/migrations/_old/0008_remove_event_event_classification_and_more.py +27 -0
  131. endoreg_db/migrations/_old/0009_alter_modelmeta_options_and_more.py +21 -0
  132. endoreg_db/models/__init__.py +78 -123
  133. endoreg_db/models/administration/__init__.py +21 -42
  134. endoreg_db/models/administration/ai/active_model.py +2 -2
  135. endoreg_db/models/administration/ai/ai_model.py +7 -6
  136. endoreg_db/models/administration/case/__init__.py +1 -15
  137. endoreg_db/models/administration/case/case.py +3 -3
  138. endoreg_db/models/administration/case/case_template/__init__.py +2 -14
  139. endoreg_db/models/administration/case/case_template/case_template.py +2 -124
  140. endoreg_db/models/administration/case/case_template/case_template_rule.py +2 -268
  141. endoreg_db/models/administration/case/case_template/case_template_rule_value.py +2 -85
  142. endoreg_db/models/administration/case/case_template/case_template_type.py +2 -25
  143. endoreg_db/models/administration/center/center.py +33 -19
  144. endoreg_db/models/administration/center/center_product.py +12 -9
  145. endoreg_db/models/administration/center/center_resource.py +25 -19
  146. endoreg_db/models/administration/center/center_shift.py +21 -17
  147. endoreg_db/models/administration/center/center_waste.py +16 -8
  148. endoreg_db/models/administration/person/__init__.py +2 -0
  149. endoreg_db/models/administration/person/employee/employee.py +10 -5
  150. endoreg_db/models/administration/person/employee/employee_qualification.py +9 -4
  151. endoreg_db/models/administration/person/employee/employee_type.py +12 -6
  152. endoreg_db/models/administration/person/examiner/examiner.py +13 -11
  153. endoreg_db/models/administration/person/patient/__init__.py +2 -0
  154. endoreg_db/models/administration/person/patient/patient.py +103 -100
  155. endoreg_db/models/administration/person/patient/patient_external_id.py +37 -0
  156. endoreg_db/models/administration/person/person.py +4 -0
  157. endoreg_db/models/administration/person/profession/__init__.py +8 -4
  158. endoreg_db/models/administration/person/user/portal_user_information.py +11 -7
  159. endoreg_db/models/administration/product/product.py +20 -15
  160. endoreg_db/models/administration/product/product_material.py +17 -18
  161. endoreg_db/models/administration/product/product_weight.py +12 -8
  162. endoreg_db/models/administration/product/reference_product.py +23 -55
  163. endoreg_db/models/administration/qualification/qualification.py +7 -3
  164. endoreg_db/models/administration/qualification/qualification_type.py +7 -3
  165. endoreg_db/models/administration/shift/scheduled_days.py +8 -5
  166. endoreg_db/models/administration/shift/shift.py +16 -12
  167. endoreg_db/models/administration/shift/shift_type.py +23 -31
  168. endoreg_db/models/label/__init__.py +7 -8
  169. endoreg_db/models/label/annotation/image_classification.py +10 -9
  170. endoreg_db/models/label/annotation/video_segmentation_annotation.py +8 -5
  171. endoreg_db/models/label/label.py +15 -15
  172. endoreg_db/models/label/label_set.py +19 -6
  173. endoreg_db/models/label/label_type.py +1 -1
  174. endoreg_db/models/label/label_video_segment/_create_from_video.py +5 -8
  175. endoreg_db/models/label/label_video_segment/label_video_segment.py +76 -102
  176. endoreg_db/models/label/video_segmentation_label.py +4 -0
  177. endoreg_db/models/label/video_segmentation_labelset.py +4 -3
  178. endoreg_db/models/media/frame/frame.py +22 -22
  179. endoreg_db/models/media/pdf/raw_pdf.py +249 -177
  180. endoreg_db/models/media/pdf/report_file.py +25 -29
  181. endoreg_db/models/media/pdf/report_reader/report_reader_config.py +30 -46
  182. endoreg_db/models/media/pdf/report_reader/report_reader_flag.py +23 -7
  183. endoreg_db/models/media/video/__init__.py +1 -0
  184. endoreg_db/models/media/video/create_from_file.py +48 -56
  185. endoreg_db/models/media/video/pipe_1.py +30 -33
  186. endoreg_db/models/media/video/pipe_2.py +8 -9
  187. endoreg_db/models/media/video/video_file.py +359 -204
  188. endoreg_db/models/media/video/video_file_ai.py +288 -74
  189. endoreg_db/models/media/video/video_file_anonymize.py +38 -38
  190. endoreg_db/models/media/video/video_file_frames/__init__.py +3 -1
  191. endoreg_db/models/media/video/video_file_frames/_bulk_create_frames.py +6 -8
  192. endoreg_db/models/media/video/video_file_frames/_create_frame_object.py +7 -9
  193. endoreg_db/models/media/video/video_file_frames/_delete_frames.py +9 -8
  194. endoreg_db/models/media/video/video_file_frames/_extract_frames.py +38 -45
  195. endoreg_db/models/media/video/video_file_frames/_get_frame.py +6 -8
  196. endoreg_db/models/media/video/video_file_frames/_get_frame_number.py +4 -18
  197. endoreg_db/models/media/video/video_file_frames/_get_frame_path.py +4 -3
  198. endoreg_db/models/media/video/video_file_frames/_get_frame_paths.py +7 -6
  199. endoreg_db/models/media/video/video_file_frames/_get_frame_range.py +6 -8
  200. endoreg_db/models/media/video/video_file_frames/_get_frames.py +6 -8
  201. endoreg_db/models/media/video/video_file_frames/_initialize_frames.py +15 -25
  202. endoreg_db/models/media/video/video_file_frames/_manage_frame_range.py +26 -23
  203. endoreg_db/models/media/video/video_file_frames/_mark_frames_extracted_status.py +23 -14
  204. endoreg_db/models/media/video/video_file_io.py +109 -62
  205. endoreg_db/models/media/video/video_file_meta/get_crop_template.py +3 -3
  206. endoreg_db/models/media/video/video_file_meta/get_endo_roi.py +5 -3
  207. endoreg_db/models/media/video/video_file_meta/get_fps.py +37 -34
  208. endoreg_db/models/media/video/video_file_meta/initialize_video_specs.py +19 -25
  209. endoreg_db/models/media/video/video_file_meta/text_meta.py +41 -38
  210. endoreg_db/models/media/video/video_file_meta/video_meta.py +14 -7
  211. endoreg_db/models/media/video/video_file_segments.py +24 -17
  212. endoreg_db/models/media/video/video_metadata.py +19 -35
  213. endoreg_db/models/media/video/video_processing.py +96 -95
  214. endoreg_db/models/medical/contraindication/__init__.py +13 -3
  215. endoreg_db/models/medical/disease.py +22 -16
  216. endoreg_db/models/medical/event.py +31 -18
  217. endoreg_db/models/medical/examination/__init__.py +13 -6
  218. endoreg_db/models/medical/examination/examination.py +17 -18
  219. endoreg_db/models/medical/examination/examination_indication.py +26 -25
  220. endoreg_db/models/medical/examination/examination_time.py +16 -6
  221. endoreg_db/models/medical/examination/examination_time_type.py +9 -6
  222. endoreg_db/models/medical/examination/examination_type.py +3 -4
  223. endoreg_db/models/medical/finding/finding.py +38 -39
  224. endoreg_db/models/medical/finding/finding_classification.py +37 -48
  225. endoreg_db/models/medical/finding/finding_intervention.py +27 -22
  226. endoreg_db/models/medical/finding/finding_type.py +13 -12
  227. endoreg_db/models/medical/hardware/endoscope.py +20 -26
  228. endoreg_db/models/medical/hardware/endoscopy_processor.py +2 -2
  229. endoreg_db/models/medical/laboratory/lab_value.py +62 -91
  230. endoreg_db/models/medical/medication/medication.py +22 -10
  231. endoreg_db/models/medical/medication/medication_indication.py +29 -3
  232. endoreg_db/models/medical/medication/medication_indication_type.py +25 -14
  233. endoreg_db/models/medical/medication/medication_intake_time.py +31 -19
  234. endoreg_db/models/medical/medication/medication_schedule.py +27 -16
  235. endoreg_db/models/medical/organ/__init__.py +15 -12
  236. endoreg_db/models/medical/patient/medication_examples.py +1 -5
  237. endoreg_db/models/medical/patient/patient_disease.py +20 -23
  238. endoreg_db/models/medical/patient/patient_event.py +19 -22
  239. endoreg_db/models/medical/patient/patient_examination.py +48 -54
  240. endoreg_db/models/medical/patient/patient_examination_indication.py +16 -14
  241. endoreg_db/models/medical/patient/patient_finding.py +122 -139
  242. endoreg_db/models/medical/patient/patient_finding_classification.py +44 -49
  243. endoreg_db/models/medical/patient/patient_finding_intervention.py +8 -19
  244. endoreg_db/models/medical/patient/patient_lab_sample.py +28 -23
  245. endoreg_db/models/medical/patient/patient_lab_value.py +82 -89
  246. endoreg_db/models/medical/patient/patient_medication.py +27 -38
  247. endoreg_db/models/medical/patient/patient_medication_schedule.py +28 -36
  248. endoreg_db/models/medical/risk/risk.py +7 -6
  249. endoreg_db/models/medical/risk/risk_type.py +8 -5
  250. endoreg_db/models/metadata/model_meta.py +60 -29
  251. endoreg_db/models/metadata/model_meta_logic.py +139 -18
  252. endoreg_db/models/metadata/pdf_meta.py +19 -24
  253. endoreg_db/models/metadata/sensitive_meta.py +102 -85
  254. endoreg_db/models/metadata/sensitive_meta_logic.py +383 -43
  255. endoreg_db/models/metadata/video_meta.py +51 -31
  256. endoreg_db/models/metadata/video_prediction_logic.py +16 -23
  257. endoreg_db/models/metadata/video_prediction_meta.py +29 -33
  258. endoreg_db/models/other/distribution/date_value_distribution.py +89 -29
  259. endoreg_db/models/other/distribution/multiple_categorical_value_distribution.py +21 -5
  260. endoreg_db/models/other/distribution/numeric_value_distribution.py +114 -53
  261. endoreg_db/models/other/distribution/single_categorical_value_distribution.py +4 -3
  262. endoreg_db/models/other/emission/emission_factor.py +18 -8
  263. endoreg_db/models/other/gender.py +10 -5
  264. endoreg_db/models/other/information_source.py +25 -25
  265. endoreg_db/models/other/material.py +9 -5
  266. endoreg_db/models/other/resource.py +6 -4
  267. endoreg_db/models/other/tag.py +10 -5
  268. endoreg_db/models/other/transport_route.py +13 -8
  269. endoreg_db/models/other/unit.py +10 -6
  270. endoreg_db/models/other/waste.py +6 -5
  271. endoreg_db/models/requirement/requirement.py +580 -272
  272. endoreg_db/models/requirement/requirement_error.py +85 -0
  273. endoreg_db/models/requirement/requirement_evaluation/evaluate_with_dependencies.py +268 -0
  274. endoreg_db/models/requirement/requirement_evaluation/operator_evaluation_models.py +3 -6
  275. endoreg_db/models/requirement/requirement_evaluation/requirement_type_parser.py +90 -64
  276. endoreg_db/models/requirement/requirement_operator.py +36 -33
  277. endoreg_db/models/requirement/requirement_set.py +74 -57
  278. endoreg_db/models/state/__init__.py +4 -4
  279. endoreg_db/models/state/abstract.py +2 -2
  280. endoreg_db/models/state/anonymization.py +12 -0
  281. endoreg_db/models/state/audit_ledger.py +46 -47
  282. endoreg_db/models/state/label_video_segment.py +9 -0
  283. endoreg_db/models/state/raw_pdf.py +40 -46
  284. endoreg_db/models/state/sensitive_meta.py +6 -2
  285. endoreg_db/models/state/video.py +58 -53
  286. endoreg_db/models/upload_job.py +32 -55
  287. endoreg_db/models/utils.py +1 -2
  288. endoreg_db/root_urls.py +21 -2
  289. endoreg_db/serializers/__init__.py +26 -57
  290. endoreg_db/serializers/anonymization.py +18 -10
  291. endoreg_db/serializers/meta/report_meta.py +1 -1
  292. endoreg_db/serializers/meta/sensitive_meta_detail.py +63 -118
  293. endoreg_db/serializers/misc/__init__.py +1 -1
  294. endoreg_db/serializers/misc/file_overview.py +33 -91
  295. endoreg_db/serializers/misc/{vop_patient_data.py → sensitive_patient_data.py} +1 -1
  296. endoreg_db/serializers/requirements/requirement_sets.py +92 -22
  297. endoreg_db/serializers/video/segmentation.py +2 -1
  298. endoreg_db/serializers/video/video_processing_history.py +20 -5
  299. endoreg_db/serializers/video_examination.py +198 -0
  300. endoreg_db/services/anonymization.py +75 -73
  301. endoreg_db/services/lookup_service.py +256 -73
  302. endoreg_db/services/lookup_store.py +174 -30
  303. endoreg_db/services/pdf_import.py +711 -310
  304. endoreg_db/services/storage_aware_video_processor.py +140 -114
  305. endoreg_db/services/video_import.py +266 -117
  306. endoreg_db/urls/__init__.py +27 -27
  307. endoreg_db/urls/label_video_segments.py +2 -0
  308. endoreg_db/urls/media.py +108 -66
  309. endoreg_db/urls/root_urls.py +29 -0
  310. endoreg_db/utils/__init__.py +15 -5
  311. endoreg_db/utils/ai/multilabel_classification_net.py +116 -20
  312. endoreg_db/utils/case_generator/__init__.py +3 -0
  313. endoreg_db/utils/dataloader.py +88 -16
  314. endoreg_db/utils/defaults/set_default_center.py +32 -0
  315. endoreg_db/utils/names.py +22 -16
  316. endoreg_db/utils/permissions.py +2 -1
  317. endoreg_db/utils/pipelines/process_video_dir.py +1 -1
  318. endoreg_db/utils/requirement_operator_logic/model_evaluators.py +414 -127
  319. endoreg_db/utils/setup_config.py +8 -5
  320. endoreg_db/utils/storage.py +115 -0
  321. endoreg_db/utils/validate_endo_roi.py +8 -2
  322. endoreg_db/utils/video/ffmpeg_wrapper.py +184 -188
  323. endoreg_db/views/__init__.py +5 -12
  324. endoreg_db/views/anonymization/media_management.py +198 -163
  325. endoreg_db/views/anonymization/overview.py +4 -1
  326. endoreg_db/views/anonymization/validate.py +174 -40
  327. endoreg_db/views/media/__init__.py +2 -0
  328. endoreg_db/views/media/pdf_media.py +131 -150
  329. endoreg_db/views/media/sensitive_metadata.py +46 -6
  330. endoreg_db/views/media/video_media.py +89 -82
  331. endoreg_db/views/media/video_segments.py +187 -260
  332. endoreg_db/views/meta/sensitive_meta_detail.py +0 -63
  333. endoreg_db/views/patient/patient.py +5 -4
  334. endoreg_db/views/pdf/__init__.py +5 -8
  335. endoreg_db/views/pdf/pdf_stream.py +186 -0
  336. endoreg_db/views/pdf/pdf_stream_views.py +0 -127
  337. endoreg_db/views/pdf/reimport.py +86 -91
  338. endoreg_db/views/requirement/evaluate.py +188 -187
  339. endoreg_db/views/requirement/lookup.py +186 -288
  340. endoreg_db/views/requirement/requirement_utils.py +89 -0
  341. endoreg_db/views/video/__init__.py +0 -4
  342. endoreg_db/views/video/correction.py +2 -2
  343. endoreg_db/views/video/video_examination_viewset.py +202 -289
  344. {endoreg_db-0.8.4.4.dist-info → endoreg_db-0.8.8.0.dist-info}/METADATA +7 -3
  345. {endoreg_db-0.8.4.4.dist-info → endoreg_db-0.8.8.0.dist-info}/RECORD +350 -255
  346. endoreg_db/models/administration/permissions/__init__.py +0 -44
  347. endoreg_db/models/media/video/refactor_plan.md +0 -0
  348. endoreg_db/models/media/video/video_file_frames.py +0 -0
  349. endoreg_db/models/metadata/frame_ocr_result.py +0 -0
  350. endoreg_db/models/rule/__init__.py +0 -13
  351. endoreg_db/models/rule/rule.py +0 -27
  352. endoreg_db/models/rule/rule_applicator.py +0 -224
  353. endoreg_db/models/rule/rule_attribute_dtype.py +0 -17
  354. endoreg_db/models/rule/rule_type.py +0 -20
  355. endoreg_db/models/rule/ruleset.py +0 -17
  356. endoreg_db/serializers/video/video_metadata.py +0 -105
  357. endoreg_db/urls/report.py +0 -48
  358. endoreg_db/urls/video.py +0 -61
  359. endoreg_db/utils/case_generator/case_generator.py +0 -159
  360. endoreg_db/utils/case_generator/utils.py +0 -30
  361. endoreg_db/views/pdf/pdf_media.py +0 -239
  362. endoreg_db/views/report/__init__.py +0 -9
  363. endoreg_db/views/report/report_list.py +0 -112
  364. endoreg_db/views/report/report_with_secure_url.py +0 -28
  365. endoreg_db/views/report/start_examination.py +0 -7
  366. endoreg_db/views/video/video_media.py +0 -158
  367. endoreg_db/views.py +0 -0
  368. /endoreg_db/data/{requirement_set → _examples/requirement_set}/endoscopy_bleeding_risk.yaml +0 -0
  369. /endoreg_db/migrations/{0002_add_video_correction_models.py → _old/0002_add_video_correction_models.py} +0 -0
  370. /endoreg_db/migrations/{0003_add_center_display_name.py → _old/0003_add_center_display_name.py} +0 -0
  371. {endoreg_db-0.8.4.4.dist-info → endoreg_db-0.8.8.0.dist-info}/WHEEL +0 -0
  372. {endoreg_db-0.8.4.4.dist-info → endoreg_db-0.8.8.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,8 @@
1
- from django.db import models
2
1
  from typing import TYPE_CHECKING
3
2
 
3
+ from django.db import models
4
+
5
+
4
6
  class LabelSetManager(models.Manager):
5
7
  """
6
8
  Manager class for handling LabelSet model operations.
@@ -10,9 +12,17 @@ class LabelSetManager(models.Manager):
10
12
 
11
13
  """
12
14
 
13
- def get_by_natural_key(self, name):
14
- """Retrieves a LabelSet instance by its natural key (name)."""
15
- return self.get(name=name)
15
+ def get_by_natural_key(self, name, version=None):
16
+ """Retrieves a LabelSet instance by its natural key (name[, version])."""
17
+
18
+ queryset = self.filter(name=name)
19
+ if version not in (None, "", -1):
20
+ queryset = queryset.filter(version=version)
21
+
22
+ labelset = queryset.order_by("-version").first()
23
+ if not labelset:
24
+ raise self.model.DoesNotExist(f"LabelSet with name='{name}' and version='{version}' not found")
25
+ return labelset
16
26
 
17
27
 
18
28
  class LabelSet(models.Model):
@@ -33,12 +43,15 @@ class LabelSet(models.Model):
33
43
  objects = LabelSetManager()
34
44
 
35
45
  if TYPE_CHECKING:
46
+ from typing import cast
47
+
36
48
  from .label import Label
37
- labels: models.QuerySet["Label"]
49
+
50
+ labels = cast(models.manager.RelatedManager["Label"], labels)
38
51
 
39
52
  def natural_key(self):
40
53
  """Return the natural key of this label set"""
41
- return (self.name,)
54
+ return (self.name, self.version)
42
55
 
43
56
  def __str__(self) -> str:
44
57
  return str(self.name)
@@ -26,4 +26,4 @@ class LabelType(models.Model):
26
26
  return (self.name,)
27
27
 
28
28
  def __str__(self):
29
- return str(self.name)
29
+ return str(self.name)
@@ -1,7 +1,9 @@
1
1
  from typing import TYPE_CHECKING, Optional
2
2
 
3
3
  if TYPE_CHECKING:
4
- from endoreg_db.models import Label, VideoPredictionMeta, VideoFile
4
+ from endoreg_db.models import Label, VideoFile, VideoPredictionMeta
5
+
6
+ __all__ = ["_create_from_video"]
5
7
 
6
8
 
7
9
  def _create_from_video(
@@ -21,15 +23,10 @@ def _create_from_video(
21
23
  raise ValueError("Source must be a VideoFile instance.")
22
24
 
23
25
  if start_frame_number < 0 or end_frame_number < 0:
24
- raise ValueError(
25
- f"Frame numbers must be non-negative: start={start_frame_number}, end={end_frame_number}"
26
- )
26
+ raise ValueError(f"Frame numbers must be non-negative: start={start_frame_number}, end={end_frame_number}")
27
27
 
28
28
  if start_frame_number > end_frame_number:
29
- raise ValueError(
30
- f"Start frame number ({start_frame_number}) must be less than or equal to "
31
- f"end frame number ({end_frame_number})"
32
- )
29
+ raise ValueError(f"Start frame number ({start_frame_number}) must be less than or equal to end frame number ({end_frame_number})")
33
30
 
34
31
  segment = cls(
35
32
  start_frame_number=start_frame_number,
@@ -1,26 +1,29 @@
1
- from django.db import models
2
- from django.db.models import Q, CheckConstraint, F
3
- from typing import TYPE_CHECKING, Union, Optional, Tuple
4
- from tqdm import tqdm
5
1
  import logging
2
+ from typing import TYPE_CHECKING, Optional, Tuple, Union, cast
3
+
6
4
  from django.core.exceptions import ObjectDoesNotExist
5
+ from django.db import models
6
+ from django.db.models import CheckConstraint, F, Q
7
+ from tqdm import tqdm
8
+
7
9
  from ._create_from_video import _create_from_video
8
10
 
9
11
  logger = logging.getLogger(__name__)
10
12
 
11
13
  if TYPE_CHECKING:
12
14
  from endoreg_db.models import (
13
- LabelVideoSegmentState,
14
- VideoFile,
15
15
  Frame,
16
- Label,
16
+ ImageClassificationAnnotation,
17
17
  InformationSource,
18
+ Label,
19
+ LabelVideoSegmentState,
18
20
  ModelMeta,
19
- VideoPredictionMeta,
20
21
  PatientFinding,
21
- ImageClassificationAnnotation,
22
+ VideoFile,
23
+ VideoPredictionMeta,
22
24
  )
23
-
25
+
26
+
24
27
  class LabelVideoSegment(models.Model):
25
28
  """
26
29
  Represents a labeled segment within a video, defined by start and end frame numbers.
@@ -28,11 +31,10 @@ class LabelVideoSegment(models.Model):
28
31
  A segment must be associated with exactly one `VideoFile`.
29
32
  If it originates from a prediction, it links to a single `VideoPredictionMeta`.
30
33
  """
34
+
31
35
  start_frame_number = models.IntegerField()
32
36
  end_frame_number = models.IntegerField()
33
- source = models.ForeignKey(
34
- "InformationSource", on_delete=models.SET_NULL, null=True
35
- )
37
+ source = models.ForeignKey("InformationSource", on_delete=models.SET_NULL, null=True)
36
38
  label = models.ForeignKey("Label", on_delete=models.SET_NULL, null=True, blank=True)
37
39
 
38
40
  # Single ForeignKey to the unified VideoFile model
@@ -61,31 +63,29 @@ class LabelVideoSegment(models.Model):
61
63
  )
62
64
 
63
65
  if TYPE_CHECKING:
64
- video_file: "VideoFile"
65
- label: Optional["Label"]
66
- source: Optional["InformationSource"]
67
- prediction_meta: Optional["VideoPredictionMeta"]
68
- patient_findings: models.QuerySet["PatientFinding"]
69
- model_meta: Optional["ModelMeta"]
70
- state:"LabelVideoSegmentState"
66
+ video_file: models.ForeignKey["VideoFile"]
67
+ label: models.ForeignKey["Label|None"]
68
+ source: models.ForeignKey["InformationSource|None"]
69
+ prediction_meta: models.ForeignKey["VideoPredictionMeta|None"]
70
+
71
+ patient_findings = cast(models.manager.RelatedManager["PatientFinding"], patient_findings)
72
+ model_meta: models.ForeignKey["ModelMeta|None"]
73
+ state: models.OneToOneField["LabelVideoSegmentState"]
71
74
 
72
75
  class Meta:
73
76
  constraints = [
74
- CheckConstraint(
75
- condition=Q(start_frame_number__lt=F("end_frame_number")),
76
- name="segment_start_lt_end"
77
- ),
77
+ CheckConstraint(check=Q(start_frame_number__lt=F("end_frame_number")), name="segment_start_lt_end"),
78
78
  ]
79
79
  indexes = [
80
- models.Index(fields=['video_file', 'label', 'start_frame_number']),
81
- models.Index(fields=['prediction_meta', 'label']),
80
+ models.Index(fields=["video_file", "label", "start_frame_number"]),
81
+ models.Index(fields=["prediction_meta", "label"]),
82
82
  ]
83
83
 
84
84
  @property
85
85
  def start_time(self) -> float:
86
86
  """
87
87
  Return the segment's start time in seconds, calculated from the start frame number and video FPS.
88
-
88
+
89
89
  Returns:
90
90
  float: Start time in seconds. Returns 0.0 if FPS is unavailable or zero.
91
91
  """
@@ -93,12 +93,12 @@ class LabelVideoSegment(models.Model):
93
93
  if fps == 0.0:
94
94
  return 0.0
95
95
  return self.start_frame_number / fps
96
-
96
+
97
97
  @property
98
98
  def end_time(self) -> float:
99
99
  """
100
100
  Return the segment's end time in seconds, calculated from the end frame number and video FPS.
101
-
101
+
102
102
  Returns:
103
103
  float: End time in seconds, or 0.0 if FPS is unavailable.
104
104
  """
@@ -138,55 +138,43 @@ class LabelVideoSegment(models.Model):
138
138
  Passes additional keyword arguments to extract_frames.
139
139
  """
140
140
  from endoreg_db.models import VideoFile
141
+
141
142
  if not isinstance(self.video_file, VideoFile):
142
143
  raise ValueError("Cannot extract frame files: No associated VideoFile.")
143
- return self.video_file.extract_specific_frame_range(
144
- start_frame=self.start_frame_number,
145
- end_frame=self.end_frame_number,
146
- overwrite=overwrite,
147
- **kwargs
148
- )
149
-
144
+ return self.video_file.extract_specific_frame_range(start_frame=self.start_frame_number, end_frame=self.end_frame_number, overwrite=overwrite, **kwargs)
145
+
150
146
  def delete_frame_files(self) -> None:
151
147
  """
152
148
  Delete the frame files corresponding to this segment's frame range from the associated video file.
153
-
149
+
154
150
  Raises:
155
151
  ValueError: If there is no associated VideoFile.
156
152
  """
157
153
  from endoreg_db.models import VideoFile
154
+
158
155
  if not isinstance(self.video_file, VideoFile):
159
156
  raise ValueError("Cannot delete frame files: No associated VideoFile.")
160
- self.video_file.delete_specific_frame_range(
161
- start_frame=self.start_frame_number,
162
- end_frame=self.end_frame_number
163
- )
157
+ self.video_file.delete_specific_frame_range(start_frame=self.start_frame_number, end_frame=self.end_frame_number)
158
+
164
159
  @classmethod
165
160
  def safe_create(cls, video_file, label, start_frame_number, end_frame_number, **kwargs):
166
161
  """
167
162
  Create a new LabelVideoSegment instance after validating the frame range.
168
-
163
+
169
164
  Validates that the provided start and end frame numbers are appropriate for the given video file before creating the segment. Raises a ValueError if validation fails.
170
-
165
+
171
166
  Returns:
172
- LabelVideoSegment: The newly created segment instance.
167
+ LabelVideoSegment: The newly created segment instance.
173
168
  """
174
169
  cls.validate_frame_range(start_frame_number, end_frame_number, video_file=video_file)
175
- return cls.objects.create(
176
- video_file=video_file,
177
- label=label,
178
- start_frame_number=start_frame_number,
179
- end_frame_number=end_frame_number,
180
- **kwargs
181
- )
182
-
170
+ return cls.objects.create(video_file=video_file, label=label, start_frame_number=start_frame_number, end_frame_number=end_frame_number, **kwargs)
171
+
183
172
  def save(self, *args, **kwargs):
184
173
  """
185
174
  Saves the LabelVideoSegment instance and ensures its associated state object exists.
186
-
175
+
187
176
  Overrides the default save behavior to guarantee that a related LabelVideoSegmentState is created or retrieved after saving.
188
177
  """
189
- from endoreg_db.models import LabelVideoSegmentState
190
178
  # Call the original save method first
191
179
  super().save(*args, **kwargs)
192
180
 
@@ -206,6 +194,7 @@ class LabelVideoSegment(models.Model):
206
194
  if it was created.
207
195
  """
208
196
  from endoreg_db.models import LabelVideoSegmentState
197
+
209
198
  state, created = LabelVideoSegmentState.objects.get_or_create(origin=self)
210
199
  return state, created
211
200
 
@@ -221,14 +210,7 @@ class LabelVideoSegment(models.Model):
221
210
  """
222
211
  Create a LabelVideoSegment instance from a VideoFile.
223
212
  """
224
- return _create_from_video(
225
- cls,
226
- source,
227
- prediction_meta,
228
- label,
229
- start_frame_number,
230
- end_frame_number
231
- )
213
+ return _create_from_video(cls, source, prediction_meta, label, start_frame_number, end_frame_number)
232
214
 
233
215
  def get_video(self) -> "VideoFile":
234
216
  """Returns the associated VideoFile instance."""
@@ -249,10 +231,7 @@ class LabelVideoSegment(models.Model):
249
231
  active_path = video_obj.active_file_path
250
232
  video_identifier = active_path.name if active_path else f"UUID {video_obj.uuid}"
251
233
 
252
- str_repr = (
253
- f"{video_identifier} Label - {label_name} - "
254
- f"{self.start_frame_number} - {self.end_frame_number}"
255
- )
234
+ str_repr = f"{video_identifier} Label - {label_name} - {self.start_frame_number} - {self.end_frame_number}"
256
235
  except ObjectDoesNotExist: # More specific exception
257
236
  str_repr = f"Segment {self.pk} (Error: Associated VideoFile missing)"
258
237
  except ValueError as e: # Catch specific error from get_video
@@ -266,7 +245,7 @@ class LabelVideoSegment(models.Model):
266
245
  def get_model_meta(self) -> Optional["ModelMeta"]:
267
246
  """
268
247
  Retrieve the associated ModelMeta object from the segment's prediction metadata, if available.
269
-
248
+
270
249
  Returns:
271
250
  ModelMeta or None: The related ModelMeta instance, or None if no prediction metadata is set.
272
251
  """
@@ -278,26 +257,24 @@ class LabelVideoSegment(models.Model):
278
257
  def frames(self) -> Union[models.QuerySet["Frame"], list]:
279
258
  """
280
259
  Return all frames within the segment's frame range.
281
-
260
+
282
261
  Returns:
283
262
  QuerySet[Frame] or list: Frames from the associated video file that fall within the segment's start and end frame numbers. Returns an empty list if the video file is unavailable.
284
263
  """
285
264
  return self.get_frames()
286
265
 
287
- def get_frames(self) -> Union[models.QuerySet["Frame"], list]:
266
+ def get_frames(self) -> models.QuerySet["Frame"]:
288
267
  """
289
268
  Retrieve all frames within the segment's frame range from the associated video.
290
-
269
+
291
270
  Returns:
292
271
  QuerySet[Frame]: Frames with frame numbers in [start_frame_number, end_frame_number) ordered by frame number, or an empty queryset if unavailable.
293
272
  """
294
273
  from endoreg_db.models.media.frame import Frame
274
+
295
275
  try:
296
276
  video_obj = self.get_video()
297
- return video_obj.frames.filter(
298
- frame_number__gte=self.start_frame_number,
299
- frame_number__lt=self.end_frame_number
300
- ).order_by('frame_number')
277
+ return video_obj.frames.filter(frame_number__gte=self.start_frame_number, frame_number__lt=self.end_frame_number).order_by("frame_number")
301
278
  except ValueError:
302
279
  logger.error("Cannot get frames for segment %s: No associated VideoFile.", self.pk)
303
280
  return Frame.objects.none()
@@ -309,7 +286,7 @@ class LabelVideoSegment(models.Model):
309
286
  def all_frame_annotations(self) -> models.QuerySet["ImageClassificationAnnotation"]:
310
287
  """
311
288
  Return all image classification annotations for frames within this segment that match the segment's label.
312
-
289
+
313
290
  Returns:
314
291
  QuerySet: ImageClassificationAnnotation objects for frames in the segment with the segment's label. Returns an empty queryset if the segment is not associated with a video.
315
292
  """
@@ -321,7 +298,7 @@ class LabelVideoSegment(models.Model):
321
298
  frame__video=video_obj, # Changed frame__video_file to frame__video
322
299
  frame__frame_number__gte=self.start_frame_number,
323
300
  frame__frame_number__lt=self.end_frame_number,
324
- label=self.label
301
+ label=self.label,
325
302
  )
326
303
  except ValueError:
327
304
  logger.error("Cannot get annotations for segment %s: No associated VideoFile.", self.pk)
@@ -331,7 +308,7 @@ class LabelVideoSegment(models.Model):
331
308
  def frame_predictions(self) -> models.QuerySet["ImageClassificationAnnotation"]:
332
309
  """
333
310
  Return prediction annotations for frames within this segment and matching the segment's label.
334
-
311
+
335
312
  Returns:
336
313
  QuerySet: ImageClassificationAnnotation objects for frames in the segment, filtered by label and information source type "prediction".
337
314
  """
@@ -344,17 +321,17 @@ class LabelVideoSegment(models.Model):
344
321
  frame__frame_number__gte=self.start_frame_number,
345
322
  frame__frame_number__lt=self.end_frame_number,
346
323
  label=self.label,
347
- information_source__information_source_types__name="prediction"
324
+ information_source__information_source_types__name="prediction",
348
325
  )
349
326
  except ValueError:
350
327
  logger.error("Cannot get predictions for segment %s: No associated VideoFile.", self.pk)
351
328
  return ImageClassificationAnnotation.objects.none()
352
-
329
+
353
330
  @property
354
331
  def manual_frame_annotations(self) -> models.QuerySet["ImageClassificationAnnotation"]:
355
332
  """
356
333
  Return manual image classification annotations for frames within this segment and matching the segment's label.
357
-
334
+
358
335
  Returns:
359
336
  QuerySet: Manual `ImageClassificationAnnotation` objects for the segment's frames and label. Returns an empty queryset if the segment is not associated with a video.
360
337
  """
@@ -367,7 +344,7 @@ class LabelVideoSegment(models.Model):
367
344
  frame__frame_number__gte=self.start_frame_number,
368
345
  frame__frame_number__lt=self.end_frame_number,
369
346
  label=self.label,
370
- information_source__information_source_types__name="manual_annotation"
347
+ information_source__information_source_types__name="manual_annotation",
371
348
  )
372
349
  except ValueError:
373
350
  logger.error("Cannot get manual annotations for segment %s: No associated VideoFile.", self.pk)
@@ -376,7 +353,7 @@ class LabelVideoSegment(models.Model):
376
353
  def get_segment_len_in_s(self) -> float:
377
354
  """
378
355
  Return the duration of the video segment in seconds, based on frame numbers and video FPS.
379
-
356
+
380
357
  Returns:
381
358
  float: Segment duration in seconds, or 0.0 if FPS is invalid or video is unavailable.
382
359
  """
@@ -406,10 +383,9 @@ class LabelVideoSegment(models.Model):
406
383
  logger.warning("Segment %s has no label. Cannot find frames without annotation.", self.pk)
407
384
  return []
408
385
 
409
- annotated_frame_ids = ImageClassificationAnnotation.objects.filter(
410
- frame__in=frames_qs.values_list('id', flat=True),
411
- label=self.label
412
- ).values_list('frame_id', flat=True)
386
+ annotated_frame_ids = ImageClassificationAnnotation.objects.filter(frame__in=frames_qs.values_list("id", flat=True), label=self.label).values_list(
387
+ "frame_id", flat=True
388
+ )
413
389
 
414
390
  frames_without_annotation = list(frames_qs.exclude(id__in=annotated_frame_ids)[:n_frames])
415
391
  return frames_without_annotation
@@ -417,11 +393,11 @@ class LabelVideoSegment(models.Model):
417
393
  def generate_annotations(self):
418
394
  """
419
395
  Creates image classification annotations for all frames in the segment if the segment is linked to a prediction, avoiding duplicates.
420
-
396
+
421
397
  Annotations are generated only if the segment has associated prediction metadata, model metadata, and label. Existing annotations for the same frame, label, model, and information source are not duplicated. Uses bulk creation for efficiency.
422
398
  """
423
399
  if not self.prediction_meta:
424
- logger.info("Skipping annotation generation for segment %s: Requires linked VideoPredictionMeta.", self.id)
400
+ logger.info("Skipping annotation generation for segment %s: Requires linked VideoPredictionMeta.", self.pk)
425
401
  return
426
402
 
427
403
  from endoreg_db.models import ImageClassificationAnnotation, InformationSource
@@ -434,27 +410,27 @@ class LabelVideoSegment(models.Model):
434
410
  label = self.label
435
411
 
436
412
  if not model_meta or not label:
437
- logger.warning("Missing model_meta or label for segment %s. Skipping annotation generation.", self.id)
413
+ logger.warning("Missing model_meta or label for segment %s. Skipping annotation generation.", self.pk)
438
414
  return
439
415
 
440
- frames_queryset = self.get_frames().only('id')
416
+ frames_queryset = self.get_frames().only("id")
441
417
  if not isinstance(frames_queryset, models.QuerySet):
442
- logger.error("Could not get frame queryset for segment %s. Skipping.", self.id)
418
+ logger.error("Could not get frame queryset for segment %s. Skipping.", self.pk)
443
419
  return
444
420
 
445
421
  existing_annotation_frame_ids = set(
446
422
  ImageClassificationAnnotation.objects.filter(
447
- frame_id__in=frames_queryset.values('id'),
423
+ frame_id__in=frames_queryset.values("id"),
448
424
  label=label,
449
425
  model_meta=model_meta,
450
426
  information_source=information_source,
451
- ).values_list('frame_id', flat=True)
427
+ ).values_list("frame_id", flat=True)
452
428
  )
453
429
 
454
430
  annotations_to_create = []
455
431
  frames_to_annotate = frames_queryset.exclude(id__in=existing_annotation_frame_ids)
456
432
 
457
- for frame in tqdm(frames_to_annotate.iterator(), total=frames_to_annotate.count(), desc=f"Preparing annotations for segment {self.id} ({label.name})"):
433
+ for frame in tqdm(frames_to_annotate.iterator(), total=frames_to_annotate.count(), desc=f"Preparing annotations for segment {self.pk} ({label.name})"):
458
434
  annotations_to_create.append(
459
435
  ImageClassificationAnnotation(
460
436
  frame=frame,
@@ -466,16 +442,16 @@ class LabelVideoSegment(models.Model):
466
442
  )
467
443
 
468
444
  if annotations_to_create:
469
- logger.info("Bulk creating %d annotations for segment %s...", len(annotations_to_create), self.id)
445
+ logger.info("Bulk creating %d annotations for segment %s...", len(annotations_to_create), self.pk)
470
446
  ImageClassificationAnnotation.objects.bulk_create(annotations_to_create, ignore_conflicts=True)
471
447
  logger.info("Bulk creation complete.")
472
448
  else:
473
- logger.info("No new annotations needed for segment %s.", self.id)
449
+ logger.info("No new annotations needed for segment %s.", self.pk)
474
450
 
475
451
  def _get_fps_safe(self):
476
452
  """
477
453
  Safely retrieves the frames per second (FPS) value from the associated video.
478
-
454
+
479
455
  Returns:
480
456
  float: The FPS of the associated video, or 0.0 if unavailable or invalid.
481
457
  """
@@ -488,12 +464,12 @@ class LabelVideoSegment(models.Model):
488
464
  def validate_frame_range(start_frame_number: int, end_frame_number: int, video_file=None):
489
465
  """
490
466
  Validate that the provided frame numbers define a valid segment range, optionally checking against a video's frame count.
491
-
467
+
492
468
  Parameters:
493
469
  start_frame_number (int): The starting frame number of the segment.
494
470
  end_frame_number (int): The ending frame number of the segment.
495
471
  video_file: Optional video file object to validate frame numbers against its frame count.
496
-
472
+
497
473
  Raises:
498
474
  ValueError: If frame numbers are not integers, are negative, are out of order, or exceed the video's frame count.
499
475
  """
@@ -504,8 +480,6 @@ class LabelVideoSegment(models.Model):
504
480
  if end_frame_number < start_frame_number:
505
481
  raise ValueError("end_frame_number must be equal or greater than start_frame_number.")
506
482
  if video_file is not None:
507
- frame_count = getattr(video_file, 'frame_count', None)
483
+ frame_count = getattr(video_file, "frame_count", None)
508
484
  if frame_count is not None and end_frame_number > frame_count:
509
485
  raise ValueError(f"end_frame_number ({end_frame_number}) exceeds video frame count ({frame_count}).")
510
-
511
-
@@ -1,12 +1,15 @@
1
1
  from django.db import models
2
2
 
3
+
3
4
  class VideoSegmentationLabelManager(models.Manager):
4
5
  """
5
6
  Manager for VideoSegmentationLabel with custom query methods.
6
7
  """
8
+
7
9
  def get_by_natural_key(self, name: str) -> "VideoSegmentationLabel":
8
10
  return self.get(name=name)
9
11
 
12
+
10
13
  class VideoSegmentationLabel(models.Model):
11
14
  """
12
15
  Represents a label for video segmentation annotations.
@@ -17,6 +20,7 @@ class VideoSegmentationLabel(models.Model):
17
20
  color (str): The color associated with the label.
18
21
  order_priority (int): The priority for ordering labels.
19
22
  """
23
+
20
24
  objects = VideoSegmentationLabelManager()
21
25
 
22
26
  name = models.CharField(max_length=255)
@@ -1,10 +1,11 @@
1
- from django.db import models
1
+ from typing import TYPE_CHECKING, cast
2
2
 
3
- from typing import TYPE_CHECKING
3
+ from django.db import models
4
4
 
5
5
  if TYPE_CHECKING:
6
6
  from endoreg_db.models import VideoSegmentationLabel
7
7
 
8
+
8
9
  class VideoSegmentationLabelSetManager(models.Manager):
9
10
  def get_by_natural_key(self, name):
10
11
  return self.get(name=name)
@@ -18,7 +19,7 @@ class VideoSegmentationLabelSet(models.Model):
18
19
  objects = VideoSegmentationLabelSetManager()
19
20
 
20
21
  if TYPE_CHECKING:
21
- labels: models.QuerySet["VideoSegmentationLabel"]
22
+ labels = cast(models.manager.RelatedManager["VideoSegmentationLabel"], labels)
22
23
 
23
24
  def natural_key(self):
24
25
  return (self.name,)
@@ -1,14 +1,17 @@
1
1
  import logging
2
- from typing import TYPE_CHECKING, Optional
3
- from django.db import models
4
2
  from pathlib import Path
3
+ from typing import TYPE_CHECKING, Optional
4
+
5
5
  import cv2
6
6
  import numpy as np
7
+ from django.db import models
8
+
7
9
  if TYPE_CHECKING:
8
- from endoreg_db.models import Label, ImageClassificationAnnotation, VideoFile
10
+ from endoreg_db.models import ImageClassificationAnnotation, VideoFile
9
11
 
10
12
  logger = logging.getLogger(__name__)
11
13
 
14
+
12
15
  # Unified Frame model
13
16
  class Frame(models.Model):
14
17
  video = models.ForeignKey(
@@ -25,61 +28,58 @@ class Frame(models.Model):
25
28
 
26
29
  if TYPE_CHECKING:
27
30
  image_classification_annotations: models.QuerySet["ImageClassificationAnnotation"]
28
- labels: models.QuerySet["Label"]
29
- video: "VideoFile"
31
+ video: models.ForeignKey["VideoFile"]
32
+
30
33
  class Meta:
31
- unique_together = ('video', 'frame_number')
32
- ordering = ['video', 'frame_number']
34
+ unique_together = ("video", "frame_number")
35
+ ordering = ["video", "frame_number"]
33
36
 
34
37
  @property
35
38
  def file_path(self) -> Path:
36
39
  """
37
40
  Return the absolute filesystem path to the frame image by combining the video's frame directory with the frame's relative path.
38
-
41
+
39
42
  Returns:
40
43
  Path: The absolute path to the frame image file.
41
44
  """
42
45
  base_dir = self.video.get_frame_dir_path()
46
+ assert base_dir is not None, "Video frame directory path should not be None"
43
47
  return base_dir / self.relative_path
44
-
48
+
45
49
  @property
46
50
  def predictions(self) -> models.QuerySet["ImageClassificationAnnotation"]:
47
51
  """
48
52
  Return all image classification annotations for this frame that are linked to an information source of type "prediction".
49
-
53
+
50
54
  Returns:
51
55
  QuerySet: A queryset of related ImageClassificationAnnotation objects filtered to those whose information source type is "prediction".
52
56
  """
53
- return self.image_classification_annotations.filter(
54
- information_source__information_source_types__name="prediction"
55
- )
56
-
57
+ return self.image_classification_annotations.filter(information_source__information_source_types__name="prediction")
58
+
57
59
  @property
58
60
  def manual_annotations(self) -> models.QuerySet["ImageClassificationAnnotation"]:
59
61
  """
60
62
  Return all manual image classification annotations associated with this frame.
61
-
63
+
62
64
  Returns:
63
65
  QuerySet: A queryset of related ImageClassificationAnnotation objects whose information source type is "manual_annotation".
64
66
  """
65
- return self.image_classification_annotations.filter(
66
- information_source__information_source_types__name="manual_annotation"
67
- )
67
+ return self.image_classification_annotations.filter(information_source__information_source_types__name="manual_annotation")
68
68
 
69
69
  @property
70
70
  def has_predictions(self) -> bool:
71
71
  """
72
72
  Returns True if the frame has any associated prediction annotations.
73
-
73
+
74
74
  A prediction annotation is defined as an ImageClassificationAnnotation whose information source type is "prediction".
75
75
  """
76
76
  return self.predictions.exists()
77
-
77
+
78
78
  @property
79
79
  def has_manual_annotations(self) -> bool:
80
80
  """
81
81
  Returns True if the frame has any manual image classification annotations.
82
-
82
+
83
83
  Manual annotations are identified as related ImageClassificationAnnotation objects whose information source type is named "manual_annotation".
84
84
  """
85
85
  return self.manual_annotations.exists()
@@ -87,7 +87,7 @@ class Frame(models.Model):
87
87
  def get_image(self) -> Optional[np.ndarray]:
88
88
  """
89
89
  Load and return the frame image as a NumPy array using OpenCV.
90
-
90
+
91
91
  Returns:
92
92
  The image as a NumPy array if successfully loaded, or None if the file does not exist or cannot be read.
93
93
  """