accelerometry-annotator 3.2.2__tar.gz → 3.3.0__tar.gz

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.
Files changed (30) hide show
  1. {accelerometry_annotator-3.2.2 → accelerometry_annotator-3.3.0}/PKG-INFO +3 -1
  2. {accelerometry_annotator-3.2.2 → accelerometry_annotator-3.3.0}/accelerometry_annotator.egg-info/PKG-INFO +3 -1
  3. {accelerometry_annotator-3.2.2 → accelerometry_annotator-3.3.0}/accelerometry_annotator.egg-info/SOURCES.txt +3 -0
  4. {accelerometry_annotator-3.2.2 → accelerometry_annotator-3.3.0}/accelerometry_annotator.egg-info/requires.txt +3 -0
  5. {accelerometry_annotator-3.2.2 → accelerometry_annotator-3.3.0}/pyproject.toml +1 -0
  6. accelerometry_annotator-3.3.0/tests/test_data_loading.py +212 -0
  7. accelerometry_annotator-3.3.0/tests/test_plotting.py +88 -0
  8. accelerometry_annotator-3.3.0/tests/test_state.py +93 -0
  9. accelerometry_annotator-3.3.0/visualize_accelerometry/__init__.py +1 -0
  10. {accelerometry_annotator-3.2.2 → accelerometry_annotator-3.3.0}/visualize_accelerometry/data_loading.py +7 -4
  11. accelerometry_annotator-3.2.2/visualize_accelerometry/__init__.py +0 -1
  12. {accelerometry_annotator-3.2.2 → accelerometry_annotator-3.3.0}/LICENSE +0 -0
  13. {accelerometry_annotator-3.2.2 → accelerometry_annotator-3.3.0}/README.md +0 -0
  14. {accelerometry_annotator-3.2.2 → accelerometry_annotator-3.3.0}/accelerometry_annotator.egg-info/dependency_links.txt +0 -0
  15. {accelerometry_annotator-3.2.2 → accelerometry_annotator-3.3.0}/accelerometry_annotator.egg-info/top_level.txt +0 -0
  16. {accelerometry_annotator-3.2.2 → accelerometry_annotator-3.3.0}/setup.cfg +0 -0
  17. {accelerometry_annotator-3.2.2 → accelerometry_annotator-3.3.0}/visualize_accelerometry/app.py +0 -0
  18. {accelerometry_annotator-3.2.2 → accelerometry_annotator-3.3.0}/visualize_accelerometry/callbacks.py +0 -0
  19. {accelerometry_annotator-3.2.2 → accelerometry_annotator-3.3.0}/visualize_accelerometry/config.py +0 -0
  20. {accelerometry_annotator-3.2.2 → accelerometry_annotator-3.3.0}/visualize_accelerometry/js/download.js +0 -0
  21. {accelerometry_annotator-3.2.2 → accelerometry_annotator-3.3.0}/visualize_accelerometry/plotting.py +0 -0
  22. {accelerometry_annotator-3.2.2 → accelerometry_annotator-3.3.0}/visualize_accelerometry/state.py +0 -0
  23. {accelerometry_annotator-3.2.2 → accelerometry_annotator-3.3.0}/visualize_accelerometry/static/favicon.ico +0 -0
  24. {accelerometry_annotator-3.2.2 → accelerometry_annotator-3.3.0}/visualize_accelerometry/static/favicon.svg +0 -0
  25. {accelerometry_annotator-3.2.2 → accelerometry_annotator-3.3.0}/visualize_accelerometry/static/logo-dark.svg +0 -0
  26. {accelerometry_annotator-3.2.2 → accelerometry_annotator-3.3.0}/visualize_accelerometry/static/logo.jpg +0 -0
  27. {accelerometry_annotator-3.2.2 → accelerometry_annotator-3.3.0}/visualize_accelerometry/static/logo.svg +0 -0
  28. {accelerometry_annotator-3.2.2 → accelerometry_annotator-3.3.0}/visualize_accelerometry/templates/index.html +0 -0
  29. {accelerometry_annotator-3.2.2 → accelerometry_annotator-3.3.0}/visualize_accelerometry/templates/login.html +0 -0
  30. {accelerometry_annotator-3.2.2 → accelerometry_annotator-3.3.0}/visualize_accelerometry/templates/logout.html +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: accelerometry-annotator
