endoreg-db 0.6.2__py3-none-any.whl → 0.6.4__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 (97) hide show
  1. endoreg_db/data/__init__.py +14 -0
  2. endoreg_db/data/disease_classification/chronic_kidney_disease.yaml +2 -2
  3. endoreg_db/data/disease_classification_choice/chronic_kidney_disease.yaml +6 -6
  4. endoreg_db/data/distribution/numeric/data.yaml +1 -1
  5. endoreg_db/data/examination/examinations/data.yaml +22 -21
  6. endoreg_db/data/examination/type/data.yaml +12 -0
  7. endoreg_db/data/examination_indication/endoscopy.yaml +417 -1
  8. endoreg_db/data/examination_indication_classification/endoscopy.yaml +157 -5
  9. endoreg_db/data/finding/data.yaml +18 -11
  10. endoreg_db/data/finding_intervention/endoscopy.yaml +26 -121
  11. endoreg_db/data/finding_intervention/endoscopy_colonoscopy.yaml +163 -0
  12. endoreg_db/data/finding_intervention/endoscopy_egd.yaml +128 -0
  13. endoreg_db/data/finding_intervention/endoscopy_ercp.yaml +32 -0
  14. endoreg_db/data/finding_intervention/endoscopy_eus_lower.yaml +9 -0
  15. endoreg_db/data/finding_intervention/endoscopy_eus_upper.yaml +36 -0
  16. endoreg_db/data/information_source/endoscopy_guidelines.yaml +7 -0
  17. endoreg_db/data/medication_indication/anticoagulation.yaml +4 -4
  18. endoreg_db/data/pdf_type/data.yaml +9 -16
  19. endoreg_db/data/requirement/colonoscopy_indications.yaml +56 -0
  20. endoreg_db/data/requirement/disease_cardiovascular.yaml +79 -0
  21. endoreg_db/data/requirement/disease_classification_choice_cardiovascular.yaml +38 -0
  22. endoreg_db/data/requirement/disease_hepatology.yaml +12 -0
  23. endoreg_db/data/requirement/disease_misc.yaml +12 -0
  24. endoreg_db/data/requirement/disease_renal.yaml +80 -0
  25. endoreg_db/data/requirement/event_cardiology.yaml +251 -0
  26. endoreg_db/data/requirement/lab_value.yaml +120 -0
  27. endoreg_db/data/requirement_operator/lab_operators.yaml +128 -0
  28. endoreg_db/data/requirement_operator/model_operators.yaml +90 -0
  29. endoreg_db/data/requirement_set/endoscopy_bleeding_risk.yaml +12 -0
  30. endoreg_db/data/requirement_set_type/data.yaml +20 -0
  31. endoreg_db/data/requirement_type/requirement_types.yaml +83 -0
  32. endoreg_db/data/risk/bleeding.yaml +26 -0
  33. endoreg_db/data/risk/thrombosis.yaml +37 -0
  34. endoreg_db/data/risk_type/data.yaml +27 -0
  35. endoreg_db/data/unit/time.yaml +36 -1
  36. endoreg_db/management/commands/load_base_db_data.py +14 -1
  37. endoreg_db/management/commands/load_center_data.py +46 -21
  38. endoreg_db/management/commands/load_examination_indication_data.py +49 -27
  39. endoreg_db/management/commands/load_requirement_data.py +156 -0
  40. endoreg_db/management/commands/load_risk_data.py +56 -0
  41. endoreg_db/mermaid/Overall_flow_patient_finding_intervention.md +10 -0
  42. endoreg_db/mermaid/anonymized_image_annotation.md +20 -0
  43. endoreg_db/mermaid/binary_classification_annotation.md +50 -0
  44. endoreg_db/mermaid/classification.md +8 -0
  45. endoreg_db/mermaid/examination.md +8 -0
  46. endoreg_db/mermaid/findings.md +7 -0
  47. endoreg_db/mermaid/image_classification.md +28 -0
  48. endoreg_db/mermaid/interventions.md +8 -0
  49. endoreg_db/mermaid/morphology.md +8 -0
  50. endoreg_db/mermaid/patient_creation.md +14 -0
  51. endoreg_db/mermaid/video_segmentation_annotation.md +17 -0
  52. endoreg_db/migrations/0009_requirementoperator_requirementsettype_and_more.py +154 -0
  53. endoreg_db/models/__init__.py +20 -0
  54. endoreg_db/models/ai_model/ai_model.py +0 -13
  55. endoreg_db/models/ai_model/model_meta.py +2 -12
  56. endoreg_db/models/data_file/base_classes/abstract_frame.py +0 -2
  57. endoreg_db/models/data_file/base_classes/abstract_pdf.py +0 -9
  58. endoreg_db/models/data_file/base_classes/abstract_video.py +7 -8
  59. endoreg_db/models/data_file/base_classes/utils.py +0 -22
  60. endoreg_db/models/data_file/frame.py +1 -1
  61. endoreg_db/models/data_file/import_classes/raw_pdf.py +5 -11
  62. endoreg_db/models/data_file/import_classes/raw_video.py +6 -4
  63. endoreg_db/models/data_file/video/video.py +3 -3
  64. endoreg_db/models/disease.py +88 -19
  65. endoreg_db/models/event.py +108 -21
  66. endoreg_db/models/examination/examination_indication.py +108 -29
  67. endoreg_db/models/examination/examination_type.py +20 -6
  68. endoreg_db/models/information_source.py +37 -1
  69. endoreg_db/models/laboratory/lab_value.py +83 -32
  70. endoreg_db/models/requirement/__init__.py +11 -0
  71. endoreg_db/models/requirement/requirement.py +325 -0
  72. endoreg_db/models/requirement/requirement_evaluation/__init__.py +134 -0
  73. endoreg_db/models/requirement/requirement_evaluation/requirement_type_parser.py +102 -0
  74. endoreg_db/models/requirement/requirement_operator.py +58 -0
  75. endoreg_db/models/requirement/requirement_set.py +127 -0
  76. endoreg_db/models/risk/__init__.py +7 -0
  77. endoreg_db/models/risk/risk.py +72 -0
  78. endoreg_db/models/risk/risk_type.py +55 -0
  79. endoreg_db/serializers/raw_pdf_anony_text_validation.py +137 -0
  80. endoreg_db/serializers/raw_pdf_meta_validation.py +223 -0
  81. endoreg_db/serializers/raw_video_meta_validation.py +163 -1
  82. endoreg_db/serializers/video_segmentation.py +208 -126
  83. endoreg_db/urls.py +127 -14
  84. endoreg_db/utils/__init__.py +43 -0
  85. endoreg_db/utils/dataloader.py +38 -19
  86. endoreg_db/utils/hashs.py +1 -0
  87. endoreg_db/utils/paths.py +86 -0
  88. endoreg_db/views/raw_pdf_anony_text_validation_views.py +95 -0
  89. endoreg_db/views/raw_pdf_meta_validation_views.py +111 -0
  90. endoreg_db/views/raw_video_meta_validation_views.py +128 -18
  91. endoreg_db/views/video_segmentation_views.py +28 -11
  92. endoreg_db/views/views.py +107 -0
  93. {endoreg_db-0.6.2.dist-info → endoreg_db-0.6.4.dist-info}/METADATA +1 -1
  94. {endoreg_db-0.6.2.dist-info → endoreg_db-0.6.4.dist-info}/RECORD +96 -46
  95. endoreg_db/management/commands/load_name_data.py +0 -37
  96. {endoreg_db-0.6.2.dist-info → endoreg_db-0.6.4.dist-info}/WHEEL +0 -0
  97. {endoreg_db-0.6.2.dist-info → endoreg_db-0.6.4.dist-info}/licenses/LICENSE +0 -0
