trackin 0.1.0__py3-none-any.whl

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.
trackin/__init__.py ADDED
@@ -0,0 +1 @@
1
+ # __init__.py
@@ -0,0 +1,22 @@
1
+ """
2
+ This module is an example of a barebones sample data provider for napari.
3
+
4
+ It implements the "sample data" specification.
5
+ see: https://napari.org/stable/plugins/guides.html?#sample-data
6
+
7
+ Replace code below according to your needs.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import numpy
13
+
14
+
15
+ def make_sample_data():
16
+ """Generates an image"""
17
+ # Return list of tuples
18
+ # [(data1, add_image_kwargs1), (data2, add_image_kwargs2)]
19
+ # Check the documentation for more information about the
20
+ # add_image_kwargs
21
+ # https://napari.org/stable/api/napari.Viewer.html#napari.Viewer.add_image
22
+ return [(numpy.random.rand(512, 512), {})]
File without changes
@@ -0,0 +1,7 @@
1
+ # from trackin import make_sample_data
2
+
3
+ # add your tests here...
4
+
5
+
6
+ def test_something():
7
+ pass
@@ -0,0 +1,169 @@
1
+ import pytest
2
+
3
+ from trackin.shared_state import shared_state
4
+ from trackin.tracking import generate_graph
5
+ from trackin.utils import accept_track, delete_all_connections, delete_segment, save_segment
6
+
7
+ SHARED_STATE_FIELDS = [
8
+ "DATA",
9
+ "track",
10
+ "csv_folder_to_save",
11
+ "SESSION_FILE",
12
+ "UPDATED_DATA_FILE",
13
+ "N_TRACKS",
14
+ "TRACKED",
15
+ "MAX_TRACK_ID",
16
+ "UPD_TRACK_FILE",
17
+ "NUM_DETS",
18
+ "G",
19
+ ]
20
+
21
+
22
+ @pytest.fixture
23
+ def staged_shared_state(tmp_path):
24
+ """Seed shared_state with a minimal two-frame, two-detection scenario
25
+ for accept_track(), and restore the original values afterward -- it's
26
+ a module-level singleton shared across tests."""
27
+ original = {field: getattr(shared_state, field) for field in SHARED_STATE_FIELDS}
28
+
29
+ shared_state.DATA = [[(10, 20, 0, 0)], [(30, 40, 0, 0)]]
30
+ shared_state.track = [0, 0] # accept the single detection in each frame
31
+ shared_state.csv_folder_to_save = str(tmp_path)
32
+ shared_state.SESSION_FILE = "session.csv"
33
+ shared_state.UPDATED_DATA_FILE = "updated.csv"
34
+ shared_state.N_TRACKS = 0
35
+ shared_state.TRACKED = False
36
+ shared_state.MAX_TRACK_ID = None # no prior track file loaded -> skip UPD_TRACK_FILE
37
+ shared_state.G = generate_graph(
38
+ shared_state.DATA, max_score=1600, score_func="squared", tracked=False
39
+ )
40
+
41
+ yield tmp_path
42
+
43
+ for field, value in original.items():
44
+ setattr(shared_state, field, value)
45
+
46
+
47
+ def test_accept_track_writes_session_file_and_marks_detections_consumed(
48
+ staged_shared_state,
49
+ ):
50
+ tmp_path = staged_shared_state
51
+
52
+ accept_track()
53
+
54
+ session_lines = (tmp_path / "session.csv").read_text().splitlines()
55
+ assert session_lines[0] == "tframe,y,x,displ_y,displ_x,track_no"
56
+ assert session_lines[1] == "0,10,20,0,0,1"
57
+ assert session_lines[2] == "1,30,40,0,0,1"
58
+
59
+ updated_lines = (tmp_path / "updated.csv").read_text().splitlines()
60
+ assert updated_lines == ["tframe,y,x,displ_y,displ_x"] # both detections consumed
61
+
62
+ assert shared_state.DATA[0][0] == (-1000000, -1000000, 0, 0)
63
+ assert shared_state.DATA[1][0] == (-1000000, -1000000, 0, 0)
64
+
65
+
66
+ def test_accept_track_does_not_duplicate_header_when_session_file_has_content(
67
+ staged_shared_state,
68
+ ):
69
+ tmp_path = staged_shared_state
70
+ session_path = tmp_path / "session.csv"
71
+ session_path.write_text("tframe,y,x,displ_y,displ_x,track_no\n0,1,2,0,0,1\n")
72
+
73
+ accept_track()
74
+
75
+ session_lines = session_path.read_text().splitlines()
76
+ assert session_lines.count("tframe,y,x,displ_y,displ_x,track_no") == 1
77
+ assert session_lines[0] == "tframe,y,x,displ_y,displ_x,track_no"
78
+ assert session_lines[1] == "0,1,2,0,0,1" # pre-existing row untouched
79
+ assert session_lines[2] == "0,10,20,0,0,1" # newly accepted row appended
80
+ assert session_lines[3] == "1,30,40,0,0,1"
81
+
82
+
83
+ def test_accept_track_adds_header_when_session_file_exists_but_empty(
84
+ staged_shared_state,
85
+ ):
86
+ tmp_path = staged_shared_state
87
+ session_path = tmp_path / "session.csv"
88
+ session_path.touch() # exists on disk, but 0 bytes
89
+
90
+ accept_track()
91
+
92
+ session_lines = session_path.read_text().splitlines()
93
+ assert session_lines[0] == "tframe,y,x,displ_y,displ_x,track_no"
94
+ assert session_lines[1] == "0,10,20,0,0,1"
95
+ assert session_lines[2] == "1,30,40,0,0,1"
96
+
97
+
98
+ def test_accept_track_writes_upd_track_file_with_offset_track_id(staged_shared_state):
99
+ tmp_path = staged_shared_state
100
+ shared_state.MAX_TRACK_ID = 5
101
+ shared_state.UPD_TRACK_FILE = "upd_tracks.csv"
102
+
103
+ accept_track()
104
+
105
+ # updated_track_id = MAX_TRACK_ID + N_TRACKS = 5 + 1 (accept_track increments
106
+ # N_TRACKS from 0 to 1 before this file is written). Unlike SESSION_FILE, this
107
+ # file has no header -- it's opened in append mode and rows are written as-is.
108
+ upd_track_lines = (tmp_path / "upd_tracks.csv").read_text().splitlines()
109
+ assert upd_track_lines == [
110
+ "0,10,20,0,0,6",
111
+ "1,30,40,0,0,6",
112
+ ]
113
+
114
+
115
+ def test_accept_track_no_op_when_track_is_empty_list(staged_shared_state):
116
+ """Right after images are reloaded (before a new CSV is loaded),
117
+ shared_state.track is [] rather than None -- accept_track() must treat
118
+ that the same way, not silently create/write into the *previous*
119
+ dataset's SESSION_FILE."""
120
+ tmp_path = staged_shared_state
121
+ shared_state.track = []
122
+
123
+ accept_track()
124
+
125
+ assert not (tmp_path / "session.csv").exists()
126
+ assert shared_state.N_TRACKS == 0 # must not increment on a no-op
127
+
128
+
129
+ def test_delete_segment_no_op_when_track_is_empty_list_even_with_stale_num_dets(
130
+ staged_shared_state,
131
+ ):
132
+ """NUM_DETS can still hold a stale nonzero value from the *previous*
133
+ dataset right after images are reloaded -- delete_segment() must not
134
+ rely on NUM_DETS alone, since that would let it through to
135
+ truncate-and-overwrite that dataset's UPDATED_DATA_FILE."""
136
+ tmp_path = staged_shared_state
137
+ shared_state.track = []
138
+ shared_state.NUM_DETS = 5 # stale nonzero value
139
+
140
+ updated_path = tmp_path / "updated.csv"
141
+ updated_path.write_text("tframe,y,x,displ_y,displ_x\n0,99,99,0,0\n")
142
+
143
+ delete_segment()
144
+
145
+ assert updated_path.read_text() == "tframe,y,x,displ_y,displ_x\n0,99,99,0,0\n"
146
+
147
+
148
+ def test_save_segment_no_op_when_track_is_empty_list(staged_shared_state):
149
+ """Right after images are reloaded (before a new CSV is loaded),
150
+ shared_state.track is [] and shared_state.G is None. The segment-
151
+ building part is a harmless no-op on an empty track either way, but
152
+ save_segment() unconditionally ended with send_track(), which calls
153
+ .copy() on shared_state.G -- an AttributeError crash when G is None."""
154
+ shared_state.track = []
155
+ shared_state.G = None
156
+
157
+ save_segment() # must not raise
158
+
159
+ assert shared_state.track == [] # send_track() never ran to reassign it
160
+
161
+
162
+ def test_delete_all_connections_no_op_when_track_is_empty_list(staged_shared_state):
163
+ """Right after images are reloaded (before a new CSV is loaded),
164
+ shared_state.track is [] -- delete_all_connections() indexed into it
165
+ with shared_state.track[shared_state.current_index] unconditionally,
166
+ an IndexError crash on an empty list."""
167
+ shared_state.track = []
168
+
169
+ delete_all_connections() # must not raise
@@ -0,0 +1,377 @@
1
+ from types import SimpleNamespace
2
+
3
+ import numpy as np
4
+ import pandas as pd
5
+
6
+ import trackin._widget as widget
7
+ from trackin._widget import (
8
+ add_white_border,
9
+ check_and_add_displ_cols,
10
+ generate_upd_track_filename,
11
+ write_updated_detections_to_file,
12
+ )
13
+ from trackin.shared_state import shared_state
14
+ from trackin.tracking import generate_graph
15
+
16
+ SHARED_STATE_FIELDS = [
17
+ "DATA",
18
+ "track",
19
+ "track_lines",
20
+ "NUM_DET_PER_FRAME",
21
+ "TRACKED",
22
+ "MAX_TRACK_ID",
23
+ "G",
24
+ "N_TRACKS",
25
+ "SESSION_FILE",
26
+ "UPDATED_DATA_FILE",
27
+ "UPD_TRACK_FILE",
28
+ "NUM_DETS",
29
+ "NUM_CONN",
30
+ ]
31
+
32
+
33
+ def test_check_and_add_displ_cols_adds_missing_columns():
34
+ df = pd.DataFrame({"tframe": [0, 1], "y": [1, 2], "x": [3, 4]})
35
+ result = check_and_add_displ_cols(df)
36
+ assert (result["displ_x"] == 0).all()
37
+ assert (result["displ_y"] == 0).all()
38
+
39
+
40
+ def test_check_and_add_displ_cols_preserves_existing_columns():
41
+ df = pd.DataFrame(
42
+ {"tframe": [0], "y": [1], "x": [3], "displ_x": [5], "displ_y": [7]}
43
+ )
44
+ result = check_and_add_displ_cols(df)
45
+ assert result["displ_x"].tolist() == [5]
46
+ assert result["displ_y"].tolist() == [7]
47
+
48
+
49
+ def test_add_white_border_grayscale():
50
+ image = np.zeros((10, 10), dtype=np.uint8)
51
+ bordered = add_white_border(image, border_size=2)
52
+ assert bordered.shape == (14, 14)
53
+ assert (bordered[0, :] == 255).all() # border row is white
54
+ assert (bordered[2:-2, 2:-2] == 0).all() # original data preserved
55
+
56
+
57
+ def test_add_white_border_rgb():
58
+ image = np.zeros((10, 10, 3), dtype=np.uint8)
59
+ bordered = add_white_border(image, border_size=2)
60
+ assert bordered.shape == (14, 14, 3)
61
+ assert (bordered[0, :, :] == 255).all()
62
+ assert (bordered[2:-2, 2:-2, :] == 0).all()
63
+
64
+
65
+ def test_write_updated_detections_to_file(tmp_path):
66
+ data = [
67
+ [(10, 20, 1, -1), (-1000000, -1000000, 0, 0)], # frame 0: one real, one deleted
68
+ [(30, 40, 0, 2)], # frame 1: one real
69
+ ]
70
+ write_updated_detections_to_file(data, "out.csv", str(tmp_path))
71
+
72
+ lines = (tmp_path / "out.csv").read_text().splitlines()
73
+
74
+ # Header row
75
+ assert lines[0] == "tframe,y,x,displ_y,displ_x"
76
+ # Deleted detection (-1000000 sentinel) excluded, only the two real ones written
77
+ assert len(lines) == 3
78
+ # Real detections written with correct tframe,y,x,displ_y,displ_x values
79
+ assert lines[1] == "0,10,20,1,-1"
80
+ assert lines[2] == "1,30,40,0,2"
81
+
82
+
83
+ def test_write_updated_detections_to_file_empty_data_writes_header_only(tmp_path):
84
+ write_updated_detections_to_file([], "out.csv", str(tmp_path))
85
+
86
+ lines = (tmp_path / "out.csv").read_text().splitlines()
87
+
88
+ assert lines == ["tframe,y,x,displ_y,displ_x"]
89
+
90
+
91
+ def test_generate_upd_track_filename_normal_path():
92
+ result = generate_upd_track_filename(
93
+ "/home/user/data/tracks.csv", "20250101_120000"
94
+ )
95
+ assert result == "with_new_tracks_added_tracks_20250101_120000.csv"
96
+
97
+
98
+ def test_generate_upd_track_filename_windows_style_path():
99
+ result = generate_upd_track_filename(
100
+ "C:\\Users\\test\\my_tracks.csv", "20250101_120000"
101
+ )
102
+ assert result == "with_new_tracks_added_my_tracks_20250101_120000.csv"
103
+
104
+
105
+ def test_generate_upd_track_filename_multiple_dots_in_filename():
106
+ result = generate_upd_track_filename(
107
+ "/home/user/data/my.tracks.v2.csv", "20250101_120000"
108
+ )
109
+ assert result == "with_new_tracks_added_my.tracks.v2_20250101_120000.csv"
110
+
111
+
112
+ class _FakeViewer:
113
+ """Minimal stand-in for a napari viewer -- clear_detections_and_tracks()
114
+ only needs `layers` to support `in`, it never touches anything else."""
115
+
116
+ def __init__(self):
117
+ self.layers = {}
118
+
119
+
120
+ def test_clear_detections_and_tracks_resets_shared_state_and_ui():
121
+ original_viewer = widget.viewer
122
+ original_widgets = {
123
+ name: getattr(widget, name)
124
+ for name in (
125
+ "track_file_label",
126
+ "leftover_row",
127
+ "leftover_path_field",
128
+ "session_row",
129
+ "session_path_field",
130
+ "upd_track_row",
131
+ "upd_track_path_field",
132
+ "dets_label",
133
+ "conns_label",
134
+ "session_files_header",
135
+ "session_files_content",
136
+ )
137
+ }
138
+ original_shared_state = {field: getattr(shared_state, field) for field in SHARED_STATE_FIELDS}
139
+
140
+ try:
141
+ widget.viewer = _FakeViewer()
142
+
143
+ widget.track_file_label = widget.ElidedPathLabel()
144
+ widget.track_file_label.set_path("Track file loaded: old_tracks.csv")
145
+
146
+ widget.leftover_row, widget.leftover_path_field = widget.create_path_row(
147
+ "Leftover detections", "..."
148
+ )
149
+ widget.session_row, widget.session_path_field = widget.create_path_row(
150
+ "Tracks accepted this session", "..."
151
+ )
152
+ widget.upd_track_row, widget.upd_track_path_field = widget.create_path_row(
153
+ "Combined tracks", "..."
154
+ )
155
+ for row in (widget.leftover_row, widget.session_row, widget.upd_track_row):
156
+ row.setVisible(True) # simulate them being shown from a prior load
157
+
158
+ widget.dets_label = widget.QLabel("Detections: 42")
159
+ widget.conns_label = widget.QLabel("Connections: 99")
160
+
161
+ widget.session_files_header = widget.QPushButton("")
162
+ widget.session_files_header.setVisible(True) # simulate it showing from a prior load
163
+ widget.session_files_content = widget.QWidget()
164
+ widget.set_session_files_expanded(True) # simulate it being expanded from a prior load
165
+
166
+ # Seed state as if a full session (images, detections, and a loaded
167
+ # track file) had already happened for the *previous* dataset
168
+ shared_state.DATA = [[(1, 2, 0, 0)]]
169
+ shared_state.track = [0]
170
+ shared_state.track_lines = np.array([[[1, 2], [3, 4]]])
171
+ shared_state.NUM_DET_PER_FRAME = [1]
172
+ shared_state.TRACKED = True
173
+ shared_state.MAX_TRACK_ID = 7
174
+ shared_state.G = object() # value doesn't matter, only that it's reset to None
175
+ shared_state.N_TRACKS = 3
176
+ shared_state.SESSION_FILE = "track_session_old_20250101_000000.csv"
177
+ shared_state.UPDATED_DATA_FILE = "upd_old_20250101_000000.csv"
178
+ shared_state.UPD_TRACK_FILE = "with_new_tracks_added_old_20250101_000000.csv"
179
+ shared_state.NUM_DETS = 42
180
+ shared_state.NUM_CONN = 99
181
+
182
+ widget.clear_detections_and_tracks()
183
+
184
+ assert shared_state.DATA == []
185
+ assert shared_state.track == []
186
+ assert shared_state.track_lines is None
187
+ assert shared_state.NUM_DET_PER_FRAME == []
188
+ assert shared_state.TRACKED is False
189
+ assert shared_state.MAX_TRACK_ID is None
190
+ assert shared_state.G is None
191
+ assert shared_state.N_TRACKS == 0
192
+ assert shared_state.SESSION_FILE == ''
193
+ assert shared_state.UPDATED_DATA_FILE == ''
194
+ assert shared_state.UPD_TRACK_FILE == ''
195
+ assert shared_state.NUM_DETS is None
196
+ assert shared_state.NUM_CONN is None
197
+
198
+ assert widget.track_file_label.full_path == ""
199
+ assert widget.leftover_row.isVisible() is False
200
+ assert widget.session_row.isVisible() is False
201
+ assert widget.upd_track_row.isVisible() is False
202
+
203
+ # The Detections/Connections labels only update in response to
204
+ # graph_updated_event -- clear_detections_and_tracks() must fire it
205
+ # itself, or these stay showing the *previous* dataset's stale
206
+ # counts until some unrelated keypress happens to fire it later.
207
+ assert widget.dets_label is None
208
+ assert widget.conns_label is None
209
+
210
+ # The Session Files card should disappear entirely (not just
211
+ # collapse) -- there's nothing left in it to show until a new CSV
212
+ # is loaded.
213
+ assert widget.session_files_header.isVisible() is False
214
+ assert widget.session_files_content.isVisible() is False
215
+ assert widget.session_files_header.text() == "▸ Session Files"
216
+ finally:
217
+ widget.viewer = original_viewer
218
+ for name, value in original_widgets.items():
219
+ setattr(widget, name, value)
220
+ for field, value in original_shared_state.items():
221
+ setattr(shared_state, field, value)
222
+
223
+
224
+ def test_delete_detection_d_key_no_op_when_track_is_empty_list():
225
+ """Right after images are reloaded (before a new CSV is loaded),
226
+ shared_state.track is [] -- the 'D' key path indexed into it with
227
+ curr_track[shared_state.current_index] unconditionally, an IndexError
228
+ crash on an empty list."""
229
+ original_track = shared_state.track
230
+ try:
231
+ shared_state.track = []
232
+ widget.delete_detection(None, use_key=True) # must not raise
233
+ finally:
234
+ shared_state.track = original_track
235
+
236
+
237
+ class _FakeLayer:
238
+ """Minimal stand-in for a napari Points layer -- delete_det_by_key/
239
+ delete_det_by_mouse only need .data (mutable, indexable) and .refresh()."""
240
+
241
+ def __init__(self, data):
242
+ self.data = data
243
+
244
+ def refresh(self):
245
+ pass
246
+
247
+
248
+ def test_delete_det_by_key_writes_updated_detections_file(tmp_path):
249
+ """Deleting a detection via the 'D' key marked it as removed in
250
+ shared_state.DATA but never persisted that to UPDATED_DATA_FILE --
251
+ unlike adding a detection, which already did via the same function."""
252
+ original = {
253
+ field: getattr(shared_state, field)
254
+ for field in (
255
+ "current_index", "DATA", "TRACKED", "G", "track",
256
+ "csv_folder_to_save", "UPDATED_DATA_FILE",
257
+ )
258
+ }
259
+ original_clicked_index = getattr(widget, "clicked_index", None)
260
+
261
+ try:
262
+ shared_state.current_index = 0
263
+ shared_state.DATA = [[(10, 20, 0, 0), (30, 40, 0, 0)]]
264
+ shared_state.TRACKED = False
265
+ shared_state.track = [-1]
266
+ shared_state.csv_folder_to_save = str(tmp_path)
267
+ shared_state.UPDATED_DATA_FILE = "updated.csv"
268
+ shared_state.G = generate_graph(
269
+ shared_state.DATA, max_score=1600, score_func="squared", tracked=False
270
+ )
271
+ widget.clicked_index = 0
272
+
273
+ layer = _FakeLayer(np.array([[10.0, 20.0], [30.0, 40.0]]))
274
+ widget.delete_det_by_key(layer)
275
+
276
+ lines = (tmp_path / "updated.csv").read_text().splitlines()
277
+ assert lines[0] == "tframe,y,x,displ_y,displ_x"
278
+ assert lines[1] == "0,30,40,0,0" # the deleted detection is excluded
279
+ finally:
280
+ for field, value in original.items():
281
+ setattr(shared_state, field, value)
282
+ widget.clicked_index = original_clicked_index
283
+
284
+
285
+ def test_delete_det_by_mouse_writes_updated_detections_file(tmp_path):
286
+ """Same bug as the 'D' key path, via right-click deletion instead."""
287
+ original = {
288
+ field: getattr(shared_state, field)
289
+ for field in (
290
+ "current_index", "DATA", "TRACKED", "G",
291
+ "csv_folder_to_save", "UPDATED_DATA_FILE",
292
+ )
293
+ }
294
+ original_clicked_index = getattr(widget, "clicked_index", None)
295
+
296
+ try:
297
+ shared_state.current_index = 0
298
+ shared_state.DATA = [[(10, 20, 0, 0), (30, 40, 0, 0)]]
299
+ shared_state.TRACKED = False
300
+ shared_state.csv_folder_to_save = str(tmp_path)
301
+ shared_state.UPDATED_DATA_FILE = "updated.csv"
302
+ shared_state.G = generate_graph(
303
+ shared_state.DATA, max_score=1600, score_func="squared", tracked=False
304
+ )
305
+ widget.clicked_index = 1
306
+
307
+ layer = _FakeLayer(np.array([[10.0, 20.0], [30.0, 40.0]]))
308
+ event = SimpleNamespace(handled=False)
309
+ widget.delete_det_by_mouse(layer, event)
310
+
311
+ lines = (tmp_path / "updated.csv").read_text().splitlines()
312
+ assert lines[0] == "tframe,y,x,displ_y,displ_x"
313
+ assert lines[1] == "0,10,20,0,0" # the deleted detection (index 1) is excluded
314
+ assert event.handled is True
315
+ finally:
316
+ for field, value in original.items():
317
+ setattr(shared_state, field, value)
318
+ widget.clicked_index = original_clicked_index
319
+
320
+
321
+ def test_update_file_paths_display_auto_expands_session_files_card(tmp_path):
322
+ """A newly added file (e.g. from "Add Track File") should be immediately
323
+ visible, not hidden behind a collapsed header the user has to think to
324
+ open -- update_file_paths_display() must expand the card whenever it
325
+ actually updates a row's content."""
326
+ original_csv_loaded = widget.csv_loaded
327
+ original_widgets = {
328
+ name: getattr(widget, name)
329
+ for name in (
330
+ "leftover_row",
331
+ "leftover_path_field",
332
+ "session_row",
333
+ "session_path_field",
334
+ "upd_track_row",
335
+ "upd_track_path_field",
336
+ "session_files_header",
337
+ "session_files_content",
338
+ )
339
+ }
340
+ original_shared_state = {
341
+ field: getattr(shared_state, field)
342
+ for field in ("csv_folder_to_save", "UPDATED_DATA_FILE", "SESSION_FILE", "MAX_TRACK_ID", "UPD_TRACK_FILE")
343
+ }
344
+
345
+ try:
346
+ widget.csv_loaded = True
347
+ shared_state.csv_folder_to_save = str(tmp_path)
348
+ shared_state.UPDATED_DATA_FILE = "updated.csv"
349
+ shared_state.SESSION_FILE = "session.csv"
350
+ shared_state.MAX_TRACK_ID = None
351
+
352
+ widget.leftover_row, widget.leftover_path_field = widget.create_path_row(
353
+ "Leftover detections", "..."
354
+ )
355
+ widget.session_row, widget.session_path_field = widget.create_path_row(
356
+ "Tracks accepted this session", "..."
357
+ )
358
+ widget.upd_track_row, widget.upd_track_path_field = widget.create_path_row(
359
+ "Combined tracks", "..."
360
+ )
361
+
362
+ widget.session_files_header = widget.QPushButton("")
363
+ widget.session_files_header.setVisible(False) # hidden until a CSV is loaded, as it would be by default
364
+ widget.session_files_content = widget.QWidget()
365
+ widget.set_session_files_expanded(False) # start collapsed, as it would be by default
366
+
367
+ widget.update_file_paths_display()
368
+
369
+ assert widget.session_files_header.isVisible() is True
370
+ assert widget.session_files_content.isVisible() is True
371
+ assert widget.session_files_header.text() == "▾ Session Files"
372
+ finally:
373
+ widget.csv_loaded = original_csv_loaded
374
+ for name, value in original_widgets.items():
375
+ setattr(widget, name, value)
376
+ for field, value in original_shared_state.items():
377
+ setattr(shared_state, field, value)
@@ -0,0 +1,7 @@
1
+ # from trackin import write_single_image, write_multiple
2
+
3
+ # add your tests here...
4
+
5
+
6
+ def test_something():
7
+ pass
trackin/_version.py ADDED
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '0.1.0'
22
+ __version_tuple__ = version_tuple = (0, 1, 0)
23
+
24
+ __commit_id__ = commit_id = None