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
@@ -8,8 +8,7 @@ Provides RESTful endpoints for video segment management:
8
8
  - Video-specific: GET/POST /api/media/videos/<pk>/segments/
9
9
  """
10
10
 
11
- from endoreg_db.models import Label, LabelVideoSegment, VideoFile
12
- from endoreg_db.serializers.label_video_segment.label_video_segment import LabelVideoSegmentSerializer
11
+ import logging
13
12
 
14
13
  from django.db import transaction
15
14
  from django.db.models import Count
@@ -18,71 +17,65 @@ from rest_framework import status
18
17
  from rest_framework.decorators import api_view, permission_classes
19
18
  from rest_framework.response import Response
20
19
 
20
+ from endoreg_db.models import Label, LabelVideoSegment, VideoFile
21
+ from endoreg_db.serializers.label_video_segment.label_video_segment import LabelVideoSegmentSerializer
21
22
  from endoreg_db.utils.permissions import EnvironmentAwarePermission
22
23
 
23
- import logging
24
24
  logger = logging.getLogger(__name__)
25
25
 
26
26
 
27
- @api_view(['GET'])
27
+ @api_view(["GET"])
28
28
  @permission_classes([EnvironmentAwarePermission])
29
29
  def video_segments_stats(request):
30
30
  """
31
31
  Statistics endpoint for video segments.
32
-
32
+
33
33
  GET /api/media/videos/segments/stats/
34
34
  Returns aggregated statistics about video segments.
35
35
  """
36
36
  try:
37
37
  # Get all segments queryset
38
38
  segments = LabelVideoSegment.objects.all()
39
-
39
+
40
40
  # Calculate statistics
41
41
  total_segments = segments.count()
42
-
43
- # Segments by status (assuming status field exists)
44
- status_counts = segments.values('status').annotate(count=Count('id'))
45
-
42
+
46
43
  # Segments by label
47
- label_counts = segments.values('label__name').annotate(count=Count('id'))
48
-
44
+ label_counts = segments.values("label__name").annotate(count=Count("id"))
45
+
49
46
  # Videos with segments
50
- videos_with_segments = segments.values('video_id').distinct().count()
51
-
47
+ videos_with_segments = segments.values("video_file").distinct().count()
48
+
52
49
  stats = {
53
- 'total_segments': total_segments,
54
- 'videos_with_segments': videos_with_segments,
55
- 'by_status': {item['status']: item['count'] for item in status_counts if item['status']},
56
- 'by_label': {item['label__name']: item['count'] for item in label_counts if item['label__name']},
50
+ "total_segments": total_segments,
51
+ "videos_with_segments": videos_with_segments,
52
+ "by_label": {item["label__name"]: item["count"] for item in label_counts if item["label__name"]},
57
53
  }
58
-
54
+
59
55
  return Response(stats, status=status.HTTP_200_OK)
60
-
56
+
61
57
  except Exception as e:
62
58
  logger.error(f"Error fetching video segment stats: {e}")
63
- return Response(
64
- {'error': 'Failed to fetch segment statistics'},
65
- status=status.HTTP_500_INTERNAL_SERVER_ERROR
66
- )
59
+ return Response({"error": "Failed to fetch segment statistics"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
67
60
 
68
61
 
69
- @api_view(['GET', 'POST'])
62
+ @api_view(["GET", "POST"])
70
63
  @permission_classes([EnvironmentAwarePermission])
71
64
  def video_segments_collection(request):
72
65
  """
73
66
  Collection endpoint for all video segments across all videos.
74
-
67
+
75
68
  GET /api/media/videos/segments/
76
69
  - Lists all segments, optionally filtered by video_id and/or label_id
77
70
  - Query params: video_id, label_id
78
-
71
+
79
72
  POST /api/media/videos/segments/
80
73
  - Creates a new video segment
81
74
  - Requires: video_id, label_id, start_frame_number, end_frame_number
82
-
75
+
83
76
  Modern replacement for: /api/video-segments/