@@ -1,22 +1,19 @@
1
1
  from pathlib import Path
2
2
  from rest_framework import serializers
3
- from django.http import FileResponse, Http404,StreamingHttpResponse
4
- from ..models import RawVideoFile,Label
3
+ from django.http import FileResponse, Http404, StreamingHttpResponse
4
+ from ..models import RawVideoFile, Label, LabelRawVideoSegment, RawVideoPredictionMeta
5
5
  import subprocess, cv2
6
6
  from django.conf import settings
7
7
 
8
8
 
9
-
10
9
  class VideoFileSerializer(serializers.ModelSerializer):
11
-
12
-
13
10
  """
14
11
  Serializer that dynamically handles video retrieval and streaming.
15
12
  Ensures file returns the relative file path (not MEDIA_URL)
16
13
  Computes full_video_path using the correct storage path (/home/admin/test-data)-need to change make it dynamic
17
14
  Returns video_url for frontend integration
18
15
  Serves the video file when needed
19
-
16
+
20
17
  """
21
18
 
22
19
  video_url = serializers.SerializerMethodField()
@@ -24,53 +21,67 @@ class VideoFileSerializer(serializers.ModelSerializer):
24
21
  file = serializers.SerializerMethodField() # Override file to remove incorrect MEDIA_URL behavior,otherwise:Django's FileField automatically generates a URL based on MEDIA_URL