3
- Version: 3.2.2
3
+ Version: 3.3.0
4
4
  Summary: Web-based tool for visualizing and annotating wrist-worn accelerometry data from physical performance assessments.
5
5
  Author-email: Manu Murugesan <manorathan@uchicago.edu>
6
6
  License: MIT
@@ -33,6 +33,8 @@ Requires-Dist: openpyxl
33
33
  Requires-Dist: jinja2
34
34
  Provides-Extra: lttb
35
35
  Requires-Dist: lttbc; extra == "lttb"
36
+ Provides-Extra: test
37
+ Requires-Dist: pytest>=7.0; extra == "test"
36
38
  Dynamic: license-file
37
39
 
38
40
  # Accelerometry Annotation Tool
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: accelerometry-annotator
3
- Version: 3.2.2
3
+ Version: 3.3.0
4
4
  Summary: Web-based tool for visualizing and annotating wrist-worn accelerometry data from physical performance assessments.
5
5
  Author-email: Manu Murugesan <manorathan@uchicago.edu>
6
6
  License: MIT
@@ -33,6 +33,8 @@ Requires-Dist: openpyxl
33
33
  Requires-Dist: jinja2
34
34
  Provides-Extra: lttb
35
35
  Requires-Dist: lttbc; extra == "lttb"
36
+ Provides-Extra: test
37
+ Requires-Dist: pytest>=7.0; extra == "test"
36
38
  Dynamic: license-file
37
39
 
38
40
  # Accelerometry Annotation Tool
@@ -6,6 +6,9 @@ accelerometry_annotator.egg-info/SOURCES.txt
6
6
  accelerometry_annotator.egg-info/dependency_links.txt
7
7
  accelerometry_annotator.egg-info/requires.txt
8
8
  accelerometry_annotator.egg-info/top_level.txt
9
+ tests/test_data_loading.py
10
+ tests/test_plotting.py
11
+ tests/test_state.py
9
12
  visualize_accelerometry/__init__.py
10
13
  visualize_accelerometry/app.py
11
14
  visualize_accelerometry/callbacks.py
