endoreg-db 0.8.5.4__py3-none-any.whl → 0.8.5.6__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.

@@ -523,8 +523,13 @@ class VideoFile(models.Model):
523
523
  """
524
524
  Validate the metadata of the VideoFile instance.
525
525
 
526
- Called after annotation in the frontend, this method deletes the associated active file, updates the sensitive meta data with the user annotated data.
527
- It also ensures the video file is properly saved after the metadata update.
526
+ Called after annotation in the frontend, this method:
527
+ 1. Updates sensitive metadata with user-annotated data
528
+ 2. Deletes the RAW video file (keeping only the anonymized version)
529
+ 3. Marks the video as validated
530
+
531
+ **IMPORTANT:** Only the raw video is deleted. The processed (anonymized)
532
+ video is preserved as the final validated output.
528
533
  """
529
534
  from datetime import date as dt_date
530
535
 
@@ -541,9 +546,22 @@ class VideoFile(models.Model):
541
546
  }
542
547
  self.sensitive_meta = SensitiveMeta.create_from_dict(default_data)
543
548
 
544
- # Delete the active file to ensure it is reprocessed with the new metadata
545
- if self.active_file_path.exists():
546
- self.active_file_path.unlink(missing_ok=True)
549
+ # CRITICAL FIX: Delete RAW video file, not the processed (anonymized) one
550
+ # After validation, only the anonymized video should remain
551
+ from .video_file_io import _get_raw_file_path
552
+
553
+ raw_path = _get_raw_file_path(self)
554
+ if raw_path and raw_path.exists():
555
+ logger.info(f"Deleting raw video file after validation: {raw_path}")
556
+ raw_path.unlink(missing_ok=True)
557
+ # Clear the raw_file field in database (use delete() to avoid save issues)
558
+ if self.raw_file:
559
+ self.raw_file.delete(save=False)
560
+ logger.info(
561
+ f"Raw video deleted for {self.uuid}. Anonymized video preserved."
562
+ )
563
+ else:
564
+ logger.warning(f"Raw video file not found for deletion: {self.uuid}")
547
565
 
548
566
  # Update sensitive metadata with user annotations