25
22
  # Video dropdown field for frontend selection (currently shows video ID, but can be changed later)
26
23
  video_selection_field = serializers.SerializerMethodField()
27
- #classification_data = serializers.SerializerMethodField() #data from database (smooth prediction values but currently hardcoded one)
28
- #The Meta class tells Django what data to include when serializing a RawVideoFile object.
24
+ # classification_data = serializers.SerializerMethodField() #data from database (smooth prediction values but currently hardcoded one)
25
+ # The Meta class tells Django what data to include when serializing a RawVideoFile object.
29
26
  sequences = serializers.SerializerMethodField()
30
27
  label_names = serializers.SerializerMethodField()
31
- # Convert selected label frames into time segments (seconds)
28
+ # Convert selected label frames into time segments (seconds)
32
29
  label_time_segments = serializers.SerializerMethodField()
33
- #label_predictions = serializers.SerializerMethodField()
30
+ # label_predictions = serializers.SerializerMethodField()
34
31
  original_file_name = serializers.CharField()
35
32
  duration = serializers.SerializerMethodField()
36
33
 
37
-
38
-
39
-
40
-
41
34
  class Meta:
42
35
  model = RawVideoFile
43
- #he fields list defines which data should be included in the API response.
44
- fields = ['id','original_file_name', 'file','duration', 'video_url', 'full_video_path','video_selection_field','label_names','sequences','label_time_segments'] # Ensure computed fields are included
45
- #@staticmethod #using @staticmethod makes it reusable without needing to create a serializer instance.
36
+ # he fields list defines which data should be included in the API response.
37
+ fields = [
38
+ "id",
39
+ "original_file_name",
40
+ "file",
41
+ "duration",
42
+ "video_url",
43
+ "full_video_path",
44
+ "video_selection_field",
45
+ "label_names",
46
+ "sequences",
47
+ "label_time_segments",
48
+ ] # Ensure computed fields are included
49
+
50
+ # @staticmethod #using @staticmethod makes it reusable without needing to create a serializer instance.
46
51
  # Without @staticmethod, you would need to instantiate the serializer before calling the method, which is unnecessary her
47
- def get_video_selection_field(self,obj):
52
+ def get_video_selection_field(self, obj):
48
53
  """
49
54
  Returns the field used for video selection in the frontend dropdown.
50
55
  Currently, it shows the video ID, but this can be changed easily later.
51
56
  """
52
57
  return obj.id
53
-
54
- def get_video_url(self, obj): # when we serialize a RawVideoFile object (video metadata), the get_video_url method is automatically invoked by DRF
58
+
59
+ def get_video_url(
60
+ self, obj
61
+ ): # when we serialize a RawVideoFile object (video metadata), the get_video_url method is automatically invoked by DRF
55
62
  """
56
63
  Returns the API endpoint where the frontend can fetch the video.
57
64
  """
58
65
  if not obj.id:
59
66
  return {"error": "Invalid video ID"}
60
-
61
- request= self.context.get('request') #Gets the request object (provided by DRF).
67
+
68
+ request = self.context.get(
69
+ "request"
70
+ ) # Gets the request object (provided by DRF).
62
71
  if request:
63
72
  return request.build_absolute_uri(f"/api/video/{obj.id}/")
64
-
73
+
65
74
  return {"error": "Video URL not avalaible"}
66
-
75
+
67
76
  def get_duration(self, obj):
68
77
  """
69
78
  Returns the total duration of the video in seconds.
70
79
  If duration is not stored in the database, it extracts it dynamically using OpenCV.
71
80
  """
