endoreg-db 0.8.9.2__py3-none-any.whl → 0.8.9.10__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 (450) hide show
  1. endoreg_db/admin.py +10 -5
  2. endoreg_db/apps.py +4 -7
  3. endoreg_db/authz/auth.py +1 -0
  4. endoreg_db/authz/backends.py +1 -1
  5. endoreg_db/authz/management/commands/list_routes.py +2 -0
  6. endoreg_db/authz/middleware.py +8 -7
  7. endoreg_db/authz/permissions.py +21 -10
  8. endoreg_db/authz/policy.py +14 -19
  9. endoreg_db/authz/views_auth.py +14 -10
  10. endoreg_db/codemods/rename_datetime_fields.py +8 -1
  11. endoreg_db/exceptions.py +5 -2
  12. endoreg_db/forms/__init__.py +0 -1
  13. endoreg_db/forms/examination_form.py +4 -3
  14. endoreg_db/forms/patient_finding_intervention_form.py +30 -8
  15. endoreg_db/forms/patient_form.py +9 -13
  16. endoreg_db/forms/questionnaires/__init__.py +1 -1
  17. endoreg_db/forms/settings/__init__.py +4 -1
  18. endoreg_db/forms/unit.py +2 -1
  19. endoreg_db/helpers/count_db.py +17 -14
  20. endoreg_db/helpers/default_objects.py +2 -1
  21. endoreg_db/helpers/download_segmentation_model.py +4 -3
  22. endoreg_db/helpers/interact.py +0 -5
  23. endoreg_db/helpers/test_video_helper.py +33 -25
  24. endoreg_db/import_files/__init__.py +1 -1
  25. endoreg_db/import_files/context/__init__.py +1 -1
  26. endoreg_db/import_files/context/default_sensitive_meta.py +11 -9
  27. endoreg_db/import_files/context/ensure_center.py +4 -4
  28. endoreg_db/import_files/context/file_lock.py +3 -3
  29. endoreg_db/import_files/context/import_context.py +11 -12
  30. endoreg_db/import_files/context/validate_directories.py +1 -0
  31. endoreg_db/import_files/file_storage/create_report_file.py +57 -34
  32. endoreg_db/import_files/file_storage/create_video_file.py +64 -35
  33. endoreg_db/import_files/file_storage/sensitive_meta_storage.py +5 -2
  34. endoreg_db/import_files/file_storage/state_management.py +89 -122
  35. endoreg_db/import_files/file_storage/storage.py +5 -1
  36. endoreg_db/import_files/processing/report_processing/report_anonymization.py +24 -19
  37. endoreg_db/import_files/processing/sensitive_meta_adapter.py +3 -3
  38. endoreg_db/import_files/processing/video_processing/video_anonymization.py +18 -18
  39. endoreg_db/import_files/pseudonymization/k_anonymity.py +8 -9
  40. endoreg_db/import_files/pseudonymization/k_pseudonymity.py +16 -5
  41. endoreg_db/import_files/report_import_service.py +36 -30
  42. endoreg_db/import_files/video_import_service.py +27 -23
  43. endoreg_db/logger_conf.py +56 -40
  44. endoreg_db/management/__init__.py +1 -1
  45. endoreg_db/management/commands/__init__.py +1 -1
  46. endoreg_db/management/commands/check_auth.py +45 -38
  47. endoreg_db/management/commands/create_model_meta_from_huggingface.py +53 -2
  48. endoreg_db/management/commands/create_multilabel_model_meta.py +54 -19
  49. endoreg_db/management/commands/fix_missing_patient_data.py +105 -71
  50. endoreg_db/management/commands/fix_video_paths.py +75 -54
  51. endoreg_db/management/commands/import_report.py +1 -3
  52. endoreg_db/management/commands/list_routes.py +2 -0
  53. endoreg_db/management/commands/load_ai_model_data.py +8 -2
  54. endoreg_db/management/commands/load_ai_model_label_data.py +0 -1
  55. endoreg_db/management/commands/load_center_data.py +3 -3
  56. endoreg_db/management/commands/load_distribution_data.py +35 -38
  57. endoreg_db/management/commands/load_endoscope_data.py +0 -3
  58. endoreg_db/management/commands/load_examination_data.py +20 -4
  59. endoreg_db/management/commands/load_finding_data.py +18 -3
  60. endoreg_db/management/commands/load_gender_data.py +17 -24
  61. endoreg_db/management/commands/load_green_endoscopy_wuerzburg_data.py +95 -85
  62. endoreg_db/management/commands/load_information_source.py +0 -3
  63. endoreg_db/management/commands/load_lab_value_data.py +14 -3
  64. endoreg_db/management/commands/load_legacy_data.py +303 -0
  65. endoreg_db/management/commands/load_name_data.py +1 -2
  66. endoreg_db/management/commands/load_pdf_type_data.py +4 -8
  67. endoreg_db/management/commands/load_profession_data.py +0 -1
  68. endoreg_db/management/commands/load_report_reader_flag_data.py +0 -4
  69. endoreg_db/management/commands/load_requirement_data.py +6 -2
  70. endoreg_db/management/commands/load_unit_data.py +0 -4
  71. endoreg_db/management/commands/load_user_groups.py +5 -7
  72. endoreg_db/management/commands/model_input.py +169 -0
  73. endoreg_db/management/commands/register_ai_model.py +22 -16
  74. endoreg_db/management/commands/setup_endoreg_db.py +110 -32
  75. endoreg_db/management/commands/storage_management.py +14 -8
  76. endoreg_db/management/commands/summarize_db_content.py +154 -63
  77. endoreg_db/management/commands/train_image_multilabel_model.py +144 -0
  78. endoreg_db/management/commands/validate_video_files.py +82 -50
  79. endoreg_db/management/commands/video_validation.py +4 -6
  80. endoreg_db/migrations/0001_initial.py +112 -63
  81. endoreg_db/models/__init__.py +8 -0
  82. endoreg_db/models/administration/ai/active_model.py +5 -5
  83. endoreg_db/models/administration/ai/ai_model.py +41 -18
  84. endoreg_db/models/administration/ai/model_type.py +1 -0
  85. endoreg_db/models/administration/case/case.py +22 -22
  86. endoreg_db/models/administration/center/__init__.py +5 -5
  87. endoreg_db/models/administration/center/center.py +6 -2
  88. endoreg_db/models/administration/center/center_resource.py +18 -4
  89. endoreg_db/models/administration/center/center_shift.py +3 -1
  90. endoreg_db/models/administration/center/center_waste.py +6 -2
  91. endoreg_db/models/administration/person/__init__.py +1 -1
  92. endoreg_db/models/administration/person/employee/__init__.py +1 -1
  93. endoreg_db/models/administration/person/employee/employee_type.py +3 -1
  94. endoreg_db/models/administration/person/examiner/__init__.py +1 -1
  95. endoreg_db/models/administration/person/examiner/examiner.py +10 -2
  96. endoreg_db/models/administration/person/names/first_name.py +6 -4
  97. endoreg_db/models/administration/person/names/last_name.py +4 -3
  98. endoreg_db/models/administration/person/patient/__init__.py +1 -1
  99. endoreg_db/models/administration/person/patient/patient.py +0 -1
  100. endoreg_db/models/administration/person/patient/patient_external_id.py +0 -1
  101. endoreg_db/models/administration/person/person.py +1 -1
  102. endoreg_db/models/administration/product/__init__.py +7 -6
  103. endoreg_db/models/administration/product/product.py +6 -2
  104. endoreg_db/models/administration/product/product_group.py +9 -7
  105. endoreg_db/models/administration/product/product_material.py +9 -2
  106. endoreg_db/models/administration/product/reference_product.py +64 -15
  107. endoreg_db/models/administration/qualification/qualification.py +3 -1
  108. endoreg_db/models/administration/shift/shift.py +3 -1
  109. endoreg_db/models/administration/shift/shift_type.py +12 -4
  110. endoreg_db/models/aidataset/__init__.py +5 -0
  111. endoreg_db/models/aidataset/aidataset.py +193 -0
  112. endoreg_db/models/label/__init__.py +1 -1
  113. endoreg_db/models/label/label.py +10 -2
  114. endoreg_db/models/label/label_set.py +3 -1
  115. endoreg_db/models/label/label_video_segment/_create_from_video.py +6 -2
  116. endoreg_db/models/label/label_video_segment/label_video_segment.py +148 -44
  117. endoreg_db/models/media/__init__.py +12 -5
  118. endoreg_db/models/media/frame/__init__.py +1 -1
  119. endoreg_db/models/media/frame/frame.py +34 -8
  120. endoreg_db/models/media/pdf/__init__.py +2 -1
  121. endoreg_db/models/media/pdf/raw_pdf.py +11 -4
  122. endoreg_db/models/media/pdf/report_file.py +6 -2
  123. endoreg_db/models/media/pdf/report_reader/__init__.py +3 -3
  124. endoreg_db/models/media/pdf/report_reader/report_reader_flag.py +15 -5
  125. endoreg_db/models/media/video/create_from_file.py +20 -41
  126. endoreg_db/models/media/video/pipe_1.py +75 -30
  127. endoreg_db/models/media/video/pipe_2.py +37 -12
  128. endoreg_db/models/media/video/video_file.py +36 -24
  129. endoreg_db/models/media/video/video_file_ai.py +235 -70
  130. endoreg_db/models/media/video/video_file_anonymize.py +240 -65
  131. endoreg_db/models/media/video/video_file_frames/_bulk_create_frames.py +6 -1
  132. endoreg_db/models/media/video/video_file_frames/_create_frame_object.py +3 -1
  133. endoreg_db/models/media/video/video_file_frames/_delete_frames.py +30 -9
  134. endoreg_db/models/media/video/video_file_frames/_extract_frames.py +95 -29
  135. endoreg_db/models/media/video/video_file_frames/_get_frame.py +13 -3
  136. endoreg_db/models/media/video/video_file_frames/_get_frame_path.py +4 -1
  137. endoreg_db/models/media/video/video_file_frames/_get_frame_paths.py +15 -3
  138. endoreg_db/models/media/video/video_file_frames/_get_frame_range.py +15 -3
  139. endoreg_db/models/media/video/video_file_frames/_get_frames.py +7 -2
  140. endoreg_db/models/media/video/video_file_frames/_initialize_frames.py +109 -23
  141. endoreg_db/models/media/video/video_file_frames/_manage_frame_range.py +111 -27
  142. endoreg_db/models/media/video/video_file_frames/_mark_frames_extracted_status.py +46 -13
  143. endoreg_db/models/media/video/video_file_io.py +85 -33
  144. endoreg_db/models/media/video/video_file_meta/__init__.py +6 -6
  145. endoreg_db/models/media/video/video_file_meta/get_crop_template.py +17 -4
  146. endoreg_db/models/media/video/video_file_meta/get_endo_roi.py +28 -7
  147. endoreg_db/models/media/video/video_file_meta/get_fps.py +46 -13
  148. endoreg_db/models/media/video/video_file_meta/initialize_video_specs.py +81 -20
  149. endoreg_db/models/media/video/video_file_meta/text_meta.py +61 -20
  150. endoreg_db/models/media/video/video_file_meta/video_meta.py +40 -12
  151. endoreg_db/models/media/video/video_file_segments.py +118 -27
  152. endoreg_db/models/media/video/video_metadata.py +25 -6
  153. endoreg_db/models/media/video/video_processing.py +54 -15
  154. endoreg_db/models/medical/__init__.py +3 -13
  155. endoreg_db/models/medical/contraindication/__init__.py +3 -1
  156. endoreg_db/models/medical/disease.py +18 -6
  157. endoreg_db/models/medical/event.py +6 -2
  158. endoreg_db/models/medical/examination/__init__.py +5 -1
  159. endoreg_db/models/medical/examination/examination.py +22 -6
  160. endoreg_db/models/medical/examination/examination_indication.py +23 -7
  161. endoreg_db/models/medical/examination/examination_time.py +6 -2
  162. endoreg_db/models/medical/finding/__init__.py +3 -1
  163. endoreg_db/models/medical/finding/finding.py +37 -12
  164. endoreg_db/models/medical/finding/finding_classification.py +27 -8
  165. endoreg_db/models/medical/finding/finding_intervention.py +19 -6
  166. endoreg_db/models/medical/finding/finding_type.py +3 -1
  167. endoreg_db/models/medical/hardware/__init__.py +1 -1
  168. endoreg_db/models/medical/hardware/endoscope.py +14 -2
  169. endoreg_db/models/medical/laboratory/__init__.py +1 -1
  170. endoreg_db/models/medical/laboratory/lab_value.py +139 -39
  171. endoreg_db/models/medical/medication/__init__.py +7 -3
  172. endoreg_db/models/medical/medication/medication.py +3 -1
  173. endoreg_db/models/medical/medication/medication_indication.py +3 -1
  174. endoreg_db/models/medical/medication/medication_indication_type.py +11 -3
  175. endoreg_db/models/medical/medication/medication_intake_time.py +3 -1
  176. endoreg_db/models/medical/medication/medication_schedule.py +3 -1
  177. endoreg_db/models/medical/patient/__init__.py +2 -10
  178. endoreg_db/models/medical/patient/medication_examples.py +3 -14
  179. endoreg_db/models/medical/patient/patient_disease.py +17 -5
  180. endoreg_db/models/medical/patient/patient_event.py +12 -4
  181. endoreg_db/models/medical/patient/patient_examination.py +52 -15
  182. endoreg_db/models/medical/patient/patient_examination_indication.py +15 -4
  183. endoreg_db/models/medical/patient/patient_finding.py +105 -29
  184. endoreg_db/models/medical/patient/patient_finding_classification.py +41 -12
  185. endoreg_db/models/medical/patient/patient_finding_intervention.py +11 -3
  186. endoreg_db/models/medical/patient/patient_lab_sample.py +6 -2
  187. endoreg_db/models/medical/patient/patient_lab_value.py +42 -10
  188. endoreg_db/models/medical/patient/patient_medication.py +25 -7
  189. endoreg_db/models/medical/patient/patient_medication_schedule.py +34 -10
  190. endoreg_db/models/metadata/model_meta.py +40 -12
  191. endoreg_db/models/metadata/model_meta_logic.py +51 -16
  192. endoreg_db/models/metadata/sensitive_meta.py +65 -28
  193. endoreg_db/models/metadata/sensitive_meta_logic.py +28 -26
  194. endoreg_db/models/metadata/video_meta.py +146 -39
  195. endoreg_db/models/metadata/video_prediction_logic.py +70 -21
  196. endoreg_db/models/metadata/video_prediction_meta.py +80 -27
  197. endoreg_db/models/operation_log.py +63 -0
  198. endoreg_db/models/other/__init__.py +10 -10
  199. endoreg_db/models/other/distribution/__init__.py +9 -7
  200. endoreg_db/models/other/distribution/base_value_distribution.py +3 -1
  201. endoreg_db/models/other/distribution/date_value_distribution.py +19 -5
  202. endoreg_db/models/other/distribution/multiple_categorical_value_distribution.py +3 -1
  203. endoreg_db/models/other/distribution/numeric_value_distribution.py +34 -9
  204. endoreg_db/models/other/emission/__init__.py +1 -1
  205. endoreg_db/models/other/emission/emission_factor.py +9 -3
  206. endoreg_db/models/other/information_source.py +15 -5
  207. endoreg_db/models/other/material.py +3 -1
  208. endoreg_db/models/other/transport_route.py +3 -1
  209. endoreg_db/models/other/unit.py +6 -2
  210. endoreg_db/models/report/report.py +0 -1
  211. endoreg_db/models/requirement/requirement.py +84 -27
  212. endoreg_db/models/requirement/requirement_error.py +5 -6
  213. endoreg_db/models/requirement/requirement_evaluation/__init__.py +1 -1
  214. endoreg_db/models/requirement/requirement_evaluation/evaluate_with_dependencies.py +8 -8
  215. endoreg_db/models/requirement/requirement_evaluation/get_values.py +3 -3
  216. endoreg_db/models/requirement/requirement_evaluation/requirement_type_parser.py +24 -8
  217. endoreg_db/models/requirement/requirement_operator.py +28 -8
  218. endoreg_db/models/requirement/requirement_set.py +34 -11
  219. endoreg_db/models/state/__init__.py +1 -0
  220. endoreg_db/models/state/audit_ledger.py +9 -2
  221. endoreg_db/models/{media → state}/processing_history/__init__.py +1 -3
  222. endoreg_db/models/state/processing_history/processing_history.py +136 -0
  223. endoreg_db/models/state/raw_pdf.py +0 -1
  224. endoreg_db/models/state/video.py +2 -4
  225. endoreg_db/models/utils.py +4 -2
  226. endoreg_db/queries/__init__.py +2 -6
  227. endoreg_db/queries/annotations/__init__.py +1 -3
  228. endoreg_db/queries/annotations/legacy.py +37 -26
  229. endoreg_db/root_urls.py +3 -4
  230. endoreg_db/schemas/examination_evaluation.py +3 -0
  231. endoreg_db/serializers/Frames_NICE_and_PARIS_classifications.py +249 -163
  232. endoreg_db/serializers/__init__.py +2 -8
  233. endoreg_db/serializers/administration/__init__.py +1 -2
  234. endoreg_db/serializers/administration/ai/__init__.py +0 -1
  235. endoreg_db/serializers/administration/ai/active_model.py +3 -1
  236. endoreg_db/serializers/administration/ai/ai_model.py +5 -3
  237. endoreg_db/serializers/administration/ai/model_type.py +3 -1
  238. endoreg_db/serializers/administration/center.py +7 -2
  239. endoreg_db/serializers/administration/gender.py +4 -2
  240. endoreg_db/serializers/anonymization.py +13 -13
  241. endoreg_db/serializers/evaluation/examination_evaluation.py +0 -1
  242. endoreg_db/serializers/examination/__init__.py +1 -1
  243. endoreg_db/serializers/examination/base.py +12 -13
  244. endoreg_db/serializers/examination/dropdown.py +6 -7
  245. endoreg_db/serializers/examination_serializer.py +3 -6
  246. endoreg_db/serializers/finding/__init__.py +1 -1
  247. endoreg_db/serializers/finding/finding.py +14 -7
  248. endoreg_db/serializers/finding_classification/__init__.py +3 -3
  249. endoreg_db/serializers/finding_classification/choice.py +3 -3
  250. endoreg_db/serializers/finding_classification/classification.py +2 -4
  251. endoreg_db/serializers/label_video_segment/__init__.py +5 -3
  252. endoreg_db/serializers/{label → label_video_segment}/image_classification_annotation.py +5 -5
  253. endoreg_db/serializers/label_video_segment/label/__init__.py +6 -0
  254. endoreg_db/serializers/{label → label_video_segment/label}/label.py +1 -1
  255. endoreg_db/serializers/label_video_segment/label_video_segment.py +338 -228
  256. endoreg_db/serializers/meta/__init__.py +1 -2
  257. endoreg_db/serializers/meta/sensitive_meta_detail.py +28 -13
  258. endoreg_db/serializers/meta/sensitive_meta_update.py +51 -46
  259. endoreg_db/serializers/meta/sensitive_meta_verification.py +19 -16
  260. endoreg_db/serializers/misc/__init__.py +2 -2
  261. endoreg_db/serializers/misc/file_overview.py +11 -7
  262. endoreg_db/serializers/misc/stats.py +10 -8
  263. endoreg_db/serializers/misc/translatable_field_mix_in.py +6 -6
  264. endoreg_db/serializers/misc/upload_job.py +32 -29
  265. endoreg_db/serializers/patient/__init__.py +2 -1
  266. endoreg_db/serializers/patient/patient.py +32 -15
  267. endoreg_db/serializers/patient/patient_dropdown.py +11 -3
  268. endoreg_db/serializers/patient_examination/__init__.py +1 -1
  269. endoreg_db/serializers/patient_examination/patient_examination.py +67 -40
  270. endoreg_db/serializers/patient_finding/__init__.py +1 -1
  271. endoreg_db/serializers/patient_finding/patient_finding.py +2 -1
  272. endoreg_db/serializers/patient_finding/patient_finding_classification.py +17 -9
  273. endoreg_db/serializers/patient_finding/patient_finding_detail.py +26 -17
  274. endoreg_db/serializers/patient_finding/patient_finding_intervention.py +7 -5
  275. endoreg_db/serializers/patient_finding/patient_finding_list.py +10 -11
  276. endoreg_db/serializers/patient_finding/patient_finding_write.py +36 -27
  277. endoreg_db/serializers/pdf/__init__.py +1 -3
  278. endoreg_db/serializers/requirements/requirement_schema.py +1 -6
  279. endoreg_db/serializers/sensitive_meta_serializer.py +100 -81
  280. endoreg_db/serializers/video/__init__.py +2 -2
  281. endoreg_db/serializers/video/{segmentation.py → video_file.py} +66 -47
  282. endoreg_db/serializers/video/video_file_brief.py +6 -2
  283. endoreg_db/serializers/video/video_file_detail.py +36 -23
  284. endoreg_db/serializers/video/video_file_list.py +4 -2
  285. endoreg_db/serializers/video/video_processing_history.py +54 -50
  286. endoreg_db/services/__init__.py +1 -1
  287. endoreg_db/services/anonymization.py +2 -2
  288. endoreg_db/services/examination_evaluation.py +40 -17
  289. endoreg_db/services/model_meta_from_hf.py +76 -0
  290. endoreg_db/services/polling_coordinator.py +101 -70
  291. endoreg_db/services/pseudonym_service.py +27 -22
  292. endoreg_db/services/report_import.py +6 -3
  293. endoreg_db/services/segment_sync.py +75 -59
  294. endoreg_db/services/video_import.py +6 -7
  295. endoreg_db/urls/__init__.py +2 -2
  296. endoreg_db/urls/ai.py +7 -25
  297. endoreg_db/urls/anonymization.py +61 -15
  298. endoreg_db/urls/auth.py +4 -4
  299. endoreg_db/urls/classification.py +4 -9
  300. endoreg_db/urls/examination.py +27 -18
  301. endoreg_db/urls/media.py +27 -34
  302. endoreg_db/urls/patient.py +11 -7
  303. endoreg_db/urls/requirements.py +3 -1
  304. endoreg_db/urls/root_urls.py +2 -3
  305. endoreg_db/urls/stats.py +24 -16
  306. endoreg_db/urls/upload.py +3 -11
  307. endoreg_db/utils/__init__.py +14 -15
  308. endoreg_db/utils/ai/__init__.py +1 -1
  309. endoreg_db/utils/ai/data_loader_for_model_input.py +262 -0
  310. endoreg_db/utils/ai/data_loader_for_model_training.py +262 -0
  311. endoreg_db/utils/ai/get.py +2 -1
  312. endoreg_db/utils/ai/inference_dataset.py +14 -15
  313. endoreg_db/utils/ai/model_training/config.py +117 -0
  314. endoreg_db/utils/ai/model_training/dataset.py +74 -0
  315. endoreg_db/utils/ai/model_training/losses.py +68 -0
  316. endoreg_db/utils/ai/model_training/metrics.py +78 -0
  317. endoreg_db/utils/ai/model_training/model_backbones.py +155 -0
  318. endoreg_db/utils/ai/model_training/model_gastronet_resnet.py +118 -0
  319. endoreg_db/utils/ai/model_training/trainer_gastronet_multilabel.py +771 -0
  320. endoreg_db/utils/ai/multilabel_classification_net.py +21 -6
  321. endoreg_db/utils/ai/predict.py +4 -4
  322. endoreg_db/utils/ai/preprocess.py +19 -11
  323. endoreg_db/utils/calc_duration_seconds.py +4 -4
  324. endoreg_db/utils/case_generator/lab_sample_factory.py +3 -4
  325. endoreg_db/utils/check_video_files.py +74 -47
  326. endoreg_db/utils/cropping.py +10 -9
  327. endoreg_db/utils/dataloader.py +11 -3
  328. endoreg_db/utils/dates.py +3 -4
  329. endoreg_db/utils/defaults/set_default_center.py +7 -6
  330. endoreg_db/utils/env.py +6 -2
  331. endoreg_db/utils/extract_specific_frames.py +24 -9
  332. endoreg_db/utils/file_operations.py +30 -18
  333. endoreg_db/utils/fix_video_path_direct.py +57 -41
  334. endoreg_db/utils/frame_anonymization_utils.py +157 -157
  335. endoreg_db/utils/hashs.py +3 -18
  336. endoreg_db/utils/links/requirement_link.py +96 -52
  337. endoreg_db/utils/ocr.py +30 -25
  338. endoreg_db/utils/operation_log.py +61 -0
  339. endoreg_db/utils/parse_and_generate_yaml.py +12 -13
  340. endoreg_db/utils/paths.py +6 -6
  341. endoreg_db/utils/permissions.py +40 -24
  342. endoreg_db/utils/pipelines/process_video_dir.py +50 -26
  343. endoreg_db/utils/product/sum_emissions.py +5 -3
  344. endoreg_db/utils/product/sum_weights.py +4 -2
  345. endoreg_db/utils/pydantic_models/__init__.py +3 -4
  346. endoreg_db/utils/requirement_operator_logic/_old/lab_value_operators.py +207 -107
  347. endoreg_db/utils/requirement_operator_logic/_old/model_evaluators.py +252 -65
  348. endoreg_db/utils/requirement_operator_logic/new_operator_logic.py +27 -10
  349. endoreg_db/utils/setup_config.py +21 -5
  350. endoreg_db/utils/storage.py +3 -1
  351. endoreg_db/utils/translation.py +19 -15
  352. endoreg_db/utils/uuid.py +1 -0
  353. endoreg_db/utils/validate_endo_roi.py +12 -4
  354. endoreg_db/utils/validate_subcategory_dict.py +26 -24
  355. endoreg_db/utils/validate_video_detailed.py +207 -149
  356. endoreg_db/utils/video/__init__.py +7 -3
  357. endoreg_db/utils/video/extract_frames.py +30 -18
  358. endoreg_db/utils/video/names.py +11 -6
  359. endoreg_db/utils/video/streaming_processor.py +175 -101
  360. endoreg_db/utils/video/video_splitter.py +30 -19
  361. endoreg_db/views/Frames_NICE_and_PARIS_classifications_views.py +59 -50
  362. endoreg_db/views/__init__.py +0 -20
  363. endoreg_db/views/anonymization/__init__.py +6 -2
  364. endoreg_db/views/anonymization/media_management.py +2 -6
  365. endoreg_db/views/anonymization/overview.py +34 -1
  366. endoreg_db/views/anonymization/validate.py +79 -18
  367. endoreg_db/views/auth/__init__.py +1 -1
  368. endoreg_db/views/auth/keycloak.py +16 -14
  369. endoreg_db/views/examination/__init__.py +12 -15
  370. endoreg_db/views/examination/examination.py +5 -5
  371. endoreg_db/views/examination/examination_manifest_cache.py +5 -5
  372. endoreg_db/views/examination/get_finding_classification_choices.py +8 -5
  373. endoreg_db/views/examination/get_finding_classifications.py +9 -7
  374. endoreg_db/views/examination/get_findings.py +8 -10
  375. endoreg_db/views/examination/get_instruments.py +3 -2
  376. endoreg_db/views/examination/get_interventions.py +1 -1
  377. endoreg_db/views/finding/__init__.py +2 -2
  378. endoreg_db/views/finding/finding.py +58 -54
  379. endoreg_db/views/finding/get_classifications.py +1 -1
  380. endoreg_db/views/finding/get_interventions.py +1 -1
  381. endoreg_db/views/finding_classification/__init__.py +5 -5
  382. endoreg_db/views/finding_classification/finding_classification.py +5 -6
  383. endoreg_db/views/finding_classification/get_classification_choices.py +3 -4
  384. endoreg_db/views/media/__init__.py +13 -13
  385. endoreg_db/views/media/pdf_media.py +9 -9
  386. endoreg_db/views/media/sensitive_metadata.py +10 -7
  387. endoreg_db/views/media/video_media.py +4 -4
  388. endoreg_db/views/meta/__init__.py +1 -1
  389. endoreg_db/views/meta/sensitive_meta_list.py +20 -22
  390. endoreg_db/views/meta/sensitive_meta_verification.py +14 -11
  391. endoreg_db/views/misc/__init__.py +6 -34
  392. endoreg_db/views/misc/center.py +2 -1
  393. endoreg_db/views/misc/csrf.py +2 -1
  394. endoreg_db/views/misc/gender.py +2 -1
  395. endoreg_db/views/misc/stats.py +141 -106
  396. endoreg_db/views/patient/__init__.py +1 -3
  397. endoreg_db/views/patient/patient.py +141 -99
  398. endoreg_db/views/patient_examination/__init__.py +5 -5
  399. endoreg_db/views/patient_examination/patient_examination.py +43 -42
  400. endoreg_db/views/patient_examination/patient_examination_create.py +10 -15
  401. endoreg_db/views/patient_examination/patient_examination_detail.py +12 -15
  402. endoreg_db/views/patient_examination/patient_examination_list.py +21 -17
  403. endoreg_db/views/patient_examination/video.py +114 -80
  404. endoreg_db/views/patient_finding/__init__.py +1 -1
  405. endoreg_db/views/patient_finding/patient_finding.py +17 -10
  406. endoreg_db/views/patient_finding/patient_finding_optimized.py +127 -95
  407. endoreg_db/views/patient_finding_classification/__init__.py +1 -1
  408. endoreg_db/views/patient_finding_classification/pfc_create.py +35 -27
  409. endoreg_db/views/report/reimport.py +1 -1
  410. endoreg_db/views/report/report_stream.py +5 -8
  411. endoreg_db/views/requirement/__init__.py +2 -1
  412. endoreg_db/views/requirement/evaluate.py +7 -9
  413. endoreg_db/views/requirement/lookup.py +2 -3
  414. endoreg_db/views/requirement/lookup_store.py +0 -1
  415. endoreg_db/views/requirement/requirement_utils.py +2 -4
  416. endoreg_db/views/stats/__init__.py +4 -4
  417. endoreg_db/views/stats/stats_views.py +152 -115
  418. endoreg_db/views/video/__init__.py +18 -27
  419. endoreg_db/views/{ai → video/ai}/__init__.py +2 -2
  420. endoreg_db/views/{ai → video/ai}/label.py +20 -16
  421. endoreg_db/views/video/correction.py +5 -6
  422. endoreg_db/views/video/reimport.py +134 -99
  423. endoreg_db/views/video/segments_crud.py +134 -44
  424. endoreg_db/views/video/video_apply_mask.py +13 -12
  425. endoreg_db/views/video/video_correction.py +2 -1
  426. endoreg_db/views/video/video_download_processed.py +15 -15
  427. endoreg_db/views/video/video_meta_stats.py +7 -6
  428. endoreg_db/views/video/video_processing_history.py +3 -2
  429. endoreg_db/views/video/video_remove_frames.py +13 -12
  430. endoreg_db/views/video/video_stream.py +110 -82
  431. {endoreg_db-0.8.9.2.dist-info → endoreg_db-0.8.9.10.dist-info}/METADATA +9 -3
  432. {endoreg_db-0.8.9.2.dist-info → endoreg_db-0.8.9.10.dist-info}/RECORD +434 -431
  433. endoreg_db/management/commands/import_fallback_video.py +0 -203
  434. endoreg_db/management/commands/import_video.py +0 -422
  435. endoreg_db/management/commands/import_video_with_classification.py +0 -367
  436. endoreg_db/models/media/processing_history/processing_history.py +0 -96
  437. endoreg_db/serializers/label/__init__.py +0 -7
  438. endoreg_db/serializers/label_video_segment/_lvs_create.py +0 -149
  439. endoreg_db/serializers/label_video_segment/_lvs_update.py +0 -138
  440. endoreg_db/serializers/label_video_segment/_lvs_validate.py +0 -149
  441. endoreg_db/serializers/label_video_segment/label_video_segment_annotation.py +0 -99
  442. endoreg_db/serializers/label_video_segment/label_video_segment_update.py +0 -163
  443. endoreg_db/services/__old/pdf_import.py +0 -1487
  444. endoreg_db/services/__old/video_import.py +0 -1306
  445. endoreg_db/tasks/upload_tasks.py +0 -216
  446. endoreg_db/tasks/video_ingest.py +0 -161
  447. endoreg_db/tasks/video_processing_tasks.py +0 -327
  448. endoreg_db/views/misc/translation.py +0 -182
  449. {endoreg_db-0.8.9.2.dist-info → endoreg_db-0.8.9.10.dist-info}/WHEEL +0 -0
  450. {endoreg_db-0.8.9.2.dist-info → endoreg_db-0.8.9.10.dist-info}/licenses/LICENSE +0 -0
