Skip to content

Commit

Permalink
kymotrack: format minimum duration as float in exported CSVs
Browse files Browse the repository at this point in the history
  • Loading branch information
dean0x7d authored and rpauszek committed Oct 16, 2023
1 parent d5a162d commit 8ed0967
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 10 deletions.
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#### Bug fixes

* Fixed a bug where the minimum length field of an exported `KymoTrackGroup` was formatted as an integer resulting in rounding errors when storing the tracks. Note that an incorrect minimum length can lead to biases when performing dwell time analysis. These values are now properly formatted as floating point numbers. The entry in the header was also changed to "minimum observable duration (seconds)" for additional clarity. This bug was introduced in version `1.2.0`.
* Fixed a bug that prevented resaving a `KymoTrackGroup` loaded from an older version of Pylake.
* Fixed a bug that inadvertently made us rely on `cachetools>=5.x`. Older versions of `cachetools` did not pass the instance to the key function resulting in a `TypeError: key() missing 1 required positional argument: '_'` error when accessing cached properties or methods.
* Fixed a bug that could cause a division by zero while fitting power spectra with `f_diode`. The lower bound of `f_diode` was changed from 0 to 1.0 Hz. This change should not impact results unless users were getting failed calibrations with this error.
Expand Down
31 changes: 25 additions & 6 deletions lumicks/pylake/kymotracker/kymotrack.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ def export_kymotrackgroup_to_csv(

position = np.hstack([track.position for track in kymotrack_group])
seconds = np.hstack([track.seconds for track in kymotrack_group])
minimum_length = np.hstack(
minimum_observable_duration = np.hstack(
[np.full(len(track), track._minimum_observable_duration) for track in kymotrack_group]
)

Expand Down Expand Up @@ -139,8 +139,10 @@ def store_column(column_title, format_string, new_data):
),
)

if None not in minimum_length:
store_column("minimum_length (-)", "%d", minimum_length)
if None not in minimum_observable_duration:
store_column(
f"minimum observable duration ({time_units})", "%.6e", minimum_observable_duration
)
else:
warnings.warn(
RuntimeWarning(
Expand All @@ -155,7 +157,7 @@ def store_column(column_title, format_string, new_data):
)
)

version_header = f"Exported with pylake v{__version__} | track coordinates v3\n"
version_header = f"Exported with pylake v{__version__} | track coordinates v4\n"
header = version_header + delimiter.join(column_titles)
data = np.vstack(data).T
np.savetxt(filename, data, fmt=fmt, header=header, delimiter=delimiter)
Expand Down Expand Up @@ -213,8 +215,25 @@ def create_track(time, coord, min_length=None):
min_length = float(np.unique(min_length).squeeze())
return KymoTrack(time.astype(int), coord, kymo, channel, min_length)

if (min_length_field := "minimum_length (-)") in data:
mandatory_fields.append(min_length_field)
if csv_version == 3:
warnings.warn(
RuntimeWarning(
"This CSV file is from a version in which the minimum observable track duration "
"was incorrectly rounded to an integer. This may result in errors in certain "
"analyses. To remedy this error, use `lk.filter_tracks()` on the "
"tracks from the widget with the desired minimum track length and resave "
"using the `KymoTrackGroup.save()` method. Note that the minimum length should be "
"greater or equal to the minimum length used for the original tracking. For more "
"information refer to "
"https://lumicks-pylake.readthedocs.io/en/latest/tutorial/nbwidgets.html#migrating-old-track-files"
)
)
min_duration_field = "minimum_length (-)"
else:
min_duration_field = "minimum observable duration (seconds)"

if min_duration_field in data:
mandatory_fields.append(min_duration_field)