@@ -48,6 +48,7 @@ dependencies = [
48
48
 
49
49
  [project.optional-dependencies]
50
50
  lttb = ["lttbc"]
51
+ test = ["pytest>=7.0"]
51
52
 
52
53
  [project.urls]
53
54
  Homepage = "https://github.com/TavoloPerUno/py_visualize_accelerometry"
@@ -0,0 +1,212 @@
1
+ """Tests for visualize_accelerometry.data_loading."""
2
+
3
+ import os
4
+
5
+ import numpy as np
6
+ import pandas as pd
7
+ import pytest
8
+
9
+ from visualize_accelerometry.config import ANNOTATION_COLUMNS, TIME_FMT
10
+ from visualize_accelerometry.data_loading import (
11
+ clamp_anchor,
12
+ cleanup_annotations,
13
+ get_annotations_from_files,
14
+ get_filedata,
15
+ get_filenames,
16
+ save_annotations,
17
+ )
18
+
19
+
20
+ class TestGetFilenames:
21
+ """Tests for file discovery and user assignment."""
22
+
23
+ def test_discovers_h5_files(self, patch_config, sample_h5):
24
+ fnames = get_filenames()
25
+ assert len(fnames) == 1
26
+ assert "900001-20230315093000" in fnames[0]
27
+
28
+ def test_deterministic_assignment(self, patch_config, sample_h5, second_h5):
29
+ """Same seed produces same assignment across calls."""
30
+ first = get_filenames()
31
+ second = get_filenames()
32
+ assert first == second
33
+
34
+ def test_assigns_all_files(self, patch_config, sample_h5, second_h5):
35
+ fnames = get_filenames()
36
+ assert len(fnames) == 2
37
+ basenames = [f.split("--")[1] for f in fnames]
38
+ assert "900001-20230315093000" in basenames
39
+ assert "900002-20230316140000" in basenames
40
+
41
+ def test_format_is_user_dash_dash_filename(self, patch_config, sample_h5):
42
+ from visualize_accelerometry.config import ANNOTATOR_USERS
43
+ fnames = get_filenames()
44
+ for f in fnames:
45
+ parts = f.split("--")
46
+ assert len(parts) == 2
47
+ assert parts[0] in ANNOTATOR_USERS
48
+
49
+ def test_ignores_non_h5_files(self, patch_config, tmp_data_dir):
50
+ readings = tmp_data_dir / "readings"
51
+ (readings / "not_a_data_file.csv").write_text("x,y,z")
52
+ fnames = get_filenames()
53
+ assert len(fnames) == 0
54
+
55
+ def test_no_global_rng_pollution(self, patch_config, sample_h5):
56
+ """Ensure get_filenames doesn't pollute global numpy random state."""
57
+ np.random.seed(123)
58
+ before = np.random.random()
59
+ np.random.seed(123)
60
+ get_filenames()
61
+ after = np.random.random()
62
+ assert before == after
63
+
64
+
65
+ class TestGetFiledata:
66
+ """Tests for HDF5 time-window loading."""
67
+
68
+ def test_first_load_returns_bounds(self, sample_h5):
69
+ fname = str(sample_h5).replace(".h5", "")
70
+ anchor, start, end, pdf = get_filedata(fname, None, 3600)
71
+ assert anchor is not None
72
+ assert start is not None
73
+ assert end is not None
74
+ assert len(pdf) > 0
75
+
76
+ def test_subsequent_load_no_bounds(self, sample_h5):
77
+ fname = str(sample_h5).replace(".h5", "")
78
+ anchor, start, end, pdf = get_filedata(fname, None, 3600)
79
+ anchor2, start2, end2, pdf2 = get_filedata(fname, anchor, 3600)
80
+ assert start2 is None
81
+ assert end2 is None
82
+
83
+ def test_returns_expected_columns(self, sample_h5):
84
+ fname = str(sample_h5).replace(".h5", "")
85
+ _, _, _, pdf = get_filedata(fname, None, 3600)
86
+ assert "timestamp" in pdf.columns
87
+ assert "x" in pdf.columns
88
+ assert "y" in pdf.columns
89
+ assert "z" in pdf.columns
90
+
91
+ def test_windowed_query(self, sample_h5):
92
+ """Small window should return fewer rows than the full file."""
93
+ fname = str(sample_h5).replace(".h5", "")
94
+ _, _, _, pdf_full = get_filedata(fname, None, 3600)
95
+ anchor = pdf_full["timestamp"].iloc[len(pdf_full) // 2].strftime(TIME_FMT)
96
+ _, _, _, pdf_small = get_filedata(fname, anchor, 1)
97
+ assert len(pdf_small) <= len(pdf_full)
98
+
99
+
100
+ class TestClampAnchor:
101
+ """Tests for anchor clamping logic."""
102
+
103
+ def test_anchor_within_bounds_unchanged(self):
104
+ start = "Mar 15 2023 09:00 AM"
105
+ end = "Mar 15 2023 10:00 AM"
106
+ anchor = "Mar 15 2023 09:30 AM"
107
+ result = clamp_anchor(anchor, start, end, 600)
108
+ assert result == anchor
109
+
110
+ def test_anchor_past_end_clamped(self):
111
+ start = "Mar 15 2023 09:00 AM"
112
+ end = "Mar 15 2023 10:00 AM"
113
+ anchor = "Mar 15 2023 10:30 AM"
114
+ result = clamp_anchor(anchor, start, end, 600)
115
+ from datetime import datetime
116
+ result_dt = datetime.strptime(result, TIME_FMT)
117
+ end_dt = datetime.strptime(end, TIME_FMT)
118
+ assert result_dt < end_dt
119
+
120
+ def test_anchor_before_start_clamped(self):
121
+ start = "Mar 15 2023 09:00 AM"
122
+ end = "Mar 15 2023 10:00 AM"
123
+ anchor = "Mar 15 2023 08:00 AM"
124
+ result = clamp_anchor(anchor, start, end, 600)
125
+ from datetime import datetime
126
+ result_dt = datetime.strptime(result, TIME_FMT)
127
+ start_dt = datetime.strptime(start, TIME_FMT)
128
+ assert result_dt > start_dt
129
+
130
+
131
+ class TestAnnotations:
132
+ """Tests for annotation I/O and cleanup."""
133
+
134
+ def test_load_annotations(self, patch_config, sample_annotations):
135
+ pdf = get_annotations_from_files()
136
+ assert len(pdf) == 2
137
+ assert set(pdf["artifact"]) == {"chair_stand", "tug"}
138
+
139
+ def test_load_empty_returns_empty_df(self, patch_config):
140
+ pdf = get_annotations_from_files()
141
+ assert len(pdf) == 0
142
+ assert list(pdf.columns) == ANNOTATION_COLUMNS
143
+
144
+ def test_cleanup_sorts_and_fills(self):
145
+ pdf = pd.DataFrame({
146
+ "fname": ["f1", "f2"],
147
+ "artifact": ["tug", "chair_stand"],
148
+ "segment": [1, np.nan],
149
+ "scoring": [np.nan, 1],
150
+ "review": [0, np.nan],
151
+ "start_epoch": [100, np.nan],
152
+ "end_epoch": [200, np.nan],
153
+ "start_time": pd.to_datetime(["2023-01-01", None]),
154
+ "end_time": pd.to_datetime(["2023-01-02", None]),
155
+ "annotated_at": pd.to_datetime(["2023-01-01", "2023-01-01"]),
156
+ "user": ["a", "b"],
157
+ "notes": [None, "test"],
158
+ })
159
+ result = cleanup_annotations(pdf)
160
+ assert result["segment"].isna().sum() == 0
161
+ assert result["scoring"].isna().sum() == 0
162
+ assert result["review"].isna().sum() == 0
163
+ assert result["notes"].dtype == object
164
+
165
+ def test_cleanup_adds_notes_column(self):
166
+ pdf = pd.DataFrame({
167
+ "fname": ["f1"],
168
+ "artifact": ["tug"],
169
+ "segment": [0], "scoring": [0], "review": [0],
170
+ "start_epoch": [100], "end_epoch": [200],
171
+ "start_time": pd.to_datetime(["2023-01-01"]),
172
+ "end_time": pd.to_datetime(["2023-01-02"]),
173
+ "annotated_at": pd.to_datetime(["2023-01-01"]),
174
+ "user": ["a"],
175
+ })
176
+ result = cleanup_annotations(pdf)
177
+ assert "notes" in result.columns
178
+
179
+ def test_save_and_reload(self, patch_config, sample_annotations):
180
+ pdf = get_annotations_from_files()
181
+ pdf = cleanup_annotations(pdf)
182
+
183
+ # Add a new annotation
184
+ new_row = pd.DataFrame({
185
+ "fname": ["900001-20230315093000"],
186
+ "artifact": ["3m_walk"],
187
+ "segment": [0], "scoring": [0], "review": [0],
188
+ "start_epoch": [1678872700.0], "end_epoch": [1678872720.0],
189
+ "start_time": pd.to_datetime(["2023-03-15 09:31:40"]),
190
+ "end_time": pd.to_datetime(["2023-03-15 09:32:00"]),
191
+ "annotated_at": pd.to_datetime(["2023-03-17 11:00:00"]),
192
+ "user": ["test_user"],
193
+ "notes": [""],
194
+ })
195
+ pdf = pd.concat([pdf, new_row], ignore_index=True)
196
+
197
+ result = save_annotations(
198
+ pdf, "test_user", "900001-20230315093000",
199
+ )
200
+ assert len(result) == 3
201
+ assert "3m_walk" in result["artifact"].values
202
+
203
+ def test_save_preserves_other_files(self, patch_config, sample_annotations):
204
+ """Saving annotations for one file shouldn't delete annotations for other files."""
205
+ pdf = get_annotations_from_files()
206
+ pdf = cleanup_annotations(pdf)
207
+
208
+ # Save with only one file's annotations modified
209
+ result = save_annotations(
210
+ pdf, "test_user", "900001-20230315093000",
211
+ )
212
+ assert len(result) == 2
@@ -0,0 +1,88 @@
1
+ """Tests for visualize_accelerometry.plotting."""
2
+
3
+ import numpy as np
4
+ import pandas as pd
5
+ import pytest
6
+
7
+ from bokeh.models import ColumnDataSource
8
+
9
+ from visualize_accelerometry.plotting import _downsample, make_plot, MAX_POINTS
10
+
11
+
12
+ class TestDownsample:
13
+ """Tests for the LTTB downsampling function."""
14
+
15
+ def test_passthrough_when_short(self):
16
+ ts = np.arange(100, dtype=np.float64)
17
+ vals = np.random.randn(100)
18
+ ds_ts, ds_vals = _downsample(ts, vals, 200)
19
+ np.testing.assert_array_equal(ds_ts, ts)
20
+ np.testing.assert_array_equal(ds_vals, vals)
21
+
22
+ def test_reduces_to_target(self):
23
+ n = 50000
24
+ ts = np.arange(n, dtype=np.float64)
25
+ vals = np.sin(np.linspace(0, 10 * np.pi, n))
26
+ ds_ts, ds_vals = _downsample(ts, vals, 1000)
27
+ assert len(ds_ts) <= 1000 + 10 # allow small tolerance
28
+ assert len(ds_ts) > 0
29
+
30
+ def test_preserves_dtype(self):
31
+ ts = np.arange(
32
+ np.datetime64("2023-01-01"),
33
+ np.datetime64("2023-01-01") + np.timedelta64(10000, "ms"),
34
+ np.timedelta64(1, "ms"),
35
+ )
36
+ vals = np.random.randn(len(ts)).astype(np.float64)
37
+ ds_ts, ds_vals = _downsample(ts, vals, 100)
38
+ assert ds_ts.dtype == ts.dtype
39
+
40
+
41
+ class TestMakePlot:
42
+ """Tests for the main plot creation function."""
43
+
44
+ def _make_annotation_cds(self):
45
+ empty = dict(start_time=[], end_time=[])
46
+ return {
47
+ "chair_stand": ColumnDataSource(data=dict(**empty)),
48
+ "3m_walk": ColumnDataSource(data=dict(**empty)),
49
+ "6min_walk": ColumnDataSource(data=dict(**empty)),
50
+ "tug": ColumnDataSource(data=dict(**empty)),
51
+ "segment": ColumnDataSource(data=dict(**empty)),
52
+ "scoring": ColumnDataSource(data=dict(**empty)),
53
+ "review": ColumnDataSource(data=dict(**empty)),
54
+ }
55
+
56
+ def test_returns_four_elements(self):
57
+ n = 2000
58
+ pdf = pd.DataFrame({
59
+ "timestamp": pd.date_range("2023-01-01", periods=n, freq="12ms"),
60
+ "x": np.random.randn(n),
61
+ "y": np.random.randn(n),
62
+ "z": np.random.randn(n),
63
+ })
64
+ result = make_plot(pdf, self._make_annotation_cds())
65
+ assert len(result) == 4
66
+
67
+ def test_empty_data_returns_placeholders(self):
68
+ result = make_plot(None, self._make_annotation_cds())
69
+ assert len(result) == 4
70
+
71
+ def test_empty_dataframe_returns_placeholders(self):
72
+ pdf = pd.DataFrame(columns=["timestamp", "x", "y", "z"])
73
+ result = make_plot(pdf, self._make_annotation_cds())
74
+ assert len(result) == 4
75
+
76
+ def test_signal_cds_has_expected_keys(self):
77
+ n = 2000
78
+ pdf = pd.DataFrame({
79
+ "timestamp": pd.date_range("2023-01-01", periods=n, freq="12ms"),
80
+ "x": np.random.randn(n),
81
+ "y": np.random.randn(n),
82
+ "z": np.random.randn(n),
83
+ })
84
+ _, _, _, signal_cds = make_plot(pdf, self._make_annotation_cds())
85
+ assert "timestamp" in signal_cds.data
86
+ assert "x" in signal_cds.data
87
+ assert "y" in signal_cds.data
88
+ assert "z" in signal_cds.data
@@ -0,0 +1,93 @@
1
+ """Tests for visualize_accelerometry.state."""
2
+
3
+ import os
4
+
5
+ import pandas as pd
6
+ import pytest
7
+
8
+ from visualize_accelerometry.state import AppState
9
+
10
+
11
+ class TestAppStateInit:
12
+ """Tests for AppState initialization."""
13
+
14
+ def test_creates_with_username(self, patch_config, sample_h5):
15
+ state = AppState("test_user")
16
+ assert state.username == "test_user"
17
+
18
+ def test_discovers_files(self, patch_config, sample_h5):
19
+ state = AppState("test_user")
20
+ assert len(state.lst_fnames) == 1
21
+
22
+ def test_sets_initial_fname(self, patch_config, sample_h5):
23
+ state = AppState("test_user")
24
+ assert "900001-20230315093000" in state.fname
25
+
26
+ def test_default_windowsize(self, patch_config, sample_h5):
27
+ state = AppState("test_user")
28
+ assert state.windowsize == 3600
29
+
30
+ def test_annotation_cds_keys(self, patch_config, sample_h5):
31
+ state = AppState("test_user")
32
+ expected = {"chair_stand", "3m_walk", "6min_walk", "tug",
33
+ "segment", "scoring", "review"}
34
+ assert set(state.annotation_cds.keys()) == expected
35
+
36
+ def test_initial_selection_bounds_none(self, patch_config, sample_h5):
37
+ state = AppState("test_user")
38
+ assert state.selection_bounds is None
39
+
40
+
41
+ class TestAppStateLoadFile:
42
+ """Tests for loading signal data."""
43
+
44
+ def test_load_file_data(self, patch_config, sample_h5):
45
+ state = AppState("test_user")
46
+ pdf = state.load_file_data()
47
+ assert pdf is not None
48
+ assert len(pdf) > 0
49
+ assert "timestamp" in pdf.columns
50
+
51
+ def test_sets_anchor_and_bounds(self, patch_config, sample_h5):
52
+ state = AppState("test_user")
53
+ state.load_file_data()
54
+ assert state.anchor_timestamp is not None
55
+ assert state.file_start_timestamp is not None
56
+ assert state.file_end_timestamp is not None
57
+
58
+ def test_stores_signal_data(self, patch_config, sample_h5):
59
+ state = AppState("test_user")
60
+ pdf = state.load_file_data()
61
+ assert state.pdf_signal_to_display is pdf
62
+
63
+
64
+ class TestAppStateAnnotations:
65
+ """Tests for annotation management in AppState."""
66
+
67
+ def test_loads_annotations(self, patch_config, sample_h5, sample_annotations):
68
+ state = AppState("test_user")
69
+ assert len(state.pdf_annotations) == 2
70
+
71
+ def test_refresh_annotations(self, patch_config, sample_h5, sample_annotations):
72
+ state = AppState("test_user")
73
+ state.refresh_annotations()
74
+ assert len(state.pdf_annotations) == 2
75
+
76
+ def test_get_displayed_annotations_filters(
77
+ self, patch_config, sample_h5, sample_annotations,
78
+ ):
79
+ state = AppState("test_user")
80
+ displayed = state.get_displayed_annotations()
81
+ assert all(displayed["user"] == "test_user")
82
+ assert all(
83
+ displayed["fname"] == os.path.basename(state.fname)
84
+ )
85
+
86
+ def test_update_annotation_sources(
87
+ self, patch_config, sample_h5, sample_annotations,
88
+ ):
89
+ state = AppState("test_user")
90
+ state.update_annotation_sources()
91
+ # chair_stand annotation should appear in the CDS
92
+ cs_data = state.annotation_cds["chair_stand"].data
93
+ assert len(cs_data["start_time"]) == 1
@@ -0,0 +1 @@
1
+ __version__ = "3.3.0"
@@ -30,10 +30,11 @@ def get_filenames():
30
30
  uses a fixed random seed so every server restart produces the
31
31
  same mapping, distributing files evenly across annotators.
32
32
  """
33
- # Fixed seed ensures the same user-to-file assignment across restarts
34
- np.random.seed(2020)
33
+ # Fixed seed ensures the same user-to-file assignment across restarts.
34
+ # Use a local Generator to avoid polluting global NumPy random state.
35
+ rng = np.random.default_rng(2020)
35
36
  users_to_assign = list(ANNOTATOR_USERS)
36
- np.random.shuffle(users_to_assign)
37
+ rng.shuffle(users_to_assign)
37
38
  users_cycle = cycle(users_to_assign)
38
39
  lst_files = sorted(
39
40
  next(users_cycle) + "--" + os.path.splitext(f)[0]
@@ -70,7 +71,9 @@ def get_filedata(fname, anchor_timestamp, windowsize):
70
71
  if anchor_timestamp is None:
71
72
  # First load: read the first and last rows to determine file bounds
72
73
  first_row = pd.read_hdf(file_path, "readings", start=0, stop=1)
73
- last_row = pd.read_hdf(file_path, "readings", start=-1)
74
+ with pd.HDFStore(file_path, mode="r") as store:
75
+ nrows = store.get_storer("readings").nrows
76
+ last_row = pd.read_hdf(file_path, "readings", start=nrows - 1, stop=nrows)
74
77
  anchor_timestamp = first_row["timestamp"].dt.strftime(TIME_FMT).values[0]
75
78
  file_start = first_row["timestamp"].dt.strftime(TIME_FMT).values[0]
76
79
  file_end = last_row["timestamp"].dt.strftime(TIME_FMT).values[0]
@@ -1 +0,0 @@
1
- __version__ = "3.2.2"