84
77
  """
85
- if request.method == 'POST':
78
+ if request.method == "POST":
86
79
  logger.info(f"Creating new video segment with data: {request.data}")
87
80
 
88
81
  with transaction.atomic():
@@ -91,27 +84,18 @@ def video_segments_collection(request):
91
84
  try:
92
85
  segment = serializer.save()
93
86
  logger.info(f"Successfully created video segment {segment.pk}")
94
- return Response(
95
- LabelVideoSegmentSerializer(segment).data,
96
- status=status.HTTP_201_CREATED
97
- )
87
+ return Response(LabelVideoSegmentSerializer(segment).data, status=status.HTTP_201_CREATED)
98
88
  except Exception as e:
99
89
  logger.error(f"Error creating video segment: {str(e)}")
100
- return Response(
101
- {'error': f'Failed to create segment: {str(e)}'},
102
- status=status.HTTP_500_INTERNAL_SERVER_ERROR
103
- )
90
+ return Response({"error": f"Failed to create segment: {str(e)}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
104
91
  else:
105
92
  logger.warning(f"Invalid data for video segment creation: {serializer.errors}")
106
- return Response(
107
- {'error': 'Invalid data', 'details': serializer.errors},
108
- status=status.HTTP_400_BAD_REQUEST
109
- )
93
+ return Response({"error": "Invalid data", "details": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
110
94
 
111
- elif request.method == 'GET':
95
+ elif request.method == "GET":
112
96
  # Optional filtering by video_id
113
- video_id = request.GET.get('video_id')
114
- label_id = request.GET.get('label_id')
97
+ video_id = request.GET.get("video_id")
98
+ label_id = request.GET.get("label_id")
115
99
 
116
100
  queryset = LabelVideoSegment.objects.all()
117
101
 
@@ -120,140 +104,114 @@ def video_segments_collection(request):
120
104
  video = VideoFile.objects.get(id=video_id)
121
105
  queryset = queryset.filter(video_file=video)
122
106
  except VideoFile.DoesNotExist:
123
- return Response(
124
- {'error': f'Video with id {video_id} not found'},
125
- status=status.HTTP_404_NOT_FOUND
126
- )
107
+ return Response({"error": f"Video with id {video_id} not found"}, status=status.HTTP_404_NOT_FOUND)
127
108
 
128
109
  if label_id:
129
110
  try:
130
111
  label = Label.objects.get(id=label_id)
131
112
  queryset = queryset.filter(label=label)
132
113
  except Label.DoesNotExist:
133
- return Response(
134
- {'error': f'Label with id {label_id} not found'},
135
- status=status.HTTP_404_NOT_FOUND
136
- )
114
+ return Response({"error": f"Label with id {label_id} not found"}, status=status.HTTP_404_NOT_FOUND)
137
115
 
138
116
  # Order by video and start time for consistent results
139
- segments = queryset.order_by('video_file__id', 'start_frame_number')
117
+ segments = queryset.order_by("video_file__id", "start_frame_number")
140
118
  serializer = LabelVideoSegmentSerializer(segments, many=True)
141
119
  return Response(serializer.data)
142
120
 
143
121
 
144
- @api_view(['GET', 'POST'])
122
+ @api_view(["GET", "POST"])
145
123
  @permission_classes([EnvironmentAwarePermission])
146
124
  def video_segments_by_video(request, pk):
147
125
  """
148
126
  Video-specific segments endpoint.
149
-
127
+
150
128
  GET /api/media/videos/<pk>/segments/
151
129
  - Lists all segments for a specific video
152
130
  - Query params: label (label name filter)
153
131
  - Note: This was already implemented in segments.py as video_segments_by_pk
154
-
132
+
155
133
  POST /api/media/videos/<pk>/segments/
156
134
  - Creates a new segment for this video
157
135
  - Automatically sets video_id to pk
158
136
  - Requires: label_id, start_frame_number, end_frame_number
159
-
137
+
160
138
  Modern replacement for: /api/video-segments/?video_id=<pk>
