Skip to content

Commit b203692

Browse files
authored
Merge pull request #815 from OpenTrafficCam/bug/9514-track-import-bricht-mit-fehler-ab
OP#9514: Fix feather file column order mismatch in concat operations
2 parents ade7db1 + 6484dee commit b203692

3 files changed

Lines changed: 102 additions & 0 deletions

File tree

OTAnalytics/plugin_parser/convert_ottrk_to_feathers.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ def convert_ottrk_to_feather(input_file: Path) -> None:
9999

100100
# Get the pandas DataFrame
101101
df = pandas_dataset.get_data()
102+
df = df[sorted(df.columns)]
102103
logger().info(f"DataFrame shape: {df.shape}")
103104

104105
# Save DataFrame to feather format

OTAnalytics/plugin_parser/feathers_parser.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ def parse_files(self, files: list[Path]) -> TracksParseResult:
137137
detections_metadata.append(detection_metadata)
138138
logger().info(f"{len(files)} track files parsed.")
139139

140+
columns = data_frames[0].columns
141+
data_frames = [df.select(columns) for df in data_frames]
140142
df = pl.concat(data_frames)
141143
# Create TrackDataset from DataFrame
142144
calculator = PolarsByMaxConfidence()

tests/unit/OTAnalytics/plugin_parser/test_feathers_parser.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,17 @@
99
from unittest.mock import Mock, patch
1010

1111
import pandas as pd
12+
import polars
1213
import pytest
1314

1415
from OTAnalytics.application.datastore import DetectionMetadata, TrackParseResult
16+
from OTAnalytics.domain import track
1517
from OTAnalytics.domain.video import VideoMetadata
1618
from OTAnalytics.plugin_datastore.polars_track_store import PolarsTrackDataset
1719
from OTAnalytics.plugin_datastore.track_geometry_store.polars_geometry_store import (
1820
PolarsTrackGeometryDataset,
1921
)
22+
from OTAnalytics.plugin_parser import ottrk_dataformat as ottrk
2023
from OTAnalytics.plugin_parser.feathers_parser import FeathersParser
2124

2225

@@ -258,3 +261,99 @@ def test_parse_detection_metadata_empty_classes(
258261

259262
assert isinstance(result, DetectionMetadata)
260263
assert result.detection_classes == frozenset()
264+
265+
@patch("OTAnalytics.plugin_parser.feathers_parser.parse_json")
266+
@patch(
267+
"OTAnalytics.plugin_datastore.polars_track_store."
268+
"PolarsTrackDataset.from_dataframe"
269+
)
270+
def test_parse_files_with_different_column_order(
271+
self,
272+
mock_from_dataframe: Mock,
273+
mock_parse_json: Mock,
274+
parser: FeathersParser,
275+
sample_metadata: dict[str, Any],
276+
test_data_tmp_dir: Path,
277+
) -> None:
278+
"""Regression test: parse_files must handle feather files with different
279+
column orders without raising polars ShapeError.
280+
281+
See: https://openproject.platomo.de/work_packages/9514
282+
"""
283+
284+
columns_order_a = [
285+
track.CLASSIFICATION,
286+
track.CONFIDENCE,
287+
track.X,
288+
track.Y,
289+
track.W,
290+
track.H,
291+
track.INTERPOLATED_DETECTION,
292+
ottrk.FIRST,
293+
ottrk.FINISHED,
294+
track.FRAME, # swapped
295+
track.VIDEO_NAME,
296+
track.INPUT_FILE,
297+
track.ORIGINAL_TRACK_ID,
298+
track.TRACK_CLASSIFICATION,
299+
track.TRACK_ID,
300+
track.OCCURRENCE,
301+
]
302+
columns_order_b = [
303+
track.CLASSIFICATION,
304+
track.CONFIDENCE,
305+
track.X,
306+
track.Y,
307+
track.W,
308+
track.H,
309+
track.FRAME, # swapped
310+
track.INTERPOLATED_DETECTION,
311+
ottrk.FIRST,
312+
ottrk.FINISHED,
313+
track.VIDEO_NAME,
314+
track.INPUT_FILE,
315+
track.TRACK_CLASSIFICATION,
316+
track.ORIGINAL_TRACK_ID,
317+
track.TRACK_ID,
318+
track.OCCURRENCE,
319+
]
320+
row = {
321+
track.CLASSIFICATION: "car",
322+
track.CONFIDENCE: 0.9,
323+
track.X: 100.0,
324+
track.Y: 100.0,
325+
track.W: 50.0,
326+
track.H: 80.0,
327+
track.FRAME: 1,
328+
track.INTERPOLATED_DETECTION: False,
329+
ottrk.FIRST: True,
330+
ottrk.FINISHED: False,
331+
track.VIDEO_NAME: "test.mp4",
332+
track.INPUT_FILE: "test.ottrk",
333+
track.ORIGINAL_TRACK_ID: "1",
334+
track.TRACK_CLASSIFICATION: "car",
335+
track.TRACK_ID: "1",
336+
track.OCCURRENCE: datetime(2023, 1, 1, 10, 0, 0),
337+
}
338+
df_a = polars.DataFrame(row).select(columns_order_a)
339+
df_b = polars.DataFrame(row).select(columns_order_b)
340+
341+
mock_parse_json.return_value = sample_metadata
342+
mock_from_dataframe.return_value = Mock(spec=PolarsTrackDataset)
343+
344+
tmpdir_path = test_data_tmp_dir
345+
346+
file_a = tmpdir_path / "file_a.feather"
347+
file_b = tmpdir_path / "file_b.feather"
348+
df_a.write_ipc(file_a)
349+
df_b.write_ipc(file_b)
350+
351+
for stem in ["file_a", "file_b"]:
352+
metadata_path = tmpdir_path / f"{stem}_metadata.json"
353+
metadata_path.write_text('{"test": "data"}')
354+
355+
parser.parse_files([file_a, file_b])
356+
357+
concat_df = mock_from_dataframe.call_args[0][0]
358+
assert concat_df.shape[0] == 2
359+
assert set(concat_df.columns) == set(columns_order_a)

0 commit comments

Comments
 (0)