549
567
  sensitive_meta = _update_text_metadata(
@@ -1,81 +1,60 @@
1
1
  from .administration import (
2
+ ActiveModelSerializer,
3
+ AiModelSerializer,
2
4
  CenterSerializer,
3
5
  GenderSerializer,
4
- ActiveModelSerializer,
5
6
  ModelTypeSerializer,
6
- AiModelSerializer
7
7
  )
8
-
9
8
  from .examination import (
9
+ ExaminationDropdownSerializer,
10
10
  ExaminationSerializer,
11
11
  ExaminationTypeSerializer,
12
- ExaminationDropdownSerializer
13
12
  )
14
-
15
13
  from .finding import FindingSerializer
16
-
17
14
  from .finding_classification import (
18
15
  FindingClassificationChoiceSerializer,
19
- FindingClassificationSerializer
16
+ FindingClassificationSerializer,
20
17
  )
21
-
22
- from .label import (
23
- LabelSerializer,
24
- ImageClassificationAnnotationSerializer
25
- )
26
-
18
+ from .label import ImageClassificationAnnotationSerializer, LabelSerializer
27
19
  from .label_video_segment import (
28
- LabelVideoSegmentSerializer,
29
20
  LabelVideoSegmentAnnotationSerializer,
21
+ LabelVideoSegmentSerializer,
30
22
  )
31
-
32
23
  from .meta import (
33
- ReportMetaSerializer,
34
24
  PDFFileForMetaSerializer,
25
+ ReportMetaSerializer,
35
26
  SensitiveMetaDetailSerializer,
36
27
  SensitiveMetaUpdateSerializer,
37
28
  SensitiveMetaVerificationSerializer,
38
29
  VideoMetaSerializer,
39
30
  )
40
-
41
31
  from .misc import (
42
32
  FileOverviewSerializer,
43
- VoPPatientDataSerializer,
44
33
  StatsSerializer,
45
- UploadJobStatusSerializer,
34
+ TranslatableFieldMixin,
46
35
  UploadCreateResponseSerializer,
47
- TranslatableFieldMixin
48
- )
49
-
50
- from .patient import (
51
- PatientSerializer,
52
- PatientDropdownSerializer,
53
- )
54
-
55
- from .patient_examination import (
56
- PatientExaminationSerializer,
36
+ UploadJobStatusSerializer,
37
+ VoPPatientDataSerializer,
57
38
  )
58
-
39
+ from .patient import PatientDropdownSerializer, PatientSerializer
40
+ from .patient_examination import PatientExaminationSerializer
59
41
  from .patient_finding import (
60
- PatientFindingSerializer,
61
42
  PatientFindingClassificationSerializer,
62
43
  PatientFindingDetailSerializer,
63
44
  PatientFindingInterventionSerializer,
64
45
  PatientFindingListSerializer,
46
+ PatientFindingSerializer,
65
47
  PatientFindingWriteSerializer,
66
48
  )
67
-
68
- from .pdf import (
69
- RawPdfAnonyTextSerializer
70
- )
71
- from .report import (
72
- ReportListSerializer,
73
- ReportDataSerializer,
74
- SecureFileUrlSerializer
75
- )
76
-
49
+ from .pdf import RawPdfAnonyTextSerializer
50
+ from .report import ReportDataSerializer, ReportListSerializer, SecureFileUrlSerializer
77
51
  from .video.video_metadata import VideoMetadataSerializer
78
52
  from .video.video_processing_history import VideoProcessingHistorySerializer
53
+ from .video_examination import (
54
+ VideoExaminationCreateSerializer,
55
+ VideoExaminationSerializer,
56
+ VideoExaminationUpdateSerializer,
57
+ )
79
58
 
80
59
  __all__ = [
81
60
  # Administration
@@ -84,24 +63,19 @@ __all__ = [
84
63
  "ActiveModelSerializer",
85
64
  "ModelTypeSerializer",
86
65
  "AiModelSerializer",
87
-
88
66
  # Examination
89
67
  "ExaminationSerializer",
90
68
  "ExaminationTypeSerializer",
91
69
  "ExaminationDropdownSerializer",
92
-
93
70
  # Finding
94
- 'FindingSerializer',
95
- 'FindingClassificationSerializer',
71
+ "FindingSerializer",
72
+ "FindingClassificationSerializer",
96
73
  "FindingClassificationChoiceSerializer",
97
-
98
74
  "LabelSerializer",
99
75
  "ImageClassificationAnnotationSerializer",
100
-
101
76
  # LabelVideoSegment
102
77
  "LabelVideoSegmentSerializer",
103
78
  "LabelVideoSegmentAnnotationSerializer",
104
-
105
79
  # Meta
106
80
  "PDFFileForMetaSerializer",
107
81
  "ReportMetaSerializer",
@@ -109,7 +83,6 @@ __all__ = [
109
83
  "SensitiveMetaUpdateSerializer",
110
84
  "SensitiveMetaVerificationSerializer",
111
85
  "VideoMetaSerializer",
112
-
113
86
  # Misc
114
87
  "FileOverviewSerializer",
115
88
  "VoPPatientDataSerializer",
@@ -117,14 +90,11 @@ __all__ = [
117
90
  "UploadJobStatusSerializer",
118
91
  "UploadCreateResponseSerializer",
119
92
  "TranslatableFieldMixin",
120
-
121
93
  # Patient
122
94
  "PatientSerializer",
123
95
  "PatientDropdownSerializer",
124
-
125
96
  # Patient Examination
126
97
  "PatientExaminationSerializer",
127
-
128
98
  # Patient Finding
129
99
  "PatientFindingSerializer",
130
100
  "PatientFindingClassificationSerializer",
@@ -132,16 +102,17 @@ __all__ = [
132
102
  "PatientFindingInterventionSerializer",
133
103
  "PatientFindingListSerializer",
134
104
  "PatientFindingWriteSerializer",
135
-
136
105
  # PDF
137
106
  "RawPdfAnonyTextSerializer",
138
-
139
107
  # Report
140
108
  "ReportListSerializer",
141
109
  "ReportDataSerializer",
142
110
  "SecureFileUrlSerializer",
143
-
144
111
  # Video Correction (Phase 1.1)
145
112
  "VideoMetadataSerializer",
146
113
  "VideoProcessingHistorySerializer",
114
+ # Video Examination
115
+ "VideoExaminationSerializer",
116
+ "VideoExaminationCreateSerializer",
117
+ "VideoExaminationUpdateSerializer",
147
118
  ]
@@ -0,0 +1,198 @@
1
+ """
2
+ Video Examination Serializer
3
+
4
+ Serializes PatientExamination instances that are associated with VideoFile records.
5
+ This allows frontend components like VideoExaminationAnnotation.vue to display
6
+ and manage examinations within the video annotation workflow.
7
+ """
8
+
9
+ from rest_framework import serializers
10
+
11
+ from ..models import Examination, PatientExamination, VideoFile
12
+
13
+
14
+ class VideoExaminationSerializer(serializers.ModelSerializer):
15
+ """
16
+ Serializer for video-based patient examinations.
17
+
18
+ Exposes examination data within the context of video annotation:
19
+ - Basic examination metadata (type, date, hash)
20
+ - Related patient information (anonymized)
21
+ - Video reference
22
+ - Associated findings
23
+ """
24
+
25
+ # Custom fields for frontend compatibility
26
+ examination_name = serializers.CharField(source="examination.name", read_only=True)
27
+ examination_id = serializers.IntegerField(source="examination.id", read_only=True)
28
+ video_id = serializers.IntegerField(source="video.id", read_only=True)
29
+ patient_hash = serializers.CharField(source="patient.patient_hash", read_only=True)
30
+
31
+ # Nested findings data
32
+ findings = serializers.SerializerMethodField()
33
+
34
+ class Meta:
35
+ model = PatientExamination
36
+ fields = [
37
+ "id",
38
+ "hash",
39
+ "examination_id",
40
+ "examination_name",
41
+ "video_id",
42
+ "patient_hash",
43
+ "date_start",
44
+ "date_end",
45
+ "findings",
46
+ ]
47
+ read_only_fields = ["hash", "patient_hash"]
48
+
49
+ def get_findings(self, obj):
50
+ """
51
+ Return serialized findings associated with this examination.
52
+
53
+ Args:
54
+ obj: PatientExamination instance
55
+
56
+ Returns:
57
+ List of finding dictionaries with basic metadata
58
+ """
59
+ patient_findings = obj.patient_findings.all()
60
+ return [
61
+ {
62
+ "id": pf.id,
63
+ "finding_id": pf.finding.id if pf.finding else None,
64
+ "finding_name": pf.finding.name if pf.finding else None,
65
+ "created_at": pf.created_at if hasattr(pf, "created_at") else None,
66
+ }
67
+ for pf in patient_findings
68
+ ]
69
+
70
+
71
+ class VideoExaminationCreateSerializer(serializers.Serializer):
72
+ """
73
+ Serializer for creating video examinations via API.
74
+
75
+ Handles the complex creation logic required to link:
76
+ - VideoFile (must exist)
77
+ - Examination type (must exist)
78
+ - Patient (derived from video's SensitiveMeta)
79
+ - New PatientExamination record
80
+ """
81
+
82
+ video_id = serializers.IntegerField(required=True)
83
+ examination_id = serializers.IntegerField(required=True)
84
+ date_start = serializers.DateField(required=False, allow_null=True)
85
+ date_end = serializers.DateField(required=False, allow_null=True)
86
+
87
+ def validate_video_id(self, value):
88
+ """Ensure video exists"""
89
+ if not VideoFile.objects.filter(id=value).exists():
90
+ raise serializers.ValidationError(f"Video with id {value} does not exist")
91
+ return value
92
+
93
+ def validate_examination_id(self, value):
94
+ """Ensure examination type exists"""
95
+ if not Examination.objects.filter(id=value).exists():
96
+ raise serializers.ValidationError(
97
+ f"Examination with id {value} does not exist"
98
+ )
99
+ return value
100
+
101
+ def create(self, validated_data):
102
+ """
103
+ Create PatientExamination record.
104
+
105
+ Links video to examination through patient relationship:
106
+ 1. Get video and extract patient from SensitiveMeta
107
+ 2. Get examination type
108
+ 3. Create PatientExamination linking patient, examination, video
109
+
110
+ Raises:
111
+ ValidationError: If video has no patient or sensitive_meta
112
+ """
113
+ video = VideoFile.objects.get(id=validated_data["video_id"])
114
+ examination = Examination.objects.get(id=validated_data["examination_id"])
115
+
116
+ # Get patient from video's sensitive metadata
117
+ if not hasattr(video, "sensitive_meta") or not video.sensitive_meta:
118
+ raise serializers.ValidationError(
119
+ "Video must have sensitive metadata with patient information"
120
+ )
121
+
122
+ sensitive_meta = video.sensitive_meta
123
+ if not sensitive_meta.pseudo_patient:
124
+ raise serializers.ValidationError(
125
+ "Video's sensitive metadata must have an associated pseudo patient"
126
+ )
127
+
128
+ patient = sensitive_meta.pseudo_patient
129
+
130
+ # Check if PatientExamination already exists for this video
131
+ existing_exam = PatientExamination.objects.filter(video=video).first()
132
+ if existing_exam:
133
+ # Update existing
134
+ patient_exam = existing_exam
135
+ patient_exam.examination = examination
136
+ if "date_start" in validated_data:
137
+ patient_exam.date_start = validated_data["date_start"]
138
+ if "date_end" in validated_data:
139
+ patient_exam.date_end = validated_data["date_end"]
140
+ patient_exam.save()
141
+ else:
142
+ # Create new
143
+ patient_exam = PatientExamination.objects.create(
144
+ patient=patient,
145
+ examination=examination,
146
+ video=video,
147
+ date_start=validated_data.get("date_start"),
148
+ date_end=validated_data.get("date_end"),
149
+ )
150
+
151
+ return patient_exam
152
+
153
+
154
+ class VideoExaminationUpdateSerializer(serializers.Serializer):
155
+ """
156
+ Serializer for updating video examinations.
157
+
158
+ Allows modification of:
159
+ - Examination type
160
+ - Date range
161
+ - Associated findings (via separate endpoint)
162
+ """
163
+
164
+ examination_id = serializers.IntegerField(required=False)
165
+ date_start = serializers.DateField(required=False, allow_null=True)
166
+ date_end = serializers.DateField(required=False, allow_null=True)
167
+
168
+ def validate_examination_id(self, value):
169
+ """Ensure examination type exists if provided"""
170
+ if value is not None and not Examination.objects.filter(id=value).exists():
171
+ raise serializers.ValidationError(
172
+ f"Examination with id {value} does not exist"
173
+ )
174
+ return value
175
+
176
+ def update(self, instance, validated_data):
177
+ """
178
+ Update PatientExamination fields.
179
+
180
+ Args:
181
+ instance: Existing PatientExamination
182
+ validated_data: Validated update data
183
+
184
+ Returns:
185
+ Updated PatientExamination instance
186
+ """
187
+ if "examination_id" in validated_data:
188
+ examination = Examination.objects.get(id=validated_data["examination_id"])
189
+ instance.examination = examination
190
+
191
+ if "date_start" in validated_data:
192
+ instance.date_start = validated_data["date_start"]
193
+
194
+ if "date_end" in validated_data:
195
+ instance.date_end = validated_data["date_end"]
196
+
197
+ instance.save()
198
+ return instance