Skip to content

Commit

Permalink
kymo: allow calibrating upside down tether
Browse files Browse the repository at this point in the history
  • Loading branch information
rpauszek committed Jan 30, 2025
1 parent d35aa11 commit 189dd81
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 13 deletions.
23 changes: 13 additions & 10 deletions lumicks/pylake/kymo.py
Original file line number Diff line number Diff line change
Expand Up @@ -1016,12 +1016,11 @@ def calibrate_to_kbp(self, length_kbp, *, start=None, end=None):
if (start is None) ^ (end is None):
raise ValueError("Both start and end points of the tether must be supplied.")

if start is not None:
if end < start:
raise ValueError("end must be larger than start.")
kbp_per_pixel = length_kbp / (end - start) * self.pixelsize_um[0]
else:
kbp_per_pixel = length_kbp / self._num_pixels[0]
kbp_per_pixel = (
length_kbp / (end - start) * self.pixelsize_um[0]
if start is not None
else length_kbp / self._num_pixels[0]
)
pixel_origin = start / self.pixelsize_um[0] if start is not None else 0.0

result = copy(self)
Expand Down Expand Up @@ -1123,7 +1122,7 @@ def label(self):
@dataclass(frozen=True)
class PositionCalibration:
unit: PositionUnit = PositionUnit.pixel
value: float = 1.0
_value: float = 1.0
origin: float = 0.0

def __post_init__(self):
Expand All @@ -1132,11 +1131,15 @@ def __post_init__(self):

def from_pixels(self, pixels):
"""Convert coordinates from pixel values to calibrated values"""
return self.value * (np.array(pixels) - self.origin)
return self._value * (np.array(pixels) - self.origin)

def to_pixels(self, calibrated):
"""Convert coordinates from calibrated values to pixel values"""
return np.array(calibrated) / self.value + self.origin
return np.array(calibrated) / self._value + self.origin

@property
def value(self):
return np.abs(self._value)

@property
def unit_label(self):
Expand All @@ -1146,7 +1149,7 @@ def downsample(self, factor):
return (
self
if self.unit == PositionUnit.pixel
else PositionCalibration(self.unit, self.value * factor)
else PositionCalibration(self.unit, self._value * factor)
)


Expand Down
39 changes: 39 additions & 0 deletions lumicks/pylake/kymotracker/tests/test_greedy_algorithm.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,42 @@ def test_default_parameters(kymo_pixel_calibrations):
with np.testing.assert_raises(AssertionError):
for ref, track in zip(ref_tracks, tracks):
np.testing.assert_allclose(ref.position, track.position)


@pytest.mark.parametrize("tether_len,start,end", [(10, 0.18, 0.62), (48.502, 0.03, 0.94)])
def test_track_calibrated_flipped(tether_len, start, end):
"""test that tracking a calibrated kymo with tether end < tether start yields the same
coordinates as tracking the flipped tether with end > start.
"""

image = np.zeros((20, 25))

# ([time, position], ...)
ref_tracks = [
([1, 5], [2, 6], [3, 7]),
([5, 15], [6, 15], [7, 14], [8, 15], [9, 16]),
]
for coords in ref_tracks:
for c in coords:
image[*c[::-1]] = 10

kymo = _kymo_from_array(image, "g", line_time_seconds=0.1, pixel_size_um=0.050)
kymo_flipped = kymo.flip()

# start, end = 0.18, 0.62
len_um = kymo._calibration.from_pixels(kymo._num_pixels[0] - 1)
end_flipped = len_um - end
start_flipped = len_um - start

# tether_len = 10
kymo = kymo.calibrate_to_kbp(tether_len, start=start, end=end)
kymo_flipped = kymo_flipped.calibrate_to_kbp(tether_len, start=start_flipped, end=end_flipped)

params = dict(pixel_threshold=5, window=3)
tracks = track_greedy(kymo, "green", track_width=3 * kymo._calibration.value, **params)
tracks_flipped = track_greedy(
kymo_flipped, "green", track_width=3 * kymo_flipped._calibration.value, **params
)

for track, track_flipped in zip(tracks, tracks_flipped):
np.testing.assert_allclose(track.position, track_flipped.position)
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,6 @@ def test_calibrate_to_kbp(test_kymo):
np.testing.assert_allclose(kymo_bp._calibration.value * n_pixels_tether, length_kbp)
np.testing.assert_allclose(kymo_bp.pixelsize, length_kbp / n_pixels_tether)

with pytest.raises(ValueError, match="end must be larger than start."):
kymo.calibrate_to_kbp(length_kbp, start=end, end=start)

with pytest.raises(RuntimeError, match="kymo is already calibrated in base pairs."):
kymo_bp.calibrate_to_kbp(10)

Expand Down Expand Up @@ -163,3 +160,12 @@ def test_coordinate_transforms():
transformed = c.from_pixels(px_coord)
np.testing.assert_allclose(kbp_coord, transformed)
np.testing.assert_allclose(px_coord, c.to_pixels(transformed))

c_flipped = PositionCalibration("kbp", -0.42, origin=2.0)
kbp_coord = -kbp_coord
transformed = c_flipped.from_pixels(px_coord)
np.testing.assert_allclose(kbp_coord, transformed)
np.testing.assert_allclose(px_coord, c_flipped.to_pixels(transformed))

assert c._value == -c_flipped._value
assert c.value == c_flipped.value

0 comments on commit 189dd81

Please sign in to comment.