44# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0> for full license details.
55
66import json
7- from datetime import datetime , timedelta
7+ from io import StringIO
88
99import dash
1010import logging_config
1111import pandas as pd
12- from dash import dcc , html
12+ from dash import callback_context , dcc , html
1313from dash .dependencies import Input , Output , State
1414from dash .exceptions import PreventUpdate
1515from main import app
1616
1717import config as cfg
1818from services import api_client , get_token
19- from utils .data import assign_event_ids , process_bbox , read_stored_DataFrame
19+ from utils .data import process_bbox
2020
2121logger = logging_config .configure_logging (cfg .DEBUG , cfg .SENTRY_DSN )
2222
@@ -145,19 +145,19 @@ def get_cameras(user_token):
145145 logger .info ("Get cameras data" )
146146 cameras = pd .DataFrame (api_client .fetch_cameras ().json ())
147147
148- return json . dumps ({ "data" : cameras .to_json (orient = "split" ), "data_loaded" : True } )
148+ return cameras .to_json (orient = "split" )
149149
150150
151151@app .callback (
152- Output ("api_detections " , "data" ),
152+ Output ("api_sequences " , "data" ),
153153 [Input ("main_api_fetch_interval" , "n_intervals" ), Input ("api_cameras" , "data" )],
154154 [
155- State ("api_detections " , "data" ),
155+ State ("api_sequences " , "data" ),
156156 State ("user_token" , "data" ),
157157 ],
158158 prevent_initial_call = True ,
159159)
160- def api_watcher (n_intervals , api_cameras , local_detections , user_token ):
160+ def api_watcher (n_intervals , api_cameras , local_sequences , user_token ):
161161 """
162162 Callback to periodically fetch alerts data from the API.
163163
@@ -176,27 +176,82 @@ def api_watcher(n_intervals, api_cameras, local_detections, user_token):
176176 if user_token is None :
177177 raise PreventUpdate
178178
179- logger .info ("Start Fetching the events" )
180- # Fetch Detections
181- previous_time_event = (datetime .now () - timedelta (days = 2 )).strftime ("%Y-%m-%d_%H:%M:%S" )
182- response = api_client .fetch_unlabeled_detections (from_date = previous_time_event , limit = 50 )
183- api_detections = pd .DataFrame (response .json ())
184-
185- local_detections , _ = read_stored_DataFrame (local_detections )
186- if len (api_detections ) == 0 :
187- return json .dumps (
188- {
189- "data" : pd .DataFrame ().to_json (orient = "split" ),
190- "data_loaded" : True ,
191- }
192- )
179+ logger .info ("Start Fetching Sequences" )
180+ # Fetch Sequences
181+ response = api_client .fetch_latest_sequences ()
182+ api_sequences = pd .DataFrame (response .json ())
183+
184+ local_sequences = pd .read_json (StringIO (local_sequences ), orient = "split" )
185+ if len (api_sequences ) == 0 :
186+ return pd .DataFrame ().to_json (orient = "split" )
193187
194188 else :
195- api_detections ["processed_bboxes" ] = api_detections ["bboxes" ].apply (process_bbox )
196- api_detections = assign_event_ids (api_detections , time_threshold = 30 * 60 )
197- if not local_detections .empty :
198- aligned_api_detections , aligned_local_detections = api_detections ["id" ].align (local_detections ["id" ])
199- if all (aligned_api_detections == aligned_local_detections ):
189+ if not local_sequences .empty :
190+ aligned_api_sequences , aligned_local_sequences = api_sequences ["id" ].align (local_sequences ["id" ])
191+ if all (aligned_api_sequences == aligned_local_sequences ):
200192 return dash .no_update
201193
202- return json .dumps ({"data" : api_detections .to_json (orient = "split" ), "data_loaded" : True })
194+ return api_sequences .to_json (orient = "split" )
195+
196+
197+ @app .callback (
198+ [Output ("are_detections_loaded" , "data" ), Output ("sequence_on_display" , "data" ), Output ("api_detections" , "data" )],
199+ [Input ("api_sequences" , "data" ), Input ("sequence_id_on_display" , "data" ), Input ("api_detections" , "data" )],
200+ State ("are_detections_loaded" , "data" ),
201+ prevent_initial_call = True ,
202+ )
203+ def load_detections (api_sequences , sequence_id_on_display , api_detections , are_detections_loaded ):
204+ # Deserialize data
205+ api_sequences = pd .read_json (StringIO (api_sequences ), orient = "split" )
206+ sequence_id_on_display = str (sequence_id_on_display )
207+ are_detections_loaded = json .loads (are_detections_loaded )
208+ api_detections = json .loads (api_detections )
209+
210+ # Initialize sequence_on_display
211+ sequence_on_display = pd .DataFrame ().to_json (orient = "split" )
212+
213+ # Identify which input triggered the callback
214+ ctx = callback_context
215+ if not ctx .triggered :
216+ raise PreventUpdate
217+
218+ triggered_input = ctx .triggered [0 ]["prop_id" ].split ("." )[0 ]
219+
220+ if triggered_input == "sequence_id_on_display" :
221+ # If the displayed sequence changes, load its detections if not already loaded
222+ if sequence_id_on_display not in api_detections :
223+ response = api_client .fetch_sequences_detections (sequence_id_on_display )
224+ detections = pd .DataFrame (response .json ())
225+ detections ["processed_bboxes" ] = detections ["bboxes" ].apply (process_bbox )
226+ api_detections [sequence_id_on_display ] = detections .to_json (orient = "split" )
227+
228+ sequence_on_display = api_detections [sequence_id_on_display ]
229+ last_seen_at = api_sequences .loc [
230+ api_sequences ["id" ].astype ("str" ) == sequence_id_on_display , "last_seen_at"
231+ ].iloc [0 ]
232+
233+ # Ensure last_seen_at is stored as a string
234+ are_detections_loaded [sequence_id_on_display ] = str (last_seen_at )
235+
236+ else :
237+ # If no specific sequence is triggered, load detections for the first missing sequence
238+ for _ , row in api_sequences .iterrows ():
239+ sequence_id = str (row ["id" ])
240+ last_seen_at = row ["last_seen_at" ]
241+
242+ if sequence_id not in are_detections_loaded or are_detections_loaded [sequence_id ] != str (last_seen_at ):
243+ response = api_client .fetch_sequences_detections (sequence_id )
244+ detections = pd .DataFrame (response .json ())
245+ detections ["processed_bboxes" ] = detections ["bboxes" ].apply (process_bbox )
246+ api_detections [sequence_id ] = detections .to_json (orient = "split" )
247+ are_detections_loaded [sequence_id ] = str (last_seen_at )
248+ break
249+
250+ # Clean up old sequences that are no longer in api_sequences
251+ sequences_in_api = api_sequences ["id" ].astype ("str" ).values
252+ to_drop = [key for key in are_detections_loaded if key not in sequences_in_api ]
253+ for key in to_drop :
254+ are_detections_loaded .pop (key , None )
255+
256+ # Serialize and return data
257+ return json .dumps (are_detections_loaded ), sequence_on_display , json .dumps (api_detections )
0 commit comments