data = [data[f] for f in mandatory_fields]
return KymoTrackGroup([create_track(*track_data) for track_data in zip(*data)])
Expand Down
8 changes: 8 additions & 0 deletions lumicks/pylake/kymotracker/tests/data/tracks_v3.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Exported with pylake v1.2.0 | track coordinates v3
# track index;time (pixels);coordinate (pixels);time (seconds);position (um);counts (summed over 5 pixels);minimum_length (-)
0;1.000000000000000000e+00;2.000000000000000000e+00;4.600000000000000000e+01;2.000000000000000000e+00;2;0
0;2.000000000000000000e+00;3.000000000000000000e+00;9.200000000000000000e+01;3.000000000000000000e+00;2;0
0;3.000000000000000000e+00;4.000000000000000000e+00;1.380000000000000000e+02;4.000000000000000000e+00;2;0
1;2.000000000000000000e+00;3.000000000000000000e+00;9.200000000000000000e+01;3.000000000000000000e+00;2;0
1;3.000000000000000000e+00;4.000000000000000000e+00;1.380000000000000000e+02;4.000000000000000000e+00;2;0
1;4.000000000000000000e+00;5.000000000000000000e+00;1.840000000000000000e+02;5.000000000000000000e+00;2;0
8 changes: 8 additions & 0 deletions lumicks/pylake/kymotracker/tests/data/tracks_v4.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Exported with pylake v1.2.0 | track coordinates v4
# track index;time (pixels);coordinate (pixels);time (seconds);position (um);counts (summed over 5 pixels);minimum observable duration (seconds)
0;1.000000000000000000e+00;2.000000000000000000e+00;4.600000000000000000e+01;2.000000000000000000e+00;2;5.888000e-04
0;2.000000000000000000e+00;3.000000000000000000e+00;9.200000000000000000e+01;3.000000000000000000e+00;2;5.888000e-04
0;3.000000000000000000e+00;4.000000000000000000e+00;1.380000000000000000e+02;4.000000000000000000e+00;2;5.888000e-04
1;2.000000000000000000e+00;3.000000000000000000e+00;9.200000000000000000e+01;3.000000000000000000e+00;2;5.888000e-04
1;3.000000000000000000e+00;4.000000000000000000e+00;1.380000000000000000e+02;4.000000000000000000e+00;2;5.888000e-04
1;4.000000000000000000e+00;5.000000000000000000e+00;1.840000000000000000e+02;5.000000000000000000e+00;2;5.888000e-04
17 changes: 13 additions & 4 deletions lumicks/pylake/kymotracker/tests/test_io.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import io
import re
import inspect
import contextlib
from copy import copy
from pathlib import Path

Expand Down Expand Up @@ -43,6 +44,7 @@ def compare_kymotrack_group(group1, group2):
[int(1e9), 1.0, ",", 0, 2],
[int(1e9), 1.0, ";", 1, 3],
[int(1e9), 2.0, ";", None, None],
[np.int64(12800), 1.0, ";", 0, 2], # realistic infowave dt
],
)
def test_kymotrackgroup_io(tmpdir_factory, dt, dx, delimiter, sampling_width, sampling_outcome):
Expand Down Expand Up @@ -147,7 +149,7 @@ def get_args(func):
tracks = KymoTrackGroup(
[
KymoTrack(
np.array(time_idx), np.array(position_idx), kymo_integration_test_data, "red", 0
np.array(time_idx), np.array(position_idx), kymo_integration_test_data, "red", 0.1
)
for time_idx, position_idx in track_coordinates
]
Expand All @@ -169,7 +171,7 @@ def get_args(func):

@pytest.mark.parametrize(
"version, read_with_version",
[[0, False], [1, False], [2, True]],
[[0, False], [1, False], [2, True], [3, True], [4, True]],
)
def test_csv_version(version, read_with_version):
# Test that header is parsed properly on CSV import
Expand All @@ -190,13 +192,20 @@ def test_csv_version(version, read_with_version):
test_data,
pixel_size_nm=1.0 * 1000,
start=np.int64(20e9),
dt=int(1e9),
dt=np.int64(12800),
samples_per_pixel=5,
line_padding=3,
)

testfile = Path(__file__).parent / f"./data/tracks_v{version}.csv"
imported_tracks = import_kymotrackgroup_from_csv(testfile, kymo, "red", delimiter=";")
with pytest.warns(
RuntimeWarning,
match=(
"This CSV file is from a version in which the minimum observable track duration "
"was incorrectly rounded to an integer."
),
) if version == 3 else contextlib.nullcontext():
imported_tracks = import_kymotrackgroup_from_csv(testfile, kymo, "red", delimiter=";")

data, pylake_version, csv_version = _read_txt(testfile, ";")

Expand Down

0 comments on commit 8ed0967

Please sign in to comment.