@@ -1,43 +1,32 @@
1
1
  from rest_framework import serializers
2
- from typing import List
3
2
  from django.core.exceptions import ObjectDoesNotExist
4
3
  from django.conf import settings
5
4
  from urllib.parse import urljoin
6
5
  from pathlib import Path
7
-
8
- from ...models import LabelVideoSegment, VideoFile
6
+ from typing import Any, Literal, Dict
9
7
  import logging
10
- from ._lvs_create import (
11
- _create,
12
- _get_video_file,
13
- _get_label,
14
- _validate_fps,
15
- _calculate_frame_numbers,
16
- _get_information_source
17
- )
18
- from ._lvs_update import (
19
- _update,
8
+ from django.db.models import Prefetch
9
+ from django.db import models
10
+
11
+ from endoreg_db.models import (
12
+ LabelVideoSegment,
13
+ VideoFile,
14
+ Label,
15
+ InformationSource,
16
+ ImageClassificationAnnotation,
20
17
  )
21
- from ._lvs_validate import (
22
- _validate,
23
- _extract_and_validate_basic_attrs,
24
- _process_time_data,
25
- _process_frame_data,
26
- _validate_time_and_frame_presence,
27
- _validate_time_constraints,
28
- _validate_frame_constraints
18
+ from endoreg_db.serializers.label_video_segment.image_classification_annotation import (
19
+ ImageClassificationAnnotationSerializer,
29
20
  )
30
- from endoreg_db.serializers import ImageClassificationAnnotationSerializer
31
21
 
32
22
  logger = logging.getLogger(__name__)
33
23
 
34
- # add these small helpers (could be private functions in this module)
24
+
25
+ # --- Helper Functions ---
26
+
27
+
35
28
  def _media_relpath_from_file_path(file_path) -> str:
36
- """
37
- Return a media-relative path (never an absolute server path).
38
- If MEDIA_ROOT is a prefix, strip it; otherwise return the basename.
39
- Accepts str or Path-like.
40
- """
29
+ """Return a media-relative path (never an absolute server path)."""
41
30
  p = Path(str(file_path))
42
31
  media_root = getattr(settings, "MEDIA_ROOT", None)
43
32
  if media_root:
@@ -48,11 +37,9 @@ def _media_relpath_from_file_path(file_path) -> str:
48
37
  pass
49
38
  return p.name # safe fallback
50
39
 
40
+
51
41
  def _media_url_from_file_path(file_path, request=None) -> str:
52
- """
53
- Build a public URL for the file using MEDIA_URL + relpath.
54
- If `request` is provided, return an absolute URL.
55
- """
42
+ """Build a public URL for the file using MEDIA_URL + relpath."""
56
43
  base = getattr(settings, "MEDIA_URL", "/media/")
57
44
  if not base.endswith("/"):
58
45
  base += "/"
@@ -67,251 +54,374 @@ def _media_url_from_file_path(file_path, request=None) -> str:
67
54
 
68
55
 
69
56
  class LabelVideoSegmentSerializer(serializers.ModelSerializer):
70
- """Serializer for creating and retrieving LabelVideoSegment instances."""
71
-
72
- # Additional fields for convenience - matching frontend expectations
73
- start_time = serializers.SerializerMethodField()
74
- end_time = serializers.SerializerMethodField()
75
-
76
- # Input fields (write_only for creation)
77
- video_id = serializers.IntegerField(required=False, help_text="Video file ID")
78
- label_id = serializers.IntegerField(required=False, allow_null=True, help_text="Label ID")
79
-
80
- # Add support for label names (both Label and VideoSegmentationLabel)
81
- label_name = serializers.CharField(write_only=True, required=False, allow_null=True, help_text="Label name")
82
- label_display = serializers.SerializerMethodField()
83
-
84
- # Read-only fields for response
85
- video_name = serializers.SerializerMethodField(read_only=True)
86
- frame_predictions = serializers.SerializerMethodField(read_only=True, help_text="Frame predictions for the video segment")
87
- manual_frame_annotations = serializers.SerializerMethodField(read_only=True, help_text="Manual frame annotations for the video segment")
88
-
89
- time_segments = serializers.SerializerMethodField(read_only=True, help_text="Time segments for the video segment")
57
+ """Serializer for creating, retrieving, and updating LabelVideoSegment instances."""
90
58
 
91
- create = _create
92
- get_video_file = _get_video_file
93
- get_label = _get_label
94
- validate_fps = _validate_fps
95
- calculate_frame_numbers = _calculate_frame_numbers
96
- get_information_source = _get_information_source
59
+ # Write-only fields for Input (Frontend sends seconds)
60
+ start_time = serializers.FloatField(
61
+ write_only=True, required=False, allow_null=True
62
+ )
63
+ end_time = serializers.FloatField(write_only=True, required=False, allow_null=True)
97
64
 
98
- update = _update
65
+ # Input fields
66
+ video_id = serializers.IntegerField(required=False, help_text="Video file ID")
67
+ label_id = serializers.IntegerField(
68
+ required=False, allow_null=True, help_text="Label ID"
69
+ )
70
+ label_name = serializers.CharField(
71
+ write_only=True, required=False, allow_null=True, help_text="Label name"
72
+ )
99
73
 
100
- validate = _validate
101
- extract_and_validate_basic_attrs = _extract_and_validate_basic_attrs
102
- process_time_data = _process_time_data
103
- process_frame_data = _process_frame_data
104
- validate_time_and_frame_presence = _validate_time_and_frame_presence
105
- validate_time_constraints = _validate_time_constraints
106
- validate_frame_constraints = _validate_frame_constraints
74
+ # Read-only fields for Output
75
+ video_name = serializers.SerializerMethodField(read_only=True)
76
+ frame_predictions = serializers.SerializerMethodField(read_only=True)
77
+ manual_frame_annotations = serializers.SerializerMethodField(read_only=True)
78
+ time_segments = serializers.SerializerMethodField(read_only=True)
107
79
 
108
80
  class Meta:
109
81
  model = LabelVideoSegment
110
82
  fields = [
111
- 'id',
83
+ "id",
112
84
  "video_file",
113
- 'video_name',
85
+ "video_name",
114
86
  "video_id",
115
87
  "label",
116
- 'label_name',
88
+ "label_name",
117
89
  "label_id",
118
- 'start_frame_number',
119
- 'end_frame_number',
120
- 'start_time',
121
- 'end_time',
122
- "label_display",
90
+ "start_frame_number",
91
+ "end_frame_number",
92
+ "start_time",
93
+ "end_time",
123
94
  "frame_predictions",
124
95
  "manual_frame_annotations",
125
- "time_segments"
96
+ "time_segments",
126
97
  ]
127
- read_only_fields = ['id', 'video_name']
98
+ read_only_fields = ["id", "video_name"]
128
99
  extra_kwargs = {
129
- 'start_frame_number': {'required': False},
130
- 'end_frame_number': {'required': False},
131
- 'video_file': {'required': False},
132
- 'label': {'required': False},
100
+ "start_frame_number": {"required": False},
101
+ "end_frame_number": {"required": False},
102
+ "video_file": {"required": False},
103
+ "label": {"required": False},
133
104
  }
134
-
135
105
 
136
- def __init__(self, *args, **kwargs):
106
+ # --- Internal Helpers ---
107
+
108
+ def _get_video_file(self, video_id) -> VideoFile:
109
+ try:
110
+ return VideoFile.objects.get(id=video_id)
111
+ except ObjectDoesNotExist:
112
+ raise serializers.ValidationError(
113
+ f"VideoFile with id {video_id} does not exist"
114
+ )
115
+
116
+ def _get_label(self, label_id, label_name):
117
+ if label_id:
118
+ try:
119
+ return Label.objects.get(id=label_id)
120
+ except ObjectDoesNotExist:
121
+ raise serializers.ValidationError(
122
+ f"Label with id {label_id} does not exist"
123
+ )
124
+ elif label_name:
125
+ label, _ = Label.get_or_create_from_name(label_name)
126
+ if not label:
127
+ raise serializers.ValidationError(
128
+ f"Failed to create or retrieve label with name {label_name}"
129
+ )
130
+ return label
131
+ return None
132
+
133
+ def _validate_fps(self, video_file) -> float:
134
+ """Helper to get valid FPS from video file."""
135
+ fps = video_file.get_fps()
136
+ if not fps or fps <= 0:
137
+ raise serializers.ValidationError(
138
+ "Video file must have a defined, positive FPS to calculate frames."
139
+ )
140
+ return float(fps)
141
+
142
+ def _convert_time_to_frame(self, time_val, fps):
143
+ return int(round(float(time_val) * fps))
144
+
145
+ def _get_information_source(self) -> InformationSource:
146
+ source, _ = InformationSource.objects.get_or_create(
147
+ name="Manual Annotation",
148
+ defaults={
149
+ "description": "Manually created label segments via web interface"
150
+ },
151
+ )
152
+ return source
153
+
154
+ # --- DRF Overrides ---
155
+
156
+ def to_internal_value(self, data) -> Any:
157
+ """Normalize input data keys."""
158
+ # Frontend might send video_file or video_id, label or label_id
159
+ if "video_file" in data:
160
+ data["video_id"] = data["video_file"]
161
+ if "label" in data:
162
+ data["label_id"] = data["label"]
163
+ return super().to_internal_value(data)
164
+
165
+ def validate(self, attrs) -> Any:
166
+ """
167
+ Validate logical consistency:
168
+ 1. Ensure we have Video reference.
169
+ 2. Ensure we have EITHER (start_time, end_time) OR (start_frame, end_frame).
170
+ 3. Ensure Start < End.
171
+ """
172
+ # 1. Video Check
173
+ video_id = attrs.get("video_id") or self.initial_data.get("video_id")
174
+ if not video_id and not self.instance:
175
+ raise serializers.ValidationError("video_id is required.")
176
+
177
+ # 2. Time vs Frame Check
178
+ start_time = attrs.get("start_time")
179
+ end_time = attrs.get("end_time")
180
+ start_frame = attrs.get("start_frame_number")
181
+ end_frame = attrs.get("end_frame_number")
182
+
183
+ # If updating, fallback to instance values
184
+ if self.instance:
185
+ if start_time is None and "start_time" not in attrs:
186
+ # We don't have time in attrs, but we might have frames
187
+ pass
188
+ if start_frame is None:
189
+ start_frame = self.instance.start_frame_number
190
+ if end_frame is None:
191
+ end_frame = self.instance.end_frame_number
192
+
193
+ has_time = start_time is not None and end_time is not None
194
+ has_frames = start_frame is not None and end_frame is not None
195
+
196
+ if not has_time and not has_frames:
197
+ # If creating, strictly require one set
198
+ if not self.instance:
199
+ raise serializers.ValidationError(
200
+ "Either (start_time, end_time) OR (start_frame_number, end_frame_number) must be provided."
201
+ )
202
+
203
+ # 3. Logical Constraints
204
+ if has_time:
205
+ if start_time < 0:
206
+ raise serializers.ValidationError(
207
+ {"start_time": "Must be non-negative."}
208
+ )
209
+ if end_time <= start_time:
210
+ raise serializers.ValidationError(
211
+ {"end_time": "Must be greater than start_time."}
212
+ )
213
+
214
+ if has_frames:
215
+ if start_frame < 0:
216
+ raise serializers.ValidationError(
217
+ {"start_frame_number": "Must be non-negative."}
218
+ )
219
+ if end_frame <= start_frame:
220
+ raise serializers.ValidationError(
221
+ {"end_frame_number": "Must be greater than start_frame_number."}
222
+ )
223
+
224
+ return attrs
225
+
226
+ def create(self, validated_data) -> LabelVideoSegment:
227
+ """
228
+ Create logic:
229
+ 1. Extract ID/Name/Time data.
230
+ 2. Resolve Objects (Video, Label).
231
+ 3. Convert Time -> Frames.
232
+ 4. Save.
233
+ """
234
+ try:
235
+ # Extract basic data
236
+ video_id = validated_data.pop("video_id")
237
+ label_id = validated_data.pop("label_id", None)
238
+ label_name = validated_data.pop("label_name", None)
239
+
240
+ # Extract time data (might be None if frames were passed directly)
241
+ start_time = validated_data.pop("start_time", None)
242
+ end_time = validated_data.pop("end_time", None)
243
+
244
+ # Resolve Objects
245
+ video_file = self._get_video_file(video_id)
246
+ label = self._get_label(label_id, label_name)
247
+ source = self._get_information_source()
248
+
249
+ # Calculate Frames if time is provided
250
+ if start_time is not None and end_time is not None:
251
+ fps = self._validate_fps(video_file)
252
+ validated_data["start_frame_number"] = self._convert_time_to_frame(
253
+ start_time, fps
254
+ )
255
+ validated_data["end_frame_number"] = self._convert_time_to_frame(
256
+ end_time, fps
257
+ )
258
+
259
+ # Final check to ensure we have frames (in case validation slipped or logic failed)
260
+ if (
261
+ "start_frame_number" not in validated_data
262
+ or "end_frame_number" not in validated_data
263
+ ):
264
+ raise serializers.ValidationError(
265
+ "Could not determine frame numbers. Please provide start_time/end_time."
266
+ )
267
+
268
+ # Create
269
+ segment = LabelVideoSegment.safe_create(
270
+ video_file=video_file,
271
+ label=label,
272
+ source=source,
273
+ start_frame_number=validated_data["start_frame_number"],
274
+ end_frame_number=validated_data["end_frame_number"],
275
+ prediction_meta=None,
276
+ )
277
+ segment.save()
278
+
279
+ logger.info(f"Created segment {segment.pk} for video {video_id}")
280
+ return segment
281
+
282
+ except Exception as e:
283
+ logger.error(f"Error creating segment: {e}")
284
+ raise serializers.ValidationError(str(e))
285
+
286
+ def update(self, instance, validated_data) -> Any:
137
287
  """
138
- Initialize the serializer and log the initial input data if available.
288
+ Update logic:
289
+ 1. Check if Video changed (affects FPS).
290
+ 2. Check if Label changed.
291
+ 3. Check if Time changed -> Recalculate Frames.
139
292
  """
140
- super().__init__(*args, **kwargs)
141
- if hasattr(self, 'initial_data'):
142
- logger.debug(f"Serializer initialized with data: {self.initial_data}")
293
+ try:
294
+ # Pop fields
295
+ video_id = validated_data.pop("video_id", None)
296
+ label_id = validated_data.pop("label_id", None)
297
+ label_id_present = "label_id" in validated_data
298
+ label_name_present = "label_name" in validated_data
299
+ label_name = validated_data.pop("label_name", None)
300
+ start_time = validated_data.pop("start_time", None)
301
+ end_time = validated_data.pop("end_time", None)
302
+
303
+ # 1. Update Video?
304
+ current_video = instance.video_file
305
+ if video_id and current_video.id != video_id:
306
+ current_video = self._get_video_file(video_id)
307
+ instance.video_file = current_video
308
+
309
+ # 2. Update Label?
310
+ if label_id_present or label_name_present:
311
+ if label_id or label_name:
312
+ instance.label = self._get_label(label_id, label_name)
313
+ else:
314
+ instance.label = None
315
+
316
+ # 3. Update Frames (from Time or direct Frames)
317
+ # We need FPS if we are using time
318
+ fps = None
319
+ if start_time is not None or end_time is not None:
320
+ fps = self._validate_fps(current_video)
321
+
322
+ if start_time is not None:
323
+ instance.start_frame_number = self._convert_time_to_frame(
324
+ start_time, fps
325
+ )
326
+ elif "start_frame_number" in validated_data:
327
+ instance.start_frame_number = validated_data["start_frame_number"]
328
+
329
+ if end_time is not None:
330
+ instance.end_frame_number = self._convert_time_to_frame(end_time, fps)
331
+ elif "end_frame_number" in validated_data:
332
+ instance.end_frame_number = validated_data["end_frame_number"]
333
+
334
+ # Final Frame Safety Check
335
+ if instance.start_frame_number >= instance.end_frame_number:
336
+ raise serializers.ValidationError(
337
+ "start_time/frame must be strictly less than end_time/frame"
338
+ )
339
+
340
+ instance.save()
341
+ logger.info(f"Updated segment {instance.pk}")
342
+ return instance
343
+
344
+ except Exception as e:
345
+ logger.error(f"Error updating segment {instance.pk}: {e}")
346
+ raise serializers.ValidationError(str(e))
347
+
348
+ # --- Read/Representation Methods (Already existed) ---
349
+
350
+ def to_representation(self, instance) -> dict[str, Any]:
351
+ """Inject calculated seconds and IDs for frontend convenience."""
352
+ data = super().to_representation(instance)
353
+
354
+ video = instance.video_file
355
+ if video:
356
+ data["start_time"] = video.frame_number_to_s(instance.start_frame_number)
357
+ data["end_time"] = video.frame_number_to_s(instance.end_frame_number)
358
+ data["video_id"] = video.id
359
+
360
+ if instance.label:
361
+ data["label_name"] = instance.label.name
362
+ data["label_id"] = instance.label.id
363
+ else:
364
+ data["label_name"] = None
365
+ data["label_id"] = None
143
366
 
367
+ return data
144
368
 
145
369
  def get_time_segments(self, obj: LabelVideoSegment) -> dict[str, dict]:
146
- frames = obj.frames
370
+ annotations_prefetch = Prefetch(
371
+ "image_classification_annotations",
372
+ queryset=ImageClassificationAnnotation.objects.select_related("label"),
373
+ )
374
+ assert isinstance(obj, LabelVideoSegment)
375
+ assert isinstance(obj.frames, models.QuerySet)
376
+ frames = obj.frames.prefetch_related(annotations_prefetch)
147
377
  time_segments = {
148
- "segment_id": obj.id,
378
+ "segment_id": obj.pk,
149
379
  "segment_start": obj.start_frame_number,
150
380
  "segment_end": obj.end_frame_number,
151
381
  "start_time": obj.start_time,
152
382
  "end_time": obj.end_time,
153
- "frames": []
383
+ "frames": [],
154
384
  }
155
385
 
156
386
  request = self.context.get("request") if hasattr(self, "context") else None
157
387
 
158
388
  for frame in frames:
389
+ # Optimization: Use annotations if available to avoid N+1 queries
159
390
  all_classifications = ImageClassificationAnnotationSerializer(
160
391
  frame.image_classification_annotations.all(), many=True
161
392
  ).data
162
- predictions = ImageClassificationAnnotationSerializer(frame.predictions, many=True).data
163
- manual_annotations = ImageClassificationAnnotationSerializer(
164
- frame.manual_annotations, many=True
165
- ).data if frame.has_manual_annotations else []
166
393
 
167
- # 👇 changed here: no absolute server path; give a media-relative path + a URL
394
+ # Use safe helpers for paths
168
395
  rel = _media_relpath_from_file_path(frame.file_path)
169
396
  url = _media_url_from_file_path(frame.file_path, request=request)
170
397
 
171
398
  frame_data = {
172
399
  "frame_filename": Path(str(frame.file_path)).name,
173
- "frame_file_path": rel, # backwards-compatible, now relative
174
- "frame_url": url, # new: what the frontend should use
400
+ "frame_file_path": rel,
401
+ "frame_url": url,
175
402
  "all_classifications": all_classifications,
176
- "predictions": predictions,
177
- "frame_id": frame.id,
178
- "manual_annotations": manual_annotations
403
+ "frame_id": frame.pk,
179
404
  }
180
405
  time_segments["frames"].append(frame_data)
181
406
 
182
407
  return time_segments
183
408
 
184
- def get_label_name(self, obj):# -> Any | Literal['unknown']:
185
- """
186
- Return the name of the label associated with the segment, or "unknown" if no label is set.
187
- """
188
- if obj.label:
189
- return obj.label.name
190
- return "unknown"
191
-
192
- def get_manual_frame_annotations(self, obj:LabelVideoSegment):
193
- """
194
- Return serialized manual frame annotations for the given video segment.
195
-
196
- Parameters:
197
- obj (LabelVideoSegment): The video segment instance whose manual frame annotations are to be serialized.
198
-
199
- Returns:
200
- list: A list of serialized manual frame annotation data.
201
- """
202
- return ImageClassificationAnnotationSerializer(obj.manual_frame_annotations, many=True).data
203
-
204
- def get_frame_predictions(self, obj:LabelVideoSegment) -> List[dict]:
205
- """
206
- Return serialized frame prediction annotations for the given video segment.
207
-
208
- Parameters:
209
- obj (LabelVideoSegment): The video segment instance whose frame predictions are to be serialized.
210
-
211
- Returns:
212
- List[dict]: A list of serialized frame prediction annotation data.
213
- """
214
- return ImageClassificationAnnotationSerializer(obj.frame_predictions, many=True).data
215
-
216
- def get_all_annotations(self, obj:LabelVideoSegment):
217
- """
218
- Retrieve all image classification annotations for every frame in the given video segment.
219
-
220
- Parameters:
221
- obj (LabelVideoSegment): The video segment instance whose frame annotations are to be retrieved.
222
-
223
- Returns:
224
- list: A list of serialized image classification annotations for all frames in the segment.
225
- """
226
- return ImageClassificationAnnotationSerializer(obj.all_frame_annotations, many=True).data
409
+ def get_label_name(self, obj) -> Any | Literal["Unknown"]:
410
+ return obj.label.name if obj.label else "Unknown"
227
411
 
412
+ def get_manual_frame_annotations(self, obj: LabelVideoSegment) -> Dict[Any, Any]:
413
+ return ImageClassificationAnnotationSerializer(
414
+ obj.manual_frame_annotations, many=True
415
+ ).data
228
416
 
229
-
230
- def get_video_name(self, obj):
231
- """
232
- Return the display name of the video file associated with the segment.
233
-
234
- If the video file has an `original_file_name`, it is returned; otherwise, a fallback name using the video ID is provided. Returns "Unknown Video" if the video file is inaccessible.
235
- """
417
+ def get_frame_predictions(self, obj: LabelVideoSegment) -> Dict[Any, Any]:
418
+ return ImageClassificationAnnotationSerializer(
419
+ obj.frame_predictions, many=True
420
+ ).data
421
+
422
+ def get_video_name(self, obj) -> Any | str:
236
423
  try:
237
424
  video = obj.video_file
238
- return getattr(video, 'original_file_name', f'Video {video.id}')
425
+ return getattr(video, "original_file_name", f"Video {video.id}")
239
426
  except (AttributeError, ObjectDoesNotExist):
240
- return 'Unknown Video'
241
-
242
- def get_start_time(self, obj:LabelVideoSegment):
243
- """
244
- Return the start time of the video segment in seconds.
245
- """
246
- return obj.start_time
247
-
248
- def get_end_time(self, obj):
249
- """
250
- Return the end time of the video segment in seconds.
251
- """
252
- return obj.end_time
253
-
254
- def is_valid(self, raise_exception=False):
255
- """
256
- Validates the serializer input data and logs the validation process.
257
-
258
- Parameters:
259
- raise_exception (bool): If True, raises a ValidationError on failure.
260
-
261
- Returns:
262
- bool: True if the data is valid, False otherwise.
263
- """
264
- logger.debug("Starting validation")
265
- result = super().is_valid(raise_exception=raise_exception)
266
- if not result:
267
- logger.debug(f"Validation errors: {self.errors}")
268
- return result
269
-
270
- def to_internal_value(self, data):
271
- """
272
- Normalizes input data by mapping between `video_id`/`label_id` and `video_file`/`label` keys to ensure consistent internal representation for model creation and validation.
273
-
274
- This allows the serializer to accept either set of keys and internally synchronize them before further processing.
275
- """
276
- #TODO @coderabbitai create an issue for @Hamzaukw
277
- # we need to ensure consistent naming across serializers
278
- # Currently we use it in various places, but it should be consistent
279
- # Either make the usage consistent or create another serializer that handles this
280
- # Map video_file → video_id and label → label_id
281
- if 'video_file' in data:
282
- data['video_id'] = data['video_file']
283
- if 'label' in data:
284
- data['label_id'] = data['label']
285
- # Map video_id → video_file and label_id → label
286
- if 'video_id' in data:
287
- data['video_file'] = data['video_id']
288
- if 'label_id' in data:
289
- data['label'] = data['label_id']
290
- return super().to_internal_value(data)
291
-
292
- def to_representation(self, instance):
293
- """
294
- Return a JSON-compatible representation of a label video segment with calculated start and end times in seconds, label name, and explicit video and label IDs.
295
-
296
- Raises:
297
- ValueError: If the segment is not associated with a video file.
298
- TypeError: If the associated video file is not a VideoFile instance.
299
- """
300
- data = super().to_representation(instance)
301
- video_file = instance.video_file
302
- if video_file is None:
303
- raise ValueError("Video file must be associated with the segment")
304
- if not isinstance(video_file, VideoFile):
305
- raise TypeError("Expected video_file to be an instance of VideoFile")
306
- # Add calculated time fields for frontend compatibility
307
- data['start_time'] = video_file.frame_number_to_s(instance.start_frame_number)
308
- data['end_time'] = video_file.frame_number_to_s(instance.end_frame_number)
309
- # Ensure label_name is always present in response
310
- if instance.label:
311
- data['label_name'] = instance.label.name
312
- else:
313
- data['label_name'] = None
314
- # Explicitly add video_id and label_id to the output for frontend convenience
315
- data['video_id'] = instance.video_file.id if instance.video_file else None
316
- data['label_id'] = instance.label.id if instance.label else None
317
- return data
427
+ return "Unknown Video"
@@ -1,4 +1,3 @@
1
-
2
1
  from .sensitive_meta_detail import SensitiveMetaDetailSerializer
3
2
  from .sensitive_meta_update import SensitiveMetaUpdateSerializer
4
3
  from .sensitive_meta_verification import SensitiveMetaVerificationSerializer
@@ -11,4 +10,4 @@ __all__ = [
11
10
  "SensitiveMetaUpdateSerializer",
12
11
  "SensitiveMetaVerificationSerializer",
13
12
  "VideoMetaSerializer",
14
- ]
13
+ ]