From 4945d7cac816f2219d137e0b69f1c267ea237342 Mon Sep 17 00:00:00 2001 From: Adam Shapiro Date: Fri, 7 Aug 2026 16:22:38 -0400 Subject: [PATCH 1/5] Fixed file index handling if only time hint is specified, no range. --- python/fusion_engine_client/parsers/file_index.py | 4 ++-- python/tests/test_file_index.py | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/python/fusion_engine_client/parsers/file_index.py b/python/fusion_engine_client/parsers/file_index.py index 32e52e20..dbe171ac 100644 --- a/python/fusion_engine_client/parsers/file_index.py +++ b/python/fusion_engine_client/parsers/file_index.py @@ -376,11 +376,11 @@ def get_time_range(self, start: Union[Timestamp, float] = None, stop: Union[Time if len(self._data) == 0: return FileIndex(data=np.copy(self._data), t0=self.t0) # No time bounds specified. Return the complete dataset. - elif start is None and stop is None: + elif start is None and stop is None and hint is None: return FileIndex(data=np.copy(self._data), t0=self.t0) # If there's no P1 timestamps in the index file whatsoever, t0 will be None. In that case, we cannot apply time # bounds to the data, since they are based on P1 time. This should be extremely rare. - elif self.t0 is None: + elif (start is not None or stop is not None) and self.t0 is None: raise IndexError(f'No P1 timestamps present in index. Cannot apply time bounds. ' f'[start={start}, stop={stop}]') else: diff --git a/python/tests/test_file_index.py b/python/tests/test_file_index.py index 4c7782c7..43f61e2b 100644 --- a/python/tests/test_file_index.py +++ b/python/tests/test_file_index.py @@ -354,9 +354,14 @@ def _lower_bound(time): # If the log does not contain P1 time, slicing it by time is not supported. with pytest.raises(IndexError): sliced_index = index[1.0:] + sliced_index = index.get_time_range(start=1.0) sliced_index = index[TimeRange(start=2.0, absolute=True)] sliced_index = index[TimeRange(start=2.0, absolute=False)] + # However, if you don't set start or stop, setting hint should still work. + sliced_index = index.get_time_range(hint='include_nans') + assert (sliced_index.message_index == [e[3] for e in raw_data]).all() + def test_empty_index(): index = FileIndex() From 6bce4818df44f591d16e3c929dbde45b5d9da979 Mon Sep 17 00:00:00 2001 From: Adam Shapiro Date: Fri, 7 Aug 2026 16:22:58 -0400 Subject: [PATCH 2/5] Fixed DataLoader handling of messages with both P1 and system time. --- .../fusion_engine_client/analysis/data_loader.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/python/fusion_engine_client/analysis/data_loader.py b/python/fusion_engine_client/analysis/data_loader.py index 2f489893..176867bc 100644 --- a/python/fusion_engine_client/analysis/data_loader.py +++ b/python/fusion_engine_client/analysis/data_loader.py @@ -441,8 +441,12 @@ def _read(self, # fast reading, messages with system times may have their index entry timestamps set to NAN since A) they can # occur in a log before P1 time is established, and B) there's not necessarily a direct way to convert between # system and P1 time. - p1_time_messages_requested = any([t in messages_with_p1_time for t in needed_message_types]) - system_time_messages_requested = any([t in messages_with_system_time for t in needed_message_types]) + requested_messages_with_p1_time = any([t in messages_with_p1_time for t in needed_message_types]) + requested_messages_with_system_time = any([t in messages_with_system_time for t in needed_message_types]) + all_system_time_messages_have_p1_time = all([t in messages_with_p1_time + for t in needed_message_types if t in messages_with_system_time]) + requested_messages_with_only_system_time = (requested_messages_with_system_time + and not all_system_time_messages_have_p1_time) # Create a dict with references to the requested types only to be returned below. If any data was already # cached, it will be present in self.data and populated here. @@ -471,8 +475,8 @@ def _read(self, # If we need to establish t0 (either P1 time or system time), we will wait to apply the user's filter criteria. # We can get t0 from any message type. - need_t0 = self._need_t0 and p1_time_messages_requested - need_system_t0 = self._need_system_t0 and system_time_messages_requested + need_t0 = self._need_t0 and requested_messages_with_p1_time + need_system_t0 = self._need_system_t0 and requested_messages_with_system_time reader_max_messages_applied = False if need_t0 or need_system_t0: @@ -486,7 +490,7 @@ def _read(self, self.reader.filter_in_place(None, source_ids=source_ids) # If the user is requiring (valid) P1 timestamps, filter to those now. - if require_p1_time and not system_time_messages_requested: + if require_p1_time and not requested_messages_with_only_system_time: self.reader.filter_out_invalid_p1_times() # If the user requested max messages, tell the reader to return max N results. The reader only supports this @@ -497,7 +501,7 @@ def _read(self, # not system time. The read_next() call below will apply this condition and only return messages with valid # system time. if (max_messages is not None and self.reader.have_index() and - not (require_system_time and system_time_messages_requested)): + not (require_system_time and requested_messages_with_system_time)): reader_max_messages_applied = True if max_messages >= 0: self.reader.filter_in_place(slice(None, max_messages)) From e7aa31e47ba8abd65255be6d4c6677e047b16426 Mon Sep 17 00:00:00 2001 From: Adam Shapiro Date: Fri, 7 Aug 2026 16:56:22 -0400 Subject: [PATCH 3/5] Fixed missing street names on map after plotly upgrade. --- python/fusion_engine_client/analysis/analyzer.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/python/fusion_engine_client/analysis/analyzer.py b/python/fusion_engine_client/analysis/analyzer.py index 0d903079..2fdadb98 100755 --- a/python/fusion_engine_client/analysis/analyzer.py +++ b/python/fusion_engine_client/analysis/analyzer.py @@ -92,10 +92,11 @@ def _build_map_style(mapbox_token: Optional[str]): """! @brief Build a `layout.map.style` value for a MapLibre-based Scattermap figure. - If a Mapbox access token is available, pull Mapbox satellite tiles via a custom raster style spec (the mechanism - MapLibre-based maps use in place of the old `layout.mapbox.accesstoken` field, which no longer exists). Otherwise, - fall back to Plotly's built-in token-free `satellite-streets` style, which serves ESRI World Imagery aerial tiles - (max zoom 16, lower resolution than Mapbox) with OpenMapTiles street labels drawn on top. + If a Mapbox access token is available, pull Mapbox's rendered `satellite-streets-v12` tiles (imagery with street + labels composited on top) via a custom raster style spec, the mechanism MapLibre-based maps use in place of the + old `layout.mapbox.accesstoken` field, which no longer exists. Otherwise, fall back to Plotly's built-in + token-free `satellite-streets` style, which serves ESRI World Imagery aerial tiles (max zoom 16, lower resolution + than Mapbox) with OpenMapTiles street labels drawn on top. """ if not mapbox_token: return 'satellite-streets' @@ -103,10 +104,10 @@ def _build_map_style(mapbox_token: Optional[str]): return { 'version': 8, 'sources': { - 'mapbox-satellite': { + 'mapbox-satellite-streets': { 'type': 'raster', 'tiles': [ - f'https://api.mapbox.com/v4/mapbox.satellite/{{z}}/{{x}}/{{y}}@2x.jpg90' + f'https://api.mapbox.com/styles/v1/mapbox/satellite-streets-v12/tiles/256/{{z}}/{{x}}/{{y}}@2x' f'?access_token={mapbox_token}' ], 'tileSize': 256, @@ -114,7 +115,7 @@ def _build_map_style(mapbox_token: Optional[str]): }, }, 'layers': [ - {'id': 'mapbox-satellite-layer', 'type': 'raster', 'source': 'mapbox-satellite'}, + {'id': 'mapbox-satellite-streets-layer', 'type': 'raster', 'source': 'mapbox-satellite-streets'}, ], } From 6ef9ab4f80282b30404d9b257bfcd5a44cc9bb73 Mon Sep 17 00:00:00 2001 From: Adam Shapiro Date: Mon, 10 Aug 2026 10:36:03 -0400 Subject: [PATCH 4/5] Fixed dtype on MasurementDetails gps_time and system_time numpy arrays. --- python/fusion_engine_client/messages/measurement_details.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/fusion_engine_client/messages/measurement_details.py b/python/fusion_engine_client/messages/measurement_details.py index 08280f9e..a943584d 100644 --- a/python/fusion_engine_client/messages/measurement_details.py +++ b/python/fusion_engine_client/messages/measurement_details.py @@ -133,13 +133,13 @@ def to_numpy(cls, messages): idx = time_source == SystemTimeSource.GPS_TIME if np.any(idx): - gps_time = np.full_like(time_source, np.nan) + gps_time = np.full(time_source.shape, np.nan) gps_time[idx] = measurement_time[idx] result['gps_time'] = gps_time idx = time_source == SystemTimeSource.TIMESTAMPED_ON_RECEPTION if np.any(idx): - system_time = np.full_like(time_source, np.nan) + system_time = np.full(time_source.shape, np.nan) system_time[idx] = measurement_time[idx] result['system_time'] = system_time From 08cfff5fa2dd3eec9bd6d3ff0249c8e2a9236508 Mon Sep 17 00:00:00 2001 From: Adam Shapiro Date: Wed, 19 Aug 2026 13:09:23 -0400 Subject: [PATCH 5/5] Minor unit test cleanup. --- python/tests/test_file_index.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/python/tests/test_file_index.py b/python/tests/test_file_index.py index 43f61e2b..51e5474d 100644 --- a/python/tests/test_file_index.py +++ b/python/tests/test_file_index.py @@ -353,10 +353,13 @@ def _lower_bound(time): # If the log does not contain P1 time, slicing it by time is not supported. with pytest.raises(IndexError): - sliced_index = index[1.0:] - sliced_index = index.get_time_range(start=1.0) - sliced_index = index[TimeRange(start=2.0, absolute=True)] - sliced_index = index[TimeRange(start=2.0, absolute=False)] + index[1.0:] + with pytest.raises(IndexError): + index.get_time_range(start=1.0) + with pytest.raises(IndexError): + index[TimeRange(start=2.0, absolute=True)] + with pytest.raises(IndexError): + index[TimeRange(start=2.0, absolute=False)] # However, if you don't set start or stop, setting hint should still work. sliced_index = index.get_time_range(hint='include_nans')