161
139
  """
162
140
  # Verify video exists
163
141
  video = get_object_or_404(VideoFile, id=pk)
164
-
165
- if request.method == 'GET':
142
+
143
+ if request.method == "GET":
166
144
  # This duplicates video_segments_by_pk functionality
167
145
  # We keep both for compatibility during migration
168
- label_name = request.GET.get('label')
169
-
146
+ label_name = request.GET.get("label")
147
+
170
148
  queryset = LabelVideoSegment.objects.filter(video_file=video)
171
-
149
+
172
150
  if label_name:
173
151
  try:
174
152
  label = Label.objects.get(name=label_name)
175
153
  queryset = queryset.filter(label=label)
176
154
  except Label.DoesNotExist:
177
- return Response(
178
- {'error': f'Label "{label_name}" not found'},
179
- status=status.HTTP_404_NOT_FOUND
180
- )
181
-
182
- segments = queryset.order_by('start_frame_number')
155
+ return Response({"error": f'Label "{label_name}" not found'}, status=status.HTTP_404_NOT_FOUND)
156
+
157
+ segments = queryset.order_by("start_frame_number")
183
158
  serializer = LabelVideoSegmentSerializer(segments, many=True)
184
159
  return Response(serializer.data)
185
-
186
- elif request.method == 'POST':
160
+
161
+ elif request.method == "POST":
187
162
  logger.info(f"Creating new segment for video {pk} with data: {request.data}")
188
-
163
+
189
164
  # Automatically set video_id to pk
190
165
  data = request.data.copy()
191
- data['video_id'] = pk
192
-
166
+ data["video_id"] = pk
167
+
193
168
  with transaction.atomic():
194
169
  serializer = LabelVideoSegmentSerializer(data=data)
195
170
  if serializer.is_valid():
196
171
  try:
197
172
  segment = serializer.save()
198
173
  logger.info(f"Successfully created segment {segment.pk} for video {pk}")
199
- return Response(
200
- LabelVideoSegmentSerializer(segment).data,
201
- status=status.HTTP_201_CREATED
202
- )
174
+ return Response(LabelVideoSegmentSerializer(segment).data, status=status.HTTP_201_CREATED)
203
175
  except Exception as e:
204
176
  logger.error(f"Error creating segment for video {pk}: {str(e)}")
205
- return Response(
206
- {'error': f'Failed to create segment: {str(e)}'},
207
- status=status.HTTP_500_INTERNAL_SERVER_ERROR
208
- )
177
+ return Response({"error": f"Failed to create segment: {str(e)}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
209
178
  else:
210
179
  logger.warning(f"Invalid data for segment creation: {serializer.errors}")
211
- return Response(
212
- {'error': 'Invalid data', 'details': serializer.errors},
213
- status=status.HTTP_400_BAD_REQUEST
214
- )
180
+ return Response({"error": "Invalid data", "details": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
215
181
 
216
182
 
217
- @api_view(['GET', 'PATCH', 'DELETE'])
183
+ @api_view(["GET", "PATCH", "DELETE"])
218
184
  @permission_classes([EnvironmentAwarePermission])
219
185
  def video_segment_detail(request, pk, segment_id):
220
186
  """
221
187
  Detail endpoint for a specific video segment.
222
-
188
+
223
189
  GET /api/media/videos/<pk>/segments/<segment_id>/
224
190
  - Returns segment details
225
-
191
+
226
192
  PATCH /api/media/videos/<pk>/segments/<segment_id>/
227
193
  - Updates segment (partial update)
228
-
194
+
229
195
  DELETE /api/media/videos/<pk>/segments/<segment_id>/
230
196
  - Deletes segment
231
-
197
+
232
198
  Modern replacement for: /api/video-segments/<segment_id>/