72
81
  if hasattr(obj, "duration") and obj.duration:
73
- return obj.duration # If duration is stored in the database, return it directly.
82
+ return (
83
+ obj.duration
84
+ ) # If duration is stored in the database, return it directly.
74
85
 
75
86
  # Dynamically extract duration if not stored
76
87
  video_path = obj.file.path
@@ -83,8 +94,9 @@ class VideoFileSerializer(serializers.ModelSerializer):
83
94
  total_frames = cap.get(cv2.CAP_PROP_FRAME_COUNT)
84
95
  cap.release()
85
96
 
86
- return round(total_frames / fps, 2) if fps > 0 else None # Return duration in seconds
87
-
97
+ return (
98
+ round(total_frames / fps, 2) if fps > 0 else None
99
+ ) # Return duration in seconds
88
100
 
89
101
  def get_file(self, obj):
90
102
  """
@@ -92,56 +104,37 @@ class VideoFileSerializer(serializers.ModelSerializer):
92
104
  """
93
105
  if not obj.file:
94
106
  return {"error": "No file associated with this entry"}
95
- #obj.file.name is an attribute of FieldFile that returns the file path as a string and name is not the database attribute, it is an attribute of Django’s FieldFile object that holds the file path as a string.
96
- if not hasattr(obj.file,'name') or not obj.file.name.strip():
107
+ # obj.file.name is an attribute of FieldFile that returns the file path as a string and name is not the database attribute, it is an attribute of Django’s FieldFile object that holds the file path as a string.
108
+ if not hasattr(obj.file, "name") or not obj.file.name.strip():
97
109
  return {"error": "Invalid file name"}
98
110
 
99
- return str(obj.file.name).strip() # Only return the file path, no URL,#obj.file returning a FieldFile object instead of a string
100
-
101
-
102
- '''The error "muxer does not support non-seekable output"
103
- happens because MP4 format requires seeking, but FFmpeg does not support writing MP4 directly to a non-seekable stream (like STDOUT).'''
111
+ return str(
112
+ obj.file.name
113
+ ).strip() # Only return the file path, no URL,#obj.file returning a FieldFile object instead of a string
104
114
 
105
115
  def get_full_video_path(self, obj):
106
116
  """
107
117
  Constructs the absolute file path dynamically.
108
118
  - Uses the actual storage directory (`/home/admin/test-data/`)
109
119
  """
120
+ from ..utils import STORAGE_DIR
110
121
  if not obj.file:
111
122
  return {"error": "No video file associated with this entry"}
112
-
123
+
113
124
  video_relative_path = str(obj.file.name).strip() # Convert FieldFile to string
114
125
  if not video_relative_path:
115
- return {"error":"Video file path is empty or invalid"} # none might cause, 500 error, Handle edge case where the file name is empty
116
-
117
- print("-----------------------------------------")
118
- # pseudo_dir = settings.PSEUDO_DIR
119
- #print(f"Using pseudo directory: {pseudo_dir}")
120
-
121
- # full path using the actual storage directory~
122
- #actual_storage_dir = Path("~/test-data") # need to change
123
- actual_storage_dir = Path("/home/admin/test-data") # need to change
124
- #actual_storage_dir = pseudo_dir
125
- full_path = actual_storage_dir / video_relative_path
126
- #full_path = Path("/home/admin/test-data/video/lux-gastro-video.mp4")
127
-
128
-
129
- return str(full_path) if full_path.exists() else {"error":f"file not found at: {full_path}"}
130
-
131
- '''
132
- ffmpeg_command = [
133
- "ffmpeg", "-i", str(full_path), # Input video
134
- "-vf", "drawtext=text='OUTSIDE':fontcolor=white:fontsize=24:x=(w-text_w)/2:y=30:enable='lt(t,10)'",
135
- "-c:v", "libx264", "-preset", "ultrafast", # Encode quickly for streaming
136
- "-f", "mp4", "-" # Output to STDOUT (no file saving)
137
- ]'''
138
- '''ffmpeg_command = [
139
- "ffmpeg", "-i", str(full_path), # Input video
140
- "-vf", "drawtext=text='OUTSIDE':fontcolor=white:fontsize=24:x=(w-text_w)/2:y=30:enable='lt(t,10)'",
141
- "-c:v", "libx264", "-preset", "ultrafast",
142
- "-f", "mpegts", "pipe:1" # Output as MPEG-TS to STDOUT
143
- ]'''
144
-
126
+ return {
127
+ "error": "Video file path is empty or invalid"
128
+ } # none might cause, 500 error, Handle edge case where the file name is empty
129
+
130
+ full_path = STORAGE_DIR / video_relative_path
131
+
132
+ return (
133
+ str(full_path)
134
+ if full_path.exists()
135
+ else {"error": f"file not found at: {full_path}"}
136
+ )
137
+
145
138
  def get_sequences(self, obj):
146
139
  """
147
140
  Extracts the sequences field from the RawVideoFile model.
@@ -152,8 +145,10 @@ class VideoFileSerializer(serializers.ModelSerializer):
152
145
  "kolonpolyp": [[91, 126]]
153
146
  }
154
147
  """
155
- return obj.sequences or {"error":"no sequence found, check database first"} # Get from sequences, return {} if missing
156
-
148
+ return obj.sequences or {
149
+ "error": "no sequence found, check database first"
150
+ } # Get from sequences, return {} if missing
151
+
157
152
  def get_label_names(self, obj):
158
153
  """
159
154
  Extracts only label names from the sequences data.
@@ -162,7 +157,6 @@ class VideoFileSerializer(serializers.ModelSerializer):
162
157
  """
163
158
  sequences = self.get_sequences(obj)
164
159
  return list(sequences.keys()) if sequences else []
165
-
166
160
 
167
161
  def get_label_time_segments(self, obj):
168
162
  """
@@ -176,9 +170,15 @@ class VideoFileSerializer(serializers.ModelSerializer):
176
170
  - segment_start and segment_end (in frame index format, not divided by FPS)
177
171
  """
178
172
 
179
- fps = obj.fps if hasattr(obj, "fps") and obj.fps is not None else obj.get_fps() if hasattr(obj, "get_fps") and obj.get_fps() is not None else 50
173
+ fps = (
174
+ obj.fps
175
+ if hasattr(obj, "fps") and obj.fps is not None
176
+ else obj.get_fps()
177
+ if hasattr(obj, "get_fps") and obj.get_fps() is not None
178
+ else 50
179
+ )
180
180
 
181
- print("here is fps::::::::::::::::::.-----------::::::",fps)
181
+ print("here is fps::::::::::::::::::.-----------::::::", fps)
182
182
  sequences = self.get_sequences(obj) # Fetch sequence data
183
183
  readable_predictions = obj.readable_predictions # Predictions from DB
184
184
 
@@ -205,51 +205,64 @@ class VideoFileSerializer(serializers.ModelSerializer):
205
205
 
206
206
  # Fetch predictions for frames within this range
207
207
  for frame_num in range(start_frame, end_frame + 1):
208
- if 0 <= frame_num < len(readable_predictions): # Ensure index is valid
208
+ if (
209
+ 0 <= frame_num < len(readable_predictions)
210
+ ): # Ensure index is valid
209
211
  frame_filename = f"frame_{str(frame_num).zfill(7)}.jpg" # Frame filename format
210
- frame_path = frame_dir / frame_filename # Full path to the frame
212
+ frame_path = (
213
+ frame_dir / frame_filename
214
+ ) # Full path to the frame
211
215
 
212
216
  frame_data[frame_num] = {
213
217
  "frame_filename": frame_filename,
214
218
  "frame_file_path": str(frame_path),
215
- "predictions": readable_predictions[frame_num]
219
+ "predictions": readable_predictions[frame_num],
216
220
  }
217
221
 
218
222
  # Store frame-wise predictions in frame_predictions
219
223
  frame_predictions[frame_num] = readable_predictions[frame_num]
220
224
 
221
225
  # Append the converted time segment
