Skip to content

Commit

Permalink
merge 'release/1.5' back into main
Browse files Browse the repository at this point in the history
  • Loading branch information
JoepVanlier committed Jul 25, 2024
2 parents 3dc3012 + 90ffcd7 commit 34f024f
Show file tree
Hide file tree
Showing 10 changed files with 248 additions and 62 deletions.
13 changes: 12 additions & 1 deletion changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@
* Added improved printing of calibrations performed with `Pylake`.
* Added parameter `titles` to customize title of each subplot in [`Kymo.plot_with_channels()`](https://lumicks-pylake.readthedocs.io/en/latest/_api/lumicks.pylake.kymo.Kymo.html#lumicks.pylake.kymo.Kymo.plot_with_channels).

## v1.5.2 | 2024-07-24

#### Improvements

* Added a fallback which allows loading scans or kymographs that have truncated photon count channels.

#### Bug fixes

* Fixed bug that prevented opening the kymotracking widget when using it with the `widget` backend on `matplotlib >= 3.9.0`.
* Fixed bug where photon counts were not being loaded from a csv file generated with the kymotracker.

## v1.5.1 | 2024-06-03

* Fixed bug that prevented loading an `h5` file where only a subset of the photon channels are available. This bug was introduced in Pylake `1.4.0`.
Expand All @@ -31,7 +42,7 @@

#### Improvements

* Added error message when parameters are passed to [`lk.parameter_trace()`](https://lumicks-pylake.readthedocs.io/en/v1.5.0/_api/lumicks.pylake.parameter_trace.html) that do not have the required attributes.
* Added error message when parameters are passed to [`lk.parameter_trace()`](https://lumicks-pylake.readthedocs.io/en/v1.5.0/_api/lumicks.pylake.parameter_trace.html) that do not have the required attributes.
* Warn when parameter estimates are hitting the fitting bounds when using [`lk.parameter_trace()`](https://lumicks-pylake.readthedocs.io/en/v1.5.0/_api/lumicks.pylake.parameter_trace.html).

#### Other changes
Expand Down
13 changes: 3 additions & 10 deletions docs/install.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ The easiest way to install Python and SciPy is with `Anaconda`_, a free scientif

.. code-block:: python
conda create -n pylake conda>=23.7.2
conda create -n pylake
#. The environment can then be activated::

Expand Down Expand Up @@ -362,15 +362,8 @@ The full error message is::

Exception: HTTPSConnectionPool(host='conda.anaconda.org', port=443): Max retries exceeded with url: /conda-forge/win-64/current_repodata.json (Caused by SSLError("Can't connect to HTTPS URL because the SSL module is not available."))

This issue has been solved upstream by conda. Make sure you install a new enough version::

conda create -n pylake conda>=23.7.2

And then follow the rest of the installation instructions.
If you already have an environment named pylake, you can remove this environment, before creating it again. Another option is to create an environment with a different name, eg::

conda create -n pylake2 conda>=23.7.2
conda activate pylake2
This issue has been solved upstream by conda.
The best option is to reinstall `conda` and then follow the rest of the installation instructions.

**I tried the installation instructions, but I cannot import Pylake inside a Jupyter notebook**

Expand Down
2 changes: 1 addition & 1 deletion lumicks/pylake/__about__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
__title__ = "lumicks.pylake"
__version__ = "1.5.1"
__version__ = "1.5.2"
__summary__ = "Bluelake data analysis tools"
__url__ = "https://github.com/lumicks/pylake"

Expand Down
8 changes: 8 additions & 0 deletions lumicks/pylake/channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,14 @@ def data(self) -> npt.ArrayLike:
def timestamps(self) -> npt.ArrayLike:
return np.empty(0)

@property
def start(self):
return None

@property
def stop(self):
return None


empty_slice = Slice(Empty())

Expand Down
37 changes: 32 additions & 5 deletions lumicks/pylake/detail/confocal.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,37 @@ def timestamp_mean(a, axis=None):
return minimum + _int_mean(a - minimum, a.size if axis is None else a.shape[axis], axis)


def _default_image_factory(self: "ConfocalImage", color):
def _get_confocal_data(self: "ConfocalImage", color):
channel = getattr(self, f"{color}_photon_count")
infowave = self.infowave

# Early out for an empty channel
if not channel:
return channel, infowave

if infowave.stop and channel.stop and len(infowave.data) != len(channel.data):
if channel.stop < infowave.stop:
warnings.warn(
RuntimeWarning(
f"Warning: {self.__class__.__name__} is truncated. Photon count data ends "
f"{(infowave.stop - channel.stop) / 1e9:.2g} seconds before the end of the "
"info wave (which encodes how the data should be read)."
)
)

return (
channel[: min(infowave.stop, channel.stop)],
infowave[: min(infowave.stop, channel.stop)],
)
else:
return channel, infowave


def _default_image_factory(self: "ConfocalImage", color):
channel, infowave = _get_confocal_data(self, color)
raw_image = reconstruct_image_sum(
channel.data.astype(float) if channel else np.zeros(self.infowave.data.size),
self.infowave.data,
channel.data.astype(float) if channel else np.zeros(infowave.data.size),
infowave.data,
self._reconstruction_shape,
)
return self._to_spatial(raw_image)
Expand All @@ -53,14 +79,15 @@ def _default_image_factory(self: "ConfocalImage", color):
def _default_timestamp_factory(self: "ConfocalImage", reduce=timestamp_mean):
# Uses the timestamps from the first non-zero-sized photon channel
for color in ("red", "green", "blue"):
channel_data = getattr(self, f"{color}_photon_count").timestamps
channel_data, infowave = _get_confocal_data(self, color)
channel_data = channel_data.timestamps
if len(channel_data) != 0:
break
else:
raise RuntimeError("Can't get pixel timestamps if there are no pixels")

raw_image = reconstruct_image(
channel_data, self.infowave.data, self._reconstruction_shape, reduce=reduce
channel_data, infowave.data, self._reconstruction_shape, reduce=reduce
)
return self._to_spatial(raw_image)

Expand Down
71 changes: 66 additions & 5 deletions lumicks/pylake/kymotracker/kymotrack.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,40 @@ def store_column(column_title, format_string, new_data):
np.savetxt(filename, data, fmt=fmt, header=header, delimiter=delimiter)


def _check_summing_mismatch(track, sampling_width):
"""Checks calling sample_from_image on a loaded track reproduces the same result as is in the
file."""
wrong_kymo_warning = (
"Photon counts do not match the photon counts found in the file. It is "
"possible that the loaded kymo or channel doesn't match the one used to "
"create this file."
)
try:
if not np.allclose(
track.sample_from_image((sampling_width - 1) // 2, correct_origin=True),
track.photon_counts,
):
if np.allclose(
track.sample_from_image((sampling_width - 1) // 2, correct_origin=False),
track.photon_counts,
):
return RuntimeWarning(
"Photon counts do not match the photon counts found in the file. Prior to "
"Pylake v1.1.0, the method `sample_from_image` had a bug that assumed the "
"origin of a pixel to be at the edge rather than the center of the pixel. "
"Consequently, the sampled window could be off by one pixel. This file was "
"likely created using the incorrect origin. "
"Note that Pylake loaded the counts found in the file as is, so if the "
"used summing window was very small, there may be a bias in the counts."
"To recreate these counts without bias invoke:"
f"`track.sample_from_image({(sampling_width - 1) // 2}, correct_origin=True)`"
)
else:
return RuntimeWarning(wrong_kymo_warning)
except IndexError:
return RuntimeWarning(wrong_kymo_warning)


def import_kymotrackgroup_from_csv(filename, kymo, channel, delimiter=";"):
"""Import a KymoTrackGroup from a csv file.
Expand Down Expand Up @@ -210,9 +244,13 @@ def import_kymotrackgroup_from_csv(filename, kymo, channel, delimiter=";"):
stacklevel=2,
)

def create_track(time, coord, min_length=None):
def create_track(time, coord, min_length=None, counts=None):
if min_length is not None:
min_length = float(np.unique(min_length).squeeze())

if counts is not None:
coord = CentroidLocalizationModel(coord * kymo.pixelsize_um, counts)

return KymoTrack(time.astype(int), coord, kymo, channel, min_length)

if csv_version == 3:
Expand All @@ -232,11 +270,34 @@ def create_track(time, coord, min_length=None):
else:
min_duration_field = "minimum observable duration (seconds)"

if min_duration_field in data:
mandatory_fields.append(min_duration_field)
count_field = [key for key in data.keys() if "counts" in key]
sampling_width = None
if count_field:
count_field = count_field[0]
if match := re.findall(r"over (\d*) pixels", count_field):
sampling_width = int(match[0])

tracks = []
resampling_mismatch = None
for track_idx in range(len(data[mandatory_fields[0]])):
tracks.append(
create_track(
time=data[mandatory_fields[0]][track_idx],
coord=data[mandatory_fields[1]][track_idx],
min_length=(
data[min_duration_field][track_idx] if min_duration_field in data else None
),
counts=data[count_field][track_idx] if count_field else None,
)
)

if sampling_width is not None:
resampling_mismatch = _check_summing_mismatch(tracks[-1], sampling_width)

if resampling_mismatch:
warnings.warn(resampling_mismatch, stacklevel=2)

data = [data[f] for f in mandatory_fields]
return KymoTrackGroup([create_track(*track_data) for track_data in zip(*data)])
return KymoTrackGroup(tracks)


class KymoTrack:
Expand Down
19 changes: 19 additions & 0 deletions lumicks/pylake/kymotracker/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,25 @@ def kymo_integration_test_data():
)


@pytest.fixture
def kymo_integration_tracks(kymo_integration_test_data):
track_coordinates = [
(np.arange(10, 20), np.full(10, 11)),
(np.arange(15, 25), np.full(10, 21.51)),
]

tracks = KymoTrackGroup(
[
KymoTrack(
np.array(time_idx), np.array(position_idx), kymo_integration_test_data, "red", 0.1
)
for time_idx, position_idx in track_coordinates
]
)

return tracks


@pytest.fixture
def kymo_pixel_calibrations():
image = raw_test_data()
Expand Down
Loading

0 comments on commit 34f024f

Please sign in to comment.