endoreg-db 0.6.1__py3-none-any.whl → 0.6.3__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.
- endoreg_db/data/distribution/numeric/data.yaml +1 -1
- endoreg_db/mermaid/Overall_flow_patient_finding_intervention.md +10 -0
- endoreg_db/mermaid/anonymized_image_annotation.md +20 -0
- endoreg_db/mermaid/binary_classification_annotation.md +50 -0
- endoreg_db/mermaid/classification.md +8 -0
- endoreg_db/mermaid/examination.md +8 -0
- endoreg_db/mermaid/findings.md +7 -0
- endoreg_db/mermaid/image_classification.md +28 -0
- endoreg_db/mermaid/interventions.md +8 -0
- endoreg_db/mermaid/morphology.md +8 -0
- endoreg_db/mermaid/patient_creation.md +14 -0
- endoreg_db/mermaid/video_segmentation_annotation.md +17 -0
- endoreg_db/models/ai_model/ai_model.py +1 -1
- endoreg_db/models/ai_model/model_meta.py +1 -1
- endoreg_db/models/data_file/base_classes/utils.py +1 -1
- endoreg_db/serializers/raw_pdf_anony_text_validation.py +137 -0
- endoreg_db/serializers/raw_pdf_meta_validation.py +223 -0
- endoreg_db/serializers/raw_video_meta_validation.py +163 -1
- endoreg_db/serializers/video_segmentation.py +207 -112
- endoreg_db/urls.py +110 -11
- endoreg_db/views/raw_pdf_anony_text_validation_views.py +95 -0
- endoreg_db/views/raw_pdf_meta_validation_views.py +111 -0
- endoreg_db/views/raw_video_meta_validation_views.py +128 -18
- endoreg_db/views/video_segmentation_views.py +28 -11
- {endoreg_db-0.6.1.dist-info → endoreg_db-0.6.3.dist-info}/METADATA +11 -1
- {endoreg_db-0.6.1.dist-info → endoreg_db-0.6.3.dist-info}/RECORD +28 -13
- {endoreg_db-0.6.1.dist-info → endoreg_db-0.6.3.dist-info}/WHEEL +0 -0
- {endoreg_db-0.6.1.dist-info → endoreg_db-0.6.3.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
|
-
|
|
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 = [
|
|
45
|
-
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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,15 +104,16 @@ 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,
|
|
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(
|
|
100
|
-
|
|
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
|
|
101
114
|
|
|
102
|
-
|
|
103
|
-
happens because MP4 format requires seeking, but FFmpeg does not support writing MP4 directly to a non-seekable stream (like STDOUT).
|
|
115
|
+
"""The error "muxer does not support non-seekable output"
|
|
116
|
+
happens because MP4 format requires seeking, but FFmpeg does not support writing MP4 directly to a non-seekable stream (like STDOUT)."""
|
|
104
117
|
|
|
105
118
|
def get_full_video_path(self, obj):
|
|
106
119
|
"""
|
|
@@ -109,39 +122,30 @@ class VideoFileSerializer(serializers.ModelSerializer):
|
|
|
109
122
|
"""
|
|
110
123
|
if not obj.file:
|
|
111
124
|
return {"error": "No video file associated with this entry"}
|
|
112
|
-
|
|
125
|
+
|
|
113
126
|
video_relative_path = str(obj.file.name).strip() # Convert FieldFile to string
|
|
114
127
|
if not video_relative_path:
|
|
115
|
-
return {
|
|
116
|
-
|
|
128
|
+
return {
|
|
129
|
+
"error": "Video file path is empty or invalid"
|
|
130
|
+
} # none might cause, 500 error, Handle edge case where the file name is empty
|
|
131
|
+
|
|
117
132
|
print("-----------------------------------------")
|
|
118
|
-
|
|
119
|
-
#print(f"Using pseudo directory: {pseudo_dir}")
|
|
133
|
+
# pseudo_dir = settings.PSEUDO_DIR
|
|
134
|
+
# print(f"Using pseudo directory: {pseudo_dir}")
|
|
120
135
|
|
|
121
136
|
# full path using the actual storage directory~
|
|
122
|
-
#actual_storage_dir = Path("~/test-data") # need to change
|
|
137
|
+
# actual_storage_dir = Path("~/test-data") # need to change
|
|
123
138
|
actual_storage_dir = Path("/home/admin/test-data") # need to change
|
|
124
|
-
#actual_storage_dir = pseudo_dir
|
|
139
|
+
# actual_storage_dir = pseudo_dir
|
|
125
140
|
full_path = actual_storage_dir / video_relative_path
|
|
126
|
-
#full_path = Path("/home/admin/test-data/video/lux-gastro-video.mp4")
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
141
|
+
# full_path = Path("/home/admin/test-data/video/lux-gastro-video.mp4")
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
str(full_path)
|
|
145
|
+
if full_path.exists()
|
|
146
|
+
else {"error": f"file not found at: {full_path}"}
|
|
147
|
+
)
|
|
148
|
+
|
|
145
149
|
def get_sequences(self, obj):
|
|
146
150
|
"""
|
|
147
151
|
Extracts the sequences field from the RawVideoFile model.
|
|
@@ -152,8 +156,10 @@ class VideoFileSerializer(serializers.ModelSerializer):
|
|
|
152
156
|
"kolonpolyp": [[91, 126]]
|
|
153
157
|
}
|
|
154
158
|
"""
|
|
155
|
-
return obj.sequences or {
|
|
156
|
-
|
|
159
|
+
return obj.sequences or {
|
|
160
|
+
"error": "no sequence found, check database first"
|
|
161
|
+
} # Get from sequences, return {} if missing
|
|
162
|
+
|
|
157
163
|
def get_label_names(self, obj):
|
|
158
164
|
"""
|
|
159
165
|
Extracts only label names from the sequences data.
|
|
@@ -162,7 +168,6 @@ class VideoFileSerializer(serializers.ModelSerializer):
|
|
|
162
168
|
"""
|
|
163
169
|
sequences = self.get_sequences(obj)
|
|
164
170
|
return list(sequences.keys()) if sequences else []
|
|
165
|
-
|
|
166
171
|
|
|
167
172
|
def get_label_time_segments(self, obj):
|
|
168
173
|
"""
|
|
@@ -176,9 +181,15 @@ class VideoFileSerializer(serializers.ModelSerializer):
|
|
|
176
181
|
- segment_start and segment_end (in frame index format, not divided by FPS)
|
|
177
182
|
"""
|
|
178
183
|
|
|
179
|
-
fps =
|
|
184
|
+
fps = (
|
|
185
|
+
obj.fps
|
|
186
|
+
if hasattr(obj, "fps") and obj.fps is not None
|
|
187
|
+
else obj.get_fps()
|
|
188
|
+
if hasattr(obj, "get_fps") and obj.get_fps() is not None
|
|
189
|
+
else 50
|
|
190
|
+
)
|
|
180
191
|
|
|
181
|
-
print("here is fps::::::::::::::::::.-----------::::::",fps)
|
|
192
|
+
print("here is fps::::::::::::::::::.-----------::::::", fps)
|
|
182
193
|
sequences = self.get_sequences(obj) # Fetch sequence data
|
|
183
194
|
readable_predictions = obj.readable_predictions # Predictions from DB
|
|
184
195
|
|
|
@@ -205,51 +216,64 @@ class VideoFileSerializer(serializers.ModelSerializer):
|
|
|
205
216
|
|
|
206
217
|
# Fetch predictions for frames within this range
|
|
207
218
|
for frame_num in range(start_frame, end_frame + 1):
|
|
208
|
-
if
|
|
219
|
+
if (
|
|
220
|
+
0 <= frame_num < len(readable_predictions)
|
|
221
|
+
): # Ensure index is valid
|
|
209
222
|
frame_filename = f"frame_{str(frame_num).zfill(7)}.jpg" # Frame filename format
|
|
210
|
-
frame_path =
|
|
223
|
+
frame_path = (
|
|
224
|
+
frame_dir / frame_filename
|
|
225
|
+
) # Full path to the frame
|
|
211
226
|
|
|
212
227
|
frame_data[frame_num] = {
|
|
213
228
|
"frame_filename": frame_filename,
|
|
214
229
|
"frame_file_path": str(frame_path),
|
|
215
|
-
"predictions": readable_predictions[frame_num]
|
|
230
|
+
"predictions": readable_predictions[frame_num],
|
|
216
231
|
}
|
|
217
232
|
|
|
218
233
|
# Store frame-wise predictions in frame_predictions
|
|
219
234
|
frame_predictions[frame_num] = readable_predictions[frame_num]
|
|
220
235
|
|
|
221
236
|
# Append the converted time segment
|
|
222
|
-
label_times.append(
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
237
|
+
label_times.append(
|
|
238
|
+
{
|
|
239
|
+
"segment_start": start_frame, # Raw start frame (not divided by FPS)
|
|
240
|
+
"segment_end": end_frame, # Raw end frame (not divided by FPS)
|
|
241
|
+
"start_time": round(
|
|
242
|
+
start_time, 2
|
|
243
|
+
), # Converted start time in seconds
|
|
244
|
+
"end_time": round(end_time, 2), # Converted end time in seconds
|
|
245
|
+
"frames": frame_data, # Attach frame details
|
|
246
|
+
}
|
|
247
|
+
)
|
|
229
248
|
|
|
230
249
|
# Store time segments and frame_predictions under the label
|
|
231
250
|
time_segments[label] = {
|
|
232
251
|
"time_ranges": label_times,
|
|
233
|
-
"frame_predictions": frame_predictions # Ensure frame_predictions is correctly assigned
|
|
252
|
+
"frame_predictions": frame_predictions, # Ensure frame_predictions is correctly assigned
|
|
234
253
|
}
|
|
235
254
|
|
|
236
255
|
return time_segments
|
|
237
256
|
|
|
257
|
+
|
|
238
258
|
class VideoListSerializer(serializers.ModelSerializer):
|
|
239
259
|
"""
|
|
240
260
|
Minimal serializer to return only `id` and `original_file_name`
|
|
241
261
|
for the video selection dropdown in Vue.js.
|
|
242
262
|
"""
|
|
263
|
+
|
|
243
264
|
class Meta:
|
|
244
265
|
model = RawVideoFile
|
|
245
|
-
fields = [
|
|
246
|
-
|
|
266
|
+
fields = ["id", "original_file_name"] # Only fetch required fields
|
|
247
267
|
|
|
248
268
|
|
|
249
269
|
from pathlib import Path
|
|
250
270
|
from rest_framework import serializers
|
|
251
271
|
from django.http import FileResponse, Http404, StreamingHttpResponse
|
|
252
|
-
from ..models import
|
|
272
|
+
from ..models import (
|
|
273
|
+
RawVideoFile,
|
|
274
|
+
Label,
|
|
275
|
+
LabelRawVideoSegment,
|
|
276
|
+
) # Importing necessary models
|
|
253
277
|
import subprocess
|
|
254
278
|
from django.conf import settings
|
|
255
279
|
from django.db.models import Q # Import Q for better querying
|
|
@@ -260,24 +284,33 @@ class LabelSerializer(serializers.ModelSerializer):
|
|
|
260
284
|
Serializer for fetching labels from the `endoreg_db_label` table.
|
|
261
285
|
Includes `id` (for backend processing) and `name` (for dropdown display in Vue.js).
|
|
262
286
|
"""
|
|
287
|
+
|
|
263
288
|
class Meta:
|
|
264
289
|
model = Label
|
|
265
|
-
fields = [
|
|
290
|
+
fields = ["id", "name"]
|
|
266
291
|
|
|
267
292
|
|
|
268
293
|
class LabelSegmentSerializer(serializers.ModelSerializer):
|
|
269
294
|
"""
|
|
270
295
|
Serializer for retrieving label segments from `endoreg_db_labelrawvideosegment`.
|
|
271
296
|
"""
|
|
297
|
+
|
|
272
298
|
class Meta:
|
|
273
299
|
model = LabelRawVideoSegment
|
|
274
|
-
fields = [
|
|
300
|
+
fields = [
|
|
301
|
+
"id",
|
|
302
|
+
"video_id",
|
|
303
|
+
"label_id",
|
|
304
|
+
"start_frame_number",
|
|
305
|
+
"end_frame_number",
|
|
306
|
+
]
|
|
275
307
|
|
|
276
308
|
|
|
277
309
|
from django.db import transaction
|
|
278
310
|
|
|
279
311
|
from django.db import transaction
|
|
280
312
|
|
|
313
|
+
|
|
281
314
|
class LabelSegmentUpdateSerializer(serializers.Serializer):
|
|
282
315
|
"""
|
|
283
316
|
Serializer for updating label segments.
|
|
@@ -308,10 +341,14 @@ class LabelSegmentUpdateSerializer(serializers.Serializer):
|
|
|
308
341
|
|
|
309
342
|
for segment in data["segments"]:
|
|
310
343
|
if "start_frame_number" not in segment or "end_frame_number" not in segment:
|
|
311
|
-
raise serializers.ValidationError(
|
|
344
|
+
raise serializers.ValidationError(
|
|
345
|
+
"Each segment must have `start_frame_number` and `end_frame_number`."
|
|
346
|
+
)
|
|
312
347
|
|
|
313
348
|
if segment["start_frame_number"] > segment["end_frame_number"]:
|
|
314
|
-
raise serializers.ValidationError(
|
|
349
|
+
raise serializers.ValidationError(
|
|
350
|
+
"Start frame must be less than or equal to end frame."
|
|
351
|
+
)
|
|
315
352
|
|
|
316
353
|
return data
|
|
317
354
|
|
|
@@ -331,20 +368,58 @@ class LabelSegmentUpdateSerializer(serializers.Serializer):
|
|
|
331
368
|
label_id = self.validated_data["label_id"]
|
|
332
369
|
new_segments = self.validated_data["segments"]
|
|
333
370
|
|
|
334
|
-
# Fetch
|
|
335
|
-
|
|
371
|
+
# Fetch the correct `prediction_meta_id` based on `video_id`
|
|
372
|
+
prediction_meta_entry = RawVideoPredictionMeta.objects.filter(
|
|
373
|
+
video_id=video_id
|
|
374
|
+
).first()
|
|
375
|
+
if not prediction_meta_entry:
|
|
376
|
+
raise serializers.ValidationError(
|
|
377
|
+
{"error": "No prediction metadata found for this video."}
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
prediction_meta_id = (
|
|
381
|
+
prediction_meta_entry.id
|
|
382
|
+
) # Get the correct prediction_meta_id
|
|
383
|
+
|
|
384
|
+
existing_segments = LabelRawVideoSegment.objects.filter(
|
|
385
|
+
video_id=video_id, label_id=label_id
|
|
386
|
+
)
|
|
336
387
|
|
|
337
388
|
# Convert existing segments into a dictionary for quick lookup
|
|
338
389
|
# Key format: (start_frame_number, end_frame_number)
|
|
339
|
-
existing_segments_dict = {
|
|
390
|
+
existing_segments_dict = {
|
|
391
|
+
(float(seg.start_frame_number), float(seg.end_frame_number)): seg
|
|
392
|
+
for seg in existing_segments
|
|
393
|
+
}
|
|
394
|
+
existing_segments_dict = {
|
|
395
|
+
(float(seg.start_frame_number), float(seg.end_frame_number)): seg
|
|
396
|
+
for seg in existing_segments
|
|
397
|
+
}
|
|
340
398
|
|
|
341
399
|
# Prepare lists for batch processing
|
|
342
400
|
updated_segments = [] # Stores segments that need to be updated
|
|
343
401
|
new_entries = [] # Stores segments that need to be created
|
|
344
|
-
existing_keys = set(
|
|
345
|
-
|
|
402
|
+
existing_keys = set(
|
|
403
|
+
existing_segments_dict.keys()
|
|
404
|
+
) # Existing database segment keys
|
|
405
|
+
new_keys = set(
|
|
406
|
+
(float(seg["start_frame_number"]), float(seg["end_frame_number"]))
|
|
407
|
+
for seg in new_segments
|
|
408
|
+
) # New frontend segment keys
|
|
346
409
|
|
|
347
410
|
# Start a transaction to ensure database consistency
|
|
411
|
+
updated_segments = []
|
|
412
|
+
new_entries = []
|
|
413
|
+
existing_keys = set(existing_segments_dict.keys())
|
|
414
|
+
new_keys = set(
|
|
415
|
+
(float(seg["start_frame_number"]), float(seg["end_frame_number"]))
|
|
416
|
+
for seg in new_segments
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
print(f" Before Update: Found {existing_segments.count()} existing segments.")
|
|
420
|
+
print(f" New Segments Received: {len(new_segments)}")
|
|
421
|
+
print(f" Using prediction_meta_id: {prediction_meta_id}")
|
|
422
|
+
|
|
348
423
|
with transaction.atomic():
|
|
349
424
|
for segment in new_segments:
|
|
350
425
|
start_frame = float(segment["start_frame_number"])
|
|
@@ -358,7 +433,7 @@ class LabelSegmentUpdateSerializer(serializers.Serializer):
|
|
|
358
433
|
existing_segment = LabelRawVideoSegment.objects.filter(
|
|
359
434
|
video_id=video_id,
|
|
360
435
|
label_id=label_id,
|
|
361
|
-
start_frame_number=start_frame
|
|
436
|
+
start_frame_number=start_frame,
|
|
362
437
|
).first()
|
|
363
438
|
|
|
364
439
|
if existing_segment:
|
|
@@ -369,16 +444,32 @@ class LabelSegmentUpdateSerializer(serializers.Serializer):
|
|
|
369
444
|
updated_segments.append(existing_segment)
|
|
370
445
|
else:
|
|
371
446
|
# If no existing segment matches, create a new one
|
|
372
|
-
new_entries.append(
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
447
|
+
new_entries.append(
|
|
448
|
+
LabelRawVideoSegment(
|
|
449
|
+
video_id=video_id,
|
|
450
|
+
label_id=label_id,
|
|
451
|
+
start_frame_number=start_frame,
|
|
452
|
+
end_frame_number=end_frame,
|
|
453
|
+
)
|
|
454
|
+
)
|
|
455
|
+
print(
|
|
456
|
+
f" Adding new segment: Start {start_frame} → End {end_frame}"
|
|
457
|
+
)
|
|
458
|
+
new_entries.append(
|
|
459
|
+
LabelRawVideoSegment(
|
|
460
|
+
video_id=video_id,
|
|
461
|
+
label_id=label_id,
|
|
462
|
+
start_frame_number=start_frame,
|
|
463
|
+
end_frame_number=end_frame,
|
|
464
|
+
prediction_meta_id=prediction_meta_id, # Assign correct prediction_meta_id
|
|
465
|
+
)
|
|
466
|
+
)
|
|
378
467
|
|
|
379
468
|
# Delete segments that are no longer present in the frontend data
|
|
380
469
|
segments_to_delete = existing_segments.exclude(
|
|
381
|
-
start_frame_number__in=[
|
|
470
|
+
start_frame_number__in=[
|
|
471
|
+
float(seg["start_frame_number"]) for seg in new_segments
|
|
472
|
+
]
|
|
382
473
|
)
|
|
383
474
|
deleted_count = segments_to_delete.count()
|
|
384
475
|
segments_to_delete.delete()
|
|
@@ -388,24 +479,32 @@ class LabelSegmentUpdateSerializer(serializers.Serializer):
|
|
|
388
479
|
LabelRawVideoSegment.objects.bulk_create(new_entries)
|
|
389
480
|
|
|
390
481
|
# Return the updated, new, and deleted segment information
|
|
391
|
-
print(
|
|
482
|
+
print(
|
|
483
|
+
"------------------------------,",
|
|
484
|
+
updated_segments,
|
|
485
|
+
"-----------------------",
|
|
486
|
+
new_segments,
|
|
487
|
+
"_-------",
|
|
488
|
+
deleted_count,
|
|
489
|
+
)
|
|
490
|
+
print(
|
|
491
|
+
f" After Update: Updated {len(updated_segments)} segments, Added {len(new_entries)}, Deleted {deleted_count}"
|
|
492
|
+
)
|
|
493
|
+
|
|
392
494
|
return {
|
|
393
|
-
"updated_segments": LabelSegmentSerializer(
|
|
495
|
+
"updated_segments": LabelSegmentSerializer(
|
|
496
|
+
updated_segments, many=True
|
|
497
|
+
).data,
|
|
394
498
|
"new_segments": LabelSegmentSerializer(new_entries, many=True).data,
|
|
395
|
-
"deleted_segments": deleted_count
|
|
499
|
+
"deleted_segments": deleted_count,
|
|
396
500
|
}
|
|
397
501
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
502
|
# Use StreamingHttpResponse to stream FFmpeg output to browser
|
|
405
|
-
|
|
503
|
+
# return StreamingHttpResponse(subprocess.Popen(ffmpeg_command, stdout=subprocess.PIPE).stdout, content_type="video/mp4")
|
|
504
|
+
|
|
505
|
+
# Ensure the path exists before returning
|
|
506
|
+
# return str(full_path) if full_path.exists() else None
|
|
406
507
|
|
|
407
|
-
# Ensure the path exists before returning
|
|
408
|
-
# return str(full_path) if full_path.exists() else None
|
|
409
508
|
|
|
410
509
|
''' def get_classification_data(self, obj):
|
|
411
510
|
"""
|
|
@@ -437,10 +536,6 @@ class LabelSegmentUpdateSerializer(serializers.Serializer):
|
|
|
437
536
|
return classifications'''
|
|
438
537
|
|
|
439
538
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
539
|
"""
|
|
445
540
|
await import('https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js');
|
|
446
541
|
const videoId = 1;
|
|
@@ -489,4 +584,4 @@ axios.put(`http://localhost:8000/api/video/${videoIdUpdate}/label/${labelIdUpdat
|
|
|
489
584
|
.catch(error => {
|
|
490
585
|
console.error(" Error Updating Segments:", error.response ? error.response.data : error);
|
|
491
586
|
});
|
|
492
|
-
"""
|
|
587
|
+
""" ""
|