222
- label_times.append({
223
- "segment_start": start_frame, # Raw start frame (not divided by FPS)
224
- "segment_end": end_frame, # Raw end frame (not divided by FPS)
225
- "start_time": round(start_time, 2), # Converted start time in seconds
226
- "end_time": round(end_time, 2), # Converted end time in seconds
227
- "frames": frame_data # Attach frame details
228
- })
226
+ label_times.append(
227
+ {
228
+ "segment_start": start_frame, # Raw start frame (not divided by FPS)
229
+ "segment_end": end_frame, # Raw end frame (not divided by FPS)
230
+ "start_time": round(
231
+ start_time, 2
232
+ ), # Converted start time in seconds
233
+ "end_time": round(end_time, 2), # Converted end time in seconds
234
+ "frames": frame_data, # Attach frame details
235
+ }
236
+ )
229
237
 
230
238
  # Store time segments and frame_predictions under the label
231
239
  time_segments[label] = {
232
240
  "time_ranges": label_times,
233
- "frame_predictions": frame_predictions # Ensure frame_predictions is correctly assigned
241
+ "frame_predictions": frame_predictions, # Ensure frame_predictions is correctly assigned
234
242
  }
235
243
 
236
244
  return time_segments
237
245
 
246
+
238
247
  class VideoListSerializer(serializers.ModelSerializer):
239
248
  """
240
249
  Minimal serializer to return only `id` and `original_file_name`
241
250
  for the video selection dropdown in Vue.js.
242
251
  """
252
+
243
253
  class Meta:
244
254
  model = RawVideoFile
245
- fields = ['id', 'original_file_name'] # Only fetch required fields
246
-
255
+ fields = ["id", "original_file_name"] # Only fetch required fields
247
256
 
248
257
 
249
258
  from pathlib import Path
250
259
  from rest_framework import serializers
251
260
  from django.http import FileResponse, Http404, StreamingHttpResponse
252
- from ..models import RawVideoFile, Label, LabelRawVideoSegment # Importing necessary models
261
+ from ..models import (
262
+ RawVideoFile,
263
+ Label,
264
+ LabelRawVideoSegment,
265
+ ) # Importing necessary models
253
266
  import subprocess
254
267
  from django.conf import settings
255
268
  from django.db.models import Q # Import Q for better querying
@@ -260,24 +273,33 @@ class LabelSerializer(serializers.ModelSerializer):
260
273
  Serializer for fetching labels from the `endoreg_db_label` table.
261
274
  Includes `id` (for backend processing) and `name` (for dropdown display in Vue.js).
262
275
  """
276
+
263
277
  class Meta:
264
278
  model = Label
265
- fields = ['id', 'name']
279
+ fields = ["id", "name"]
266
280
 
267
281
 
268
282
  class LabelSegmentSerializer(serializers.ModelSerializer):
269
283
  """
270
284
  Serializer for retrieving label segments from `endoreg_db_labelrawvideosegment`.
271
285
  """
286
+
272
287
  class Meta:
273
288
  model = LabelRawVideoSegment
274
- fields = ['id', 'video_id', 'label_id', 'start_frame_number', 'end_frame_number']
289
+ fields = [
290
+ "id",
291
+ "video_id",
292
+ "label_id",
293
+ "start_frame_number",
294
+ "end_frame_number",
295
+ ]
275
296
 
276
297
 
277
298
  from django.db import transaction
278
299
 
279
300
  from django.db import transaction
280
301
 
302
+
281
303
  class LabelSegmentUpdateSerializer(serializers.Serializer):
282
304
  """
283
305
  Serializer for updating label segments.
@@ -308,43 +330,83 @@ class LabelSegmentUpdateSerializer(serializers.Serializer):
308
330
 
309
331
  for segment in data["segments"]:
310
332
  if "start_frame_number" not in segment or "end_frame_number" not in segment:
311
- raise serializers.ValidationError("Each segment must have `start_frame_number` and `end_frame_number`.")
333
+ raise serializers.ValidationError(
334
+ "Each segment must have `start_frame_number` and `end_frame_number`."
335
+ )
312
336
 
313
337
  if segment["start_frame_number"] > segment["end_frame_number"]:
314
- raise serializers.ValidationError("Start frame must be less than or equal to end frame.")
338
+ raise serializers.ValidationError(
339
+ "Start frame must be less than or equal to end frame."
340
+ )
315
341
 
316
342
  return data
317
343
 
318
344
  def save(self):