233
199
  """
234
200
  # Verify video exists
235
201
  video = get_object_or_404(VideoFile, id=pk)
236
-
202
+
237
203
  # Get segment and verify it belongs to this video
238
- segment = get_object_or_404(
239
- LabelVideoSegment,
240
- id=segment_id,
241
- video_file=video
242
- )
243
-
244
- if request.method == 'GET':
204
+ segment = get_object_or_404(LabelVideoSegment, id=segment_id, video_file=video)
205
+
206
+ if request.method == "GET":
245
207
  serializer = LabelVideoSegmentSerializer(segment)
246
208
  return Response(serializer.data)
247
-
248
- elif request.method == 'PATCH':
209
+
210
+ elif request.method == "PATCH":
249
211
  logger.info(f"Updating segment {segment_id} for video {pk} with data: {request.data}")
250
-
212
+
251
213
  with transaction.atomic():
252
- serializer = LabelVideoSegmentSerializer(
253
- segment,
254
- data=request.data,
255
- partial=True
256
- )
214
+ serializer = LabelVideoSegmentSerializer(segment, data=request.data, partial=True)
257
215
  if serializer.is_valid():
258
216
  try:
259
217
  segment = serializer.save()
@@ -261,33 +219,21 @@ def video_segment_detail(request, pk, segment_id):
261
219
  return Response(LabelVideoSegmentSerializer(segment).data)
262
220
  except Exception as e:
263
221
  logger.error(f"Error updating segment {segment_id}: {str(e)}")
264
- return Response(
265
- {'error': f'Failed to update segment: {str(e)}'},
266
- status=status.HTTP_500_INTERNAL_SERVER_ERROR
267
- )
222
+ return Response({"error": f"Failed to update segment: {str(e)}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
268
223
  else:
269
224
  logger.warning(f"Invalid data for segment update: {serializer.errors}")
270
- return Response(
271
- {'error': 'Invalid data', 'details': serializer.errors},
272
- status=status.HTTP_400_BAD_REQUEST
273
- )
274
-
275
- elif request.method == 'DELETE':
225
+ return Response({"error": "Invalid data", "details": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
226
+
227
+ elif request.method == "DELETE":
276
228
  logger.info(f"Deleting segment {segment_id} from video {pk}")
277
229
  try:
278
230
  with transaction.atomic():
279
231
  segment.delete()
280
232
  logger.info(f"Successfully deleted segment {segment_id}")
281
- return Response(
282
- {'message': f'Segment {segment_id} deleted successfully'},
283
- status=status.HTTP_204_NO_CONTENT
284
- )
233
+ return Response({"message": f"Segment {segment_id} deleted successfully"}, status=status.HTTP_204_NO_CONTENT)
285
234
  except Exception as e:
286
235
  logger.error(f"Error deleting segment {segment_id}: {str(e)}")
287
- return Response(
288
- {'error': f'Failed to delete segment: {str(e)}'},
289
- status=status.HTTP_500_INTERNAL_SERVER_ERROR
290
- )
236
+ return Response({"error": f"Failed to delete segment: {str(e)}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
291
237
 
292
238
 
293
239
  # ============================================================================
@@ -295,23 +241,24 @@ def video_segment_detail(request, pk, segment_id):
295
241
  # Migrated from /api/label-video-segment/*/validate/ (October 14, 2025)
296
242
  # ============================================================================
297
243
 
298
- @api_view(['POST'])
244
+
245
+ @api_view(["POST"])
299
246
  @permission_classes([EnvironmentAwarePermission])
300
247
  def video_segment_validate(request, pk: int, segment_id: int):
301
248
  """
302
249
  Validate a single video segment.
303
-
250
+
304
251
  POST /api/media/videos/<pk>/segments/<segment_id>/validate/
305
-
252
+
306
253
  Validates a single LabelVideoSegment and marks it as verified.
307
254
  Used to confirm user-reviewed segment annotations.
308
-
255
+
309
256
  Request Body (optional):
310
257
  {
311
258
  "is_validated": true, // optional, default true
312
259
  "notes": "..." // optional, validation notes
313
260
  }
314
-
261
+
315
262
  Response:
316
263
  {
317
264
  "message": "Segment validated successfully",
@@ -325,69 +272,62 @@ def video_segment_validate(request, pk: int, segment_id: int):
325
272
  """
326
273
  # Verify video exists
327
274
  video = get_object_or_404(VideoFile, pk=pk)
328
-
275
+
329
276
  # Get segment and verify it belongs to this video
330
- segment = get_object_or_404(
331
- LabelVideoSegment.objects.select_related('state', 'video_file', 'label'),
332
- pk=segment_id,
333
- video_file=video
334
- )
335
-
277
+ segment = get_object_or_404(LabelVideoSegment.objects.select_related("state", "video_file", "label"), pk=segment_id, video_file=video)
278
+
336
279
  try:
337
280
  # Validation status from request (default: True)
338
- is_validated = request.data.get('is_validated', True)
339
- notes = request.data.get('notes', '')
340
-
281
+ is_validated = request.data.get("is_validated", True)
282
+ notes = request.data.get("notes", "")
283
+
341
284
  # Get or create state object
342
- if not hasattr(segment, 'state') or segment.state is None:
343
- return Response({
344
- "error": "Segment has no state object. Cannot validate."
345
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
346
-
285
+ if not hasattr(segment, "state") or segment.state is None:
286
+ return Response({"error": "Segment has no state object. Cannot validate."}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
287
+
347
288
  # Update state
348
289
  with transaction.atomic():
349
290
  segment.state.is_validated = is_validated
350
- if notes and hasattr(segment.state, 'validation_notes'):
351
- segment.state.validation_notes = notes
352
291
  segment.state.save()
353
-
292
+
354
293
  logger.info(f"Validated segment {segment_id} in video {pk}: {is_validated}")
355
-
356
- return Response({
357
- "message": f"Segment {segment_id} validation status updated",
358
- "segment_id": segment_id,
359
- "is_validated": is_validated,
360
- "label": segment.label.name if segment.label else None,
361
- "video_id": video.id,
362
- "start_frame": segment.start_frame_number,
363
- "end_frame": segment.end_frame_number
364
- }, status=status.HTTP_200_OK)
365
-
294
+
295
+ return Response(
296
+ {
297
+ "message": f"Segment {segment_id} validation status updated",
298
+ "segment_id": segment_id,
299
+ "is_validated": is_validated,
300
+ "label": segment.label.name if segment.label else None,
301
+ "video_id": video.id,
302
+ "start_frame": segment.start_frame_number,
303
+ "end_frame": segment.end_frame_number,
304
+ },
305
+ status=status.HTTP_200_OK,
306
+ )
307
+
366
308
  except Exception as e:
367
309
  logger.error(f"Error validating segment {segment_id} in video {pk}: {e}")
368
- return Response({
369
- "error": f"Validation failed: {str(e)}"
370
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
310
+ return Response({"error": f"Validation failed: {str(e)}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
371
311
 
372
312
 
373
- @api_view(['POST'])
313
+ @api_view(["POST"])
374
314
  @permission_classes([EnvironmentAwarePermission])
375
315
  def video_segments_validate_bulk(request, pk: int):
376
316
  """
377
317
  Validate multiple video segments at once.
378
-
318
+
379
319
  POST /api/media/videos/<pk>/segments/validate-bulk/
380
-
320
+
381
321
  Validates multiple LabelVideoSegments simultaneously.
382
322
  Useful for batch validation after review.
383
-
323
+
384
324
  Request Body:
385
325
  {
386
326
  "segment_ids": [1, 2, 3, ...],
387
327
  "is_validated": true, // optional, default true
388
328
  "notes": "..." // optional, applies to all segments
389
329
  }
390
-
330
+
391
331
  Response:
392
332
  {
393
333
  "message": "Bulk validation completed. 3 segments updated.",
@@ -399,37 +339,30 @@ def video_segments_validate_bulk(request, pk: int):
399
339
  """
400
340
  # Verify video exists
401
341
  video = get_object_or_404(VideoFile, pk=pk)
402
-
403
- segment_ids = request.data.get('segment_ids', [])
404
- is_validated = request.data.get('is_validated', True)
405
- notes = request.data.get('notes', '')
406
-
342
+
343
+ segment_ids = request.data.get("segment_ids", [])
344
+ is_validated = request.data.get("is_validated", True)
345
+ notes = request.data.get("notes", "")
346
+
407
347
  if not segment_ids:
408
- return Response({
409
- "error": "segment_ids is required"
410
- }, status=status.HTTP_400_BAD_REQUEST)
411
-
348
+ return Response({"error": "segment_ids is required"}, status=status.HTTP_400_BAD_REQUEST)
349
+
412
350
  try:
413
351
  # Get all segments for this video only
414
- segments = LabelVideoSegment.objects.filter(
415
- pk__in=segment_ids,
416
- video_file=video
417
- ).select_related('state')
418
-
352
+ segments = LabelVideoSegment.objects.filter(pk__in=segment_ids, video_file=video).select_related("state")
353
+
419
354
  if not segments.exists():
420
- return Response({
421
- "error": "No segments found with provided IDs for this video"
422
- }, status=status.HTTP_404_NOT_FOUND)
423
-
355
+ return Response({"error": "No segments found with provided IDs for this video"}, status=status.HTTP_404_NOT_FOUND)
356
+
424
357
  updated_count = 0
425
358
  failed_ids = []
426
-
359
+
427
360
  with transaction.atomic():
428
361
  for segment in segments:
429
362
  try:
430
363
  if segment.state:
431
364
  segment.state.is_validated = is_validated
432
- if notes and hasattr(segment.state, 'validation_notes'):
365
+ if notes and hasattr(segment.state, "validation_notes"):
433
366
  segment.state.validation_notes = notes
434
367
  segment.state.save()
435
368
  updated_count += 1
@@ -438,51 +371,49 @@ def video_segments_validate_bulk(request, pk: int):
438
371
  except Exception as e:
439
372
  logger.error(f"Error validating segment {segment.id}: {e}")
440
373
  failed_ids.append(segment.id)
441
-
374
+
442
375
  logger.info(f"Bulk validated {updated_count} segments in video {pk}")
443
-
376
+
444
377
  response_data = {
445
378
  "message": f"Bulk validation completed. {updated_count} segments updated.",
446
379
  "updated_count": updated_count,
447
380
  "requested_count": len(segment_ids),
448
381
  "is_validated": is_validated,
449
- "video_id": pk
382
+ "video_id": pk,
450
383
  }
451
-
384
+
452
385
  if failed_ids:
453
386
  response_data["failed_ids"] = failed_ids
454
387
  response_data["warning"] = f"{len(failed_ids)} segments could not be validated"
455
-
388
+
456
389
  return Response(response_data, status=status.HTTP_200_OK)
457
-
390
+
458
391
  except Exception as e:
459
392
  logger.error(f"Error in bulk validation for video {pk}: {e}")
460
- return Response({
461
- "error": f"Bulk validation failed: {str(e)}"
462
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
393
+ return Response({"error": f"Bulk validation failed: {str(e)}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
463
394
 
464
395
 
465
- @api_view(['GET', 'POST'])
396
+ @api_view(["GET", "POST"])
466
397
  @permission_classes([EnvironmentAwarePermission])
467
398
  def video_segments_validation_status(request, pk: int):
468
399
  """
469
400
  Get or update validation status for all segments of a video.
470
-
401
+
471
402
  GET /api/media/videos/<pk>/segments/validation-status/
472
403
  Returns validation statistics for all segments.
473
-
404
+
474
405
  POST /api/media/videos/<pk>/segments/validation-status/
475
406
  Marks all segments (or filtered by label) as validated.
476
-
407
+
477
408
  Query Parameters (GET):
478
409
  - label_name: filter by label (optional)
479
-
410
+
480
411
  Request Body (POST, optional):
481
412
  {
482
413
  "label_name": "...", // optional, only validate segments with this label
483
414
  "notes": "..." // optional
484
415
  }
485
-
416
+
486
417
  Response (GET):
487
418
  {
488
419
  "video_id": 123,
@@ -492,7 +423,7 @@ def video_segments_validation_status(request, pk: int):
492
423
  "validation_complete": false,
493
424
  "by_label": {...}
494
425
  }
495
-
426
+
496
427
  Response (POST):
497
428
  {
498
429
  "message": "Video segment validation completed",
@@ -504,77 +435,69 @@ def video_segments_validation_status(request, pk: int):
504
435
  """
505
436
  # Verify video exists
506
437
  video = get_object_or_404(VideoFile, pk=pk)
507
-
508
- if request.method == 'GET':
438
+
439
+ if request.method == "GET":
509
440
  # Get validation status
510
- label_name = request.query_params.get('label_name')
511
-
512
- segments_query = LabelVideoSegment.objects.filter(
513
- video_file=video
514
- ).select_related('state', 'label')
515
-
441
+ label_name = request.query_params.get("label_name")
442
+
443
+ segments_query = LabelVideoSegment.objects.filter(video_file=video).select_related("state", "label")
444
+
516
445
  if label_name:
517
446
  segments_query = segments_query.filter(label__name=label_name)
518
-
447
+
519
448
  segments = segments_query.all()
520
449
  total_count = segments.count()
521
-
450
+
522
451
  # Count validated segments
523
- validated_count = sum(
524
- 1 for s in segments
525
- if s.state and s.state.is_validated
526
- )
527
-
452
+ validated_count = sum(1 for s in segments if s.state and s.state.is_validated)
453
+
528
454
  # By label breakdown
529
455
  by_label = {}
530
456
  for segment in segments:
531
- label = segment.label.name if segment.label else 'unknown'
457
+ label = segment.label.name if segment.label else "unknown"
532
458
  if label not in by_label:
533
- by_label[label] = {'total': 0, 'validated': 0}
534
- by_label[label]['total'] += 1
459
+ by_label[label] = {"total": 0, "validated": 0}
460
+ by_label[label]["total"] += 1
535
461
  if segment.state and segment.state.is_validated:
536
- by_label[label]['validated'] += 1
537
-
538
- return Response({
539
- "video_id": pk,
540
- "total_segments": total_count,
541
- "validated_count": validated_count,
542
- "unvalidated_count": total_count - validated_count,
543
- "validation_complete": validated_count == total_count and total_count > 0,
544
- "by_label": by_label,
545
- "label_filter": label_name
546
- }, status=status.HTTP_200_OK)
547
-
548
- elif request.method == 'POST':
462
+ by_label[label]["validated"] += 1
463
+
464
+ return Response(
465
+ {
466
+ "video_id": pk,
467
+ "total_segments": total_count,
468
+ "validated_count": validated_count,
469
+ "unvalidated_count": total_count - validated_count,
470
+ "validation_complete": validated_count == total_count and total_count > 0,
471
+ "by_label": by_label,
472
+ "label_filter": label_name,
473
+ },
474
+ status=status.HTTP_200_OK,
475
+ )
476
+
477
+ elif request.method == "POST":
549
478
  # Mark all segments as validated
550
- label_name = request.data.get('label_name')
551
- notes = request.data.get('notes', '')
552
-
553
- segments_query = LabelVideoSegment.objects.filter(
554
- video_file=video
555
- ).select_related('state', 'label')
556
-
479
+ label_name = request.data.get("label_name")
480
+ notes = request.data.get("notes", "")
481
+
482
+ segments_query = LabelVideoSegment.objects.filter(video_file=video).select_related("state", "label")
483
+
557
484
  if label_name:
558
485
  segments_query = segments_query.filter(label__name=label_name)
559
-
486
+
560
487
  segments = segments_query.all()
561
-
488
+
562
489
  if not segments.exists():
563
- return Response({
564
- "message": "No segments found to validate",
565
- "video_id": pk,
566
- "updated_count": 0
567
- }, status=status.HTTP_200_OK)
568
-
490
+ return Response({"message": "No segments found to validate", "video_id": pk, "updated_count": 0}, status=status.HTTP_200_OK)
491
+
569
492
  updated_count = 0
570
493
  failed_count = 0
571
-
494
+
572
495
  with transaction.atomic():
573
496
  for segment in segments:
574
497
  try:
575
498
  if segment.state:
576
499
  segment.state.is_validated = True
577
- if notes and hasattr(segment.state, 'validation_notes'):
500
+ if notes and hasattr(segment.state, "validation_notes"):
578
501
  segment.state.validation_notes = notes
579
502
  segment.state.save()
580
503
  updated_count += 1
@@ -583,14 +506,18 @@ def video_segments_validation_status(request, pk: int):
583
506
  except Exception as e:
584
507
  logger.error(f"Error validating segment {segment.id}: {e}")
585
508
  failed_count += 1
586
-
509
+
587
510
  logger.info(f"Completed validation for {updated_count} segments in video {pk}")
588
-
589
- return Response({
590
- "message": f"Video segment validation completed for video {pk}",
591
- "video_id": pk,
592
- "total_segments": len(segments),
593
- "updated_count": updated_count,
594
- "failed_count": failed_count,
595
- "label_filter": label_name
596
- }, status=status.HTTP_200_OK)
511
+ logger.info(f"Removing Outside Segments")
512
+ video_file.label_video_segments.filter(video_file=video, label="outside", state__is_validated=False).delete()
513
+ return Response(
514
+ {
515
+ "message": f"Video segment validation completed for video {pk}",
516
+ "video_id": pk,
517
+ "total_segments": len(segments),
518
+ "updated_count": updated_count,
519
+ "failed_count": failed_count,
520
+ "label_filter": label_name,
521
+ },
522
+ status=status.HTTP_200_OK,
523
+ )