@@ -123,7 +123,7 @@ def assign_occurrences_from_detection_chains(source_images, logger):
123
123
Walk detection chains across source images and assign a new occurrence to each chain.
124
124
"""
125
125
visited = set ()
126
-
126
+ created_occurrences_count = 0
127
127
for image in source_images :
128
128
for det in image .detections .all ():
129
129
if det .id in visited or getattr (det , "previous_detection" , None ) is not None :
@@ -143,34 +143,39 @@ def assign_occurrences_from_detection_chains(source_images, logger):
143
143
for occ_id in old_occurrences :
144
144
try :
145
145
Occurrence .objects .filter (id = occ_id ).delete ()
146
- logger .info (f"Deleted old occurrence { occ_id } before reassignment." )
146
+ logger .debug (f"Deleted old occurrence { occ_id } before reassignment." )
147
147
except Exception as e :
148
- logger .warning (f"Failed to delete occurrence { occ_id } : { e } " )
148
+ logger .info (f"Failed to delete occurrence { occ_id } : { e } " )
149
149
150
150
occurrence = Occurrence .objects .create (
151
151
event = chain [0 ].source_image .event ,
152
152
deployment = chain [0 ].source_image .deployment ,
153
153
project = chain [0 ].source_image .project ,
154
154
)
155
+ created_occurrences_count += 1
155
156
156
157
for d in chain :
157
158
d .occurrence = occurrence
158
159
d .save ()
159
160
160
161
occurrence .save ()
161
162
162
- logger .info (f"Assigned occurrence { occurrence .pk } to chain of { len (chain )} detections" )
163
+ logger .debug (f"Assigned occurrence { occurrence .pk } to chain of { len (chain )} detections" )
164
+ logger .info (
165
+ f"Assigned { created_occurrences_count } occurrences from detection chains across { len (source_images )} images."
166
+ )
163
167
164
168
165
169
def assign_occurrences_by_tracking_images (
166
- source_images ,
167
- logger ,
168
- cost_threshold : float = TRACKING_COST_THRESHOLD ,
170
+ event , logger , cost_threshold : float = TRACKING_COST_THRESHOLD , job = None
169
171
) -> None :
170
172
"""
171
173
Track detections across ordered source images and assign them to occurrences.
172
174
"""
173
- logger .info (f"Starting occurrence tracking over { len (source_images )} images" )
175
+ from ami .jobs .models import JobState
176
+
177
+ source_images = event .captures .order_by ("timestamp" )
178
+ logger .info (f"Found { len (source_images )} source images for event { event .pk } " )
174
179
if len (source_images ) < 2 :
175
180
logger .info ("Not enough images to perform tracking. At least 2 images are required." )
176
181
return
@@ -181,13 +186,10 @@ def assign_occurrences_by_tracking_images(
181
186
current_detections = list (current_image .detections .all ())
182
187
next_detections = list (next_image .detections .all ())
183
188
184
- logger .info (
185
- f"""Tracking: Processing image { i + 1 } /{ len (source_images )} :
186
- { len (current_detections )} -> { len (next_detections )} detections"""
187
- )
189
+ logger .debug (f"""Tracking: Processing image { i + 1 } /{ len (source_images )} """ )
188
190
# Get the most common algorithm for the current event
189
191
most_common_algorithm = get_most_common_algorithm_for_event (current_image .event )
190
- logger .info (
192
+ logger .debug (
191
193
f"""Using most common algorithm for event { current_image .event .pk } :
192
194
{ most_common_algorithm .name if most_common_algorithm else 'None' } """
193
195
)
@@ -201,8 +203,21 @@ def assign_occurrences_by_tracking_images(
201
203
algorithm = most_common_algorithm ,
202
204
logger = logger ,
203
205
)
206
+ if job :
207
+ job .progress .update_stage (
208
+ f"event_{ event .pk } " ,
209
+ status = JobState .STARTED ,
210
+ progress = (i + 1 ) / (len (source_images ) - 1 ),
211
+ )
212
+ job .save ()
204
213
205
214
assign_occurrences_from_detection_chains (source_images , logger )
215
+ if job :
216
+ job .progress .update_stage (
217
+ f"event_{ event .pk } " ,
218
+ progress = 1.0 ,
219
+ )
220
+ job .save ()
206
221
207
222
208
223
def pair_detections (
@@ -220,7 +235,7 @@ def pair_detections(
220
235
221
236
Only pairs with cost < threshold are considered.
222
237
"""
223
- logger .info (f"Pairing { len (current_detections )} - >{ len (next_detections )} detections" )
238
+ logger .debug (f"Pairing { len (current_detections )} - >{ len (next_detections )} detections" )
224
239
225
240
potential_matches = []
226
241
@@ -258,16 +273,16 @@ def pair_detections(
258
273
continue
259
274
# check if next detection has a previous detection already assigned
260
275
if getattr (next_det , "previous_detection" , None ) is not None :
261
- logger .info (f"{ next_det .id } already has previous detection: { next_det .previous_detection .id } " )
276
+ logger .debug (f"{ next_det .id } already has previous detection: { next_det .previous_detection .id } " )
262
277
previous_detection = getattr (next_det , "previous_detection" , None )
263
278
previous_detection .next_detection = None
264
279
previous_detection .save ()
265
- logger .info (f"Cleared previous detection { previous_detection .pk } -> { next_det .pk } link" )
280
+ logger .debug (f"Cleared previous detection { previous_detection .pk } -> { next_det .pk } link" )
266
281
267
- logger .info (f"Trying to link { det .id } => { next_det .id } " )
282
+ logger .debug (f"Trying to link { det .id } => { next_det .id } " )
268
283
det .next_detection = next_det
269
284
det .save ()
270
- logger .info (f"Linked detection { det .id } => { next_det .id } with cost { cost :.4f} " )
285
+ logger .debug (f"Linked detection { det .id } => { next_det .id } with cost { cost :.4f} " )
271
286
272
287
assigned_current_ids .add (det .id )
273
288
assigned_next_ids .add (next_det .id )
@@ -278,27 +293,25 @@ def perform_tracking(job):
278
293
Perform detection tracking for all events in the job's source image collection.
279
294
Runs tracking only if all images in an event have processed detections with features.
280
295
"""
281
- from ami .jobs .models import JobState
282
296
283
297
cost_threshold = job .params .get ("cost_threshold" , TRACKING_COST_THRESHOLD )
284
298
job .logger .info ("Tracking started" )
285
299
job .logger .info (f"Using cost threshold: { cost_threshold } " )
286
- job .progress .update_stage ("tracking" , status = JobState .STARTED , progress = 0 )
287
- job .save ()
288
- job .logger .info ("Progresss updated and job saved" )
289
300
collection = job .source_image_collection
290
301
if not collection :
291
- job .logger .warning ("Tracking: No source image collection found. Skipping tracking." )
302
+ job .logger .info ("Tracking: No source image collection found. Skipping tracking." )
292
303
return
293
304
job .logger .info ("Tracking: Fetching events for collection %s" , collection .pk )
294
- events_qs = Event .objects .filter (captures__collections = collection ).distinct ()
305
+ events_qs = Event .objects .filter (captures__collections = collection ).order_by ( "created_at" ). distinct ()
295
306
total_events = events_qs .count ()
296
307
events = events_qs .iterator ()
297
- processed_events = 0
298
308
job .logger .info ("Tracking: Found %d events in collection %s" , total_events , collection .pk )
299
- for event in events :
300
- job .logger .info ("Tracking: Processing event %s" , event .pk )
301
- source_images = event .captures .order_by ("timestamp" )
309
+ for event in events_qs :
310
+ job .progress .add_stage (name = f"Event { event .pk } " , key = f"event_{ event .pk } " )
311
+ job .save ()
312
+ for idx , event in enumerate (events , start = 1 ):
313
+ job .logger .info (f"Tracking: Processing event { idx } /{ total_events } (Event ID: { event .pk } )" )
314
+
302
315
# Check if there are human identifications in the event
303
316
if Occurrence .objects .filter (event = event , identifications__isnull = False ).exists ():
304
317
job .logger .info (f"Tracking: Skipping tracking for event { event .pk } : human identifications present." )
@@ -311,16 +324,7 @@ def perform_tracking(job):
311
324
continue
312
325
313
326
job .logger .info (f"Tracking: Running tracking for event { event .pk } " )
314
- assign_occurrences_by_tracking_images (source_images , job .logger , cost_threshold = cost_threshold )
315
- processed_events += 1
316
-
317
- job .progress .update_stage (
318
- "tracking" ,
319
- status = JobState .STARTED ,
320
- progress = processed_events / total_events if total_events else 1 ,
321
- )
322
- job .save ()
327
+ assign_occurrences_by_tracking_images (event , job .logger , cost_threshold = cost_threshold , job = job )
323
328
324
329
job .logger .info ("Tracking: Finished tracking." )
325
- job .progress .update_stage ("tracking" , status = JobState .SUCCESS , progress = 1 )
326
330
job .save ()
0 commit comments