319
345
  """
320
- Updates, inserts, and deletes label segments to ensure database consistency.
321
-
322
- Steps:
323
- 1. Fetch all existing segments for the given `video_id` and `label_id`.
324
- 2. Compare existing segments with the new ones from the frontend.
325
- 3. Update segments where `start_frame_number` exists but `end_frame_number` has changed.
326
- 4. Insert new segments that are not already in the database.
327
- 5. Delete segments that exist in the database but are missing from the frontend data.
346
+ Synchronizes label segments with updated frontend data.
347
+
348
+ This method compares the incoming segments with the current database entries for a given video and label.
349
+ It updates segments with modified end frame numbers, inserts new segments, and deletes existing segments
350
+ that are not present in the provided data. All operations are performed within a transaction to ensure
351
+ database consistency. A validation error is raised if no prediction metadata is found for the video.
352
+
353
+ Returns:
354
+ dict: A dictionary containing serialized updated segments, serialized new segments, and the count
355
+ of deleted segments.
328
356
  """
329
357
 
330
358
  video_id = self.validated_data["video_id"]
331
359
  label_id = self.validated_data["label_id"]
332
360
  new_segments = self.validated_data["segments"]
333
361
 
334
- # Fetch all existing segments for this video and label from the database
335
- existing_segments = LabelRawVideoSegment.objects.filter(video_id=video_id, label_id=label_id)
362
+ # Fetch the correct `prediction_meta_id` based on `video_id`
363
+ prediction_meta_entry = RawVideoPredictionMeta.objects.filter(
364
+ video_id=video_id
365
+ ).first()
366
+ if not prediction_meta_entry:
367
+ raise serializers.ValidationError(
368
+ {"error": "No prediction metadata found for this video."}
369
+ )
370
+
371
+ prediction_meta_id = (
372
+ prediction_meta_entry.id
373
+ ) # Get the correct prediction_meta_id
374
+
375
+ existing_segments = LabelRawVideoSegment.objects.filter(
376
+ video_id=video_id, label_id=label_id
377
+ )
336
378
 
337
379
  # Convert existing segments into a dictionary for quick lookup
338
380
  # Key format: (start_frame_number, end_frame_number)
339
- existing_segments_dict = {(float(seg.start_frame_number), float(seg.end_frame_number)): seg for seg in existing_segments}
381
+ existing_segments_dict = {
382
+ (float(seg.start_frame_number), float(seg.end_frame_number)): seg
383
+ for seg in existing_segments
384
+ }
340
385
 
341
386
  # Prepare lists for batch processing
342
387
  updated_segments = [] # Stores segments that need to be updated
343
388
  new_entries = [] # Stores segments that need to be created
344
- existing_keys = set(existing_segments_dict.keys()) # Existing database segment keys
345
- new_keys = set((float(seg["start_frame_number"]), float(seg["end_frame_number"])) for seg in new_segments) # New frontend segment keys
389
+ existing_keys = set(
390
+ existing_segments_dict.keys()
391
+ ) # Existing database segment keys
392
+ new_keys = set(
393
+ (float(seg["start_frame_number"]), float(seg["end_frame_number"]))
394
+ for seg in new_segments
395
+ ) # New frontend segment keys
346
396
 
347
397
  # Start a transaction to ensure database consistency
398
+ updated_segments = []
399
+ new_entries = []
400
+ existing_keys = set(existing_segments_dict.keys())
401
+ new_keys = set(
402
+ (float(seg["start_frame_number"]), float(seg["end_frame_number"]))
403
+ for seg in new_segments
404
+ )
405
+
406
+ print(f" Before Update: Found {existing_segments.count()} existing segments.")
407
+ print(f" New Segments Received: {len(new_segments)}")
408
+ print(f" Using prediction_meta_id: {prediction_meta_id}")
409
+
348
410
  with transaction.atomic():
349
411
  for segment in new_segments:
350
412
  start_frame = float(segment["start_frame_number"])
@@ -358,7 +420,7 @@ class LabelSegmentUpdateSerializer(serializers.Serializer):
358
420
  existing_segment = LabelRawVideoSegment.objects.filter(
359
421
  video_id=video_id,
360
422
  label_id=label_id,
361
- start_frame_number=start_frame
423
+ start_frame_number=start_frame,
362
424
  ).first()
363
425
 
364
426
  if existing_segment:
@@ -369,16 +431,32 @@ class LabelSegmentUpdateSerializer(serializers.Serializer):
369
431
  updated_segments.append(existing_segment)
370
432
  else:
371
433
  # If no existing segment matches, create a new one
372
- new_entries.append(LabelRawVideoSegment(
373
- video_id=video_id,
374
- label_id=label_id,
375
- start_frame_number=start_frame,
376
- end_frame_number=end_frame
377
- ))
434
+ new_entries.append(
435
+ LabelRawVideoSegment(
436
+ video_id=video_id,
437
+ label_id=label_id,
438
+ start_frame_number=start_frame,
439
+ end_frame_number=end_frame,
440
+ )
441
+ )
442
+ print(
443
+ f" Adding new segment: Start {start_frame} → End {end_frame}"
444
+ )
445
+ new_entries.append(
446
+ LabelRawVideoSegment(
447
+ video_id=video_id,
448
+ label_id=label_id,
449
+ start_frame_number=start_frame,
450
+ end_frame_number=end_frame,
451
+ prediction_meta_id=prediction_meta_id, # Assign correct prediction_meta_id
452
+ )
453
+ )
378
454
 
379
455
  # Delete segments that are no longer present in the frontend data
380
456
  segments_to_delete = existing_segments.exclude(
381
- start_frame_number__in=[float(seg["start_frame_number"]) for seg in new_segments]
457
+ start_frame_number__in=[
458
+ float(seg["start_frame_number"]) for seg in new_segments
459
+ ]
382
460
  )
383
461
  deleted_count = segments_to_delete.count()
384
462
  segments_to_delete.delete()
@@ -388,24 +466,32 @@ class LabelSegmentUpdateSerializer(serializers.Serializer):
388
466
  LabelRawVideoSegment.objects.bulk_create(new_entries)
389
467
 
390
468
  # Return the updated, new, and deleted segment information
391
- print("------------------------------,",updated_segments,"-----------------------",new_segments,"_-------",deleted_count)
469
+ print(
470
+ "------------------------------,",
471
+ updated_segments,
472
+ "-----------------------",
473
+ new_segments,
474
+ "_-------",
475
+ deleted_count,
476
+ )
477
+ print(
478
+ f" After Update: Updated {len(updated_segments)} segments, Added {len(new_entries)}, Deleted {deleted_count}"
479
+ )
480
+
392
481
  return {
393
- "updated_segments": LabelSegmentSerializer(updated_segments, many=True).data,
482
+ "updated_segments": LabelSegmentSerializer(
483
+ updated_segments, many=True
484
+ ).data,
394
485
  "new_segments": LabelSegmentSerializer(new_entries, many=True).data,
395
- "deleted_segments": deleted_count
486
+ "deleted_segments": deleted_count,
396
487
  }
397
488
 
398
-
399
-
400
-
401
-
402
-
403
-
404
489
  # Use StreamingHttpResponse to stream FFmpeg output to browser
405
- #return StreamingHttpResponse(subprocess.Popen(ffmpeg_command, stdout=subprocess.PIPE).stdout, content_type="video/mp4")
490
+ # return StreamingHttpResponse(subprocess.Popen(ffmpeg_command, stdout=subprocess.PIPE).stdout, content_type="video/mp4")
491
+
492
+ # Ensure the path exists before returning
493
+ # return str(full_path) if full_path.exists() else None
406
494
 
407
- # Ensure the path exists before returning
408
- # return str(full_path) if full_path.exists() else None
409
495
 
410
496
  ''' def get_classification_data(self, obj):
411
497
  """
@@ -437,10 +523,6 @@ class LabelSegmentUpdateSerializer(serializers.Serializer):
437
523
  return classifications'''
438
524
 
439
525
 
440
-
441
-
442
-
443
-
444
526
  """
445
527
  await import('https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js');
446
528
  const videoId = 1;
@@ -489,4 +571,4 @@ axios.put(`http://localhost:8000/api/video/${videoIdUpdate}/label/${labelIdUpdat
489
571
  .catch(error => {
490
572
  console.error(" Error Updating Segments:", error.response ? error.response.data : error);
491
573
  });
492
- """''
574
+ """ ""