Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ci: some ci maintenance work #675

Merged
merged 3 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/pylake_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
run: python -c "import lumicks.pylake as lk; lk.benchmark(repeat=1)"
- name: Black
run: |
pip install black==23.7.0 isort==5.12.0
pip install black==24.4.2 isort==5.13.2
black --check --diff --color .
isort --check-only --diff .
- name: flake8
Expand Down
24 changes: 24 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
repos:
- repo: https://github.com/psf/black
rev: 24.4.2
hooks:
- id: black
- repo: https://github.com/pycqa/isort
rev: 5.13.2
hooks:
- id: isort
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: end-of-file-fixer
exclude: ^.*\.png$ ^.*\.gif$
- id: requirements-txt-fixer
- id: trailing-whitespace
exclude: ^.*\.png$ ^.*\.gif$
- repo: https://github.com/pycqa/flake8
rev: 7.1.0
hooks:
- id: flake8
language_version: python3
additional_dependencies:
- flake8-bugbear==24.4.26
3 changes: 2 additions & 1 deletion setup.manifest → MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
include license.md readme.md
include license.md
include readme.md
global-include *.npz
global-include *.gb
global-include *.csv
Expand Down
14 changes: 14 additions & 0 deletions contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,17 @@ Code and documentation contributions will generally proceed through a Pull Reque
- Please set up your editor to follow PEP8 (remove trailing white space, no tabs, etc.). Use [black](https://github.com/psf/black) to format your code.
- All public functions and classes require an informative docstring. Document these using the [numpydoc](https://numpydoc.readthedocs.io/en/latest/format.html) style.
- Features should ideally be grouped by category in their own folder, with implementational details in a sub-folder named `detail`. Tests for each specific category should be placed in a sub-folder `tests`.

### Installing pre-commit hooks

To help with formatting and code-style consider installing the pre-commit hooks.
These will check the formatting whenever you commit.
Install `pre-commit` with:

pip install pre-commit

Next, set up the hook with::

pre-commit install

That's all there is to it.
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
"preamble": r"\definecolor{VerbatimBorderColor}{rgb}{1,1,1}"
r"\usepackage{etoolbox}\patchcmd{\thebibliography}{\section*{\refname}}{}{}{}"
r"\usepackage{etoolbox}\patchcmd{\thebibliography}{\section*{\refname}}{}{}{}",
# Latex figure (float) alignment
# 'figure_align': 'htbp',
}
Expand Down
8 changes: 5 additions & 3 deletions lumicks/pylake/calibration.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,11 @@ def format_timestamp(timestamp):
item.kind,
f"{item.stiffness:.2f}" if item.stiffness else "N/A",
f"{item.force_sensitivity:.2f}" if item.force_sensitivity else "N/A",
f"{item.displacement_sensitivity:.2f}"
if item.displacement_sensitivity
else "N/A",
(
f"{item.displacement_sensitivity:.2f}"
if item.displacement_sensitivity
else "N/A"
),
item.hydrodynamically_correct,
item.distance_to_surface is not None,
bool(
Expand Down
1 change: 1 addition & 0 deletions lumicks/pylake/detail/mixin.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Mixin class which add properties for predefined channels"""

from ..channel import Slice, empty_slice


Expand Down
8 changes: 5 additions & 3 deletions lumicks/pylake/detail/tests/test_bead_crop.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ def test_bead_cropping_allow_negative_beads(filename, ref_edges, ref_edges_no_ne
# because the fluorescence typically tails out wide.
np.testing.assert_equal(edges, ref_edges)

with nullcontext() if ref_edges_no_negative else pytest.raises(
RuntimeError, match="Did not find two beads"
with (
nullcontext()
if ref_edges_no_negative
else pytest.raises(RuntimeError, match="Did not find two beads")
):
edges = find_beads_brightness(
np.atleast_2d(data["green"]).T,
Expand All @@ -50,7 +52,7 @@ def test_bead_cropping_failure():


def test_plotting():
data = np.load(pathlib.Path(__file__).parent / "data" / f"kymo_sum0.npz")
data = np.load(pathlib.Path(__file__).parent / "data" / "kymo_sum0.npz")
for allow_negative in (False, True):
edges = find_beads_brightness(
np.atleast_2d(data["green"]).T,
Expand Down
16 changes: 3 additions & 13 deletions lumicks/pylake/fitting/detail/model_implementation.py
Original file line number Diff line number Diff line change
Expand Up @@ -889,16 +889,8 @@ def ewlc_marko_siggia_force_jac(d, Lp, Lc, St, kT=4.11):
denom1 = Lc**3 * (Lp * St + kT) ** 2
denom2 = Lc * (Lp**2 * St**2 + 2.0 * Lp * St * kT + kT**2)
dkT = d * kT
dc_dLc = (
St**3 * dkT * (1.5 * Lc**2 - 4.5 * Lc * d + 3.0 * d**2) / (Lc**4 * (Lp * St + kT))
)
dc_dSt = (
-(St**2)
* dkT
* (2 * Lp * St + 3 * kT)
* (1.5 * Lc**2 - 2.25 * Lc * d + d**2)
/ denom1
)
dc_dLc = St**3 * dkT * (1.5 * Lc**2 - 4.5 * Lc * d + 3.0 * d**2) / (Lc**4 * (Lp * St + kT))
dc_dSt = -(St**2) * dkT * (2 * Lp * St + 3 * kT) * (1.5 * Lc**2 - 2.25 * Lc * d + d**2) / denom1
dc_dLp = St**4 * dkT * (1.5 * Lc**2 - 2.25 * Lc * d + d**2) / denom1
dc_dkT = -Lp * St**4 * d * (1.5 * Lc**2 - 2.25 * Lc * d + d**2) / denom1
db_dLc = (
Expand Down Expand Up @@ -1287,9 +1279,7 @@ def ewlc_marko_siggia_distance_derivative(f, Lp, Lc, St, kT=4.11):
)
/ (St**3 * kT)
)
db_df = (
Lc**2 * (4 * f * Lp * St + 6 * f * kT + 2 * Lp * St**2 + 4.5 * St * kT) / (St**2 * kT)
)
db_df = Lc**2 * (4 * f * Lp * St + 6 * f * kT + 2 * Lp * St**2 + 4.5 * St * kT) / (St**2 * kT)
da_df = -Lc * Lp / kT - 3 * Lc / St

return total_dy_da * da_df + total_dy_db * db_df + total_dy_dc * dc_df
6 changes: 3 additions & 3 deletions lumicks/pylake/force_calibration/calibration_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -866,9 +866,9 @@ def __init__(
bead_radius=self.bead_diameter * 1e-6 / 2.0, # um diameter -> m radius
rho_sample=self.rho_sample,
rho_bead=self.rho_bead,
distance_to_surface=None
if self.distance_to_surface is None
else self.distance_to_surface * 1e-6, # um => m
distance_to_surface=(
None if self.distance_to_surface is None else self.distance_to_surface * 1e-6
), # um => m
)
else:
self._theoretical_driving_power_model = partial(
Expand Down
16 changes: 10 additions & 6 deletions lumicks/pylake/kymotracker/kymotracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,9 +189,11 @@ def track_greedy(
bias_correction=bias_correction,
filter_width=filter_width / kymograph.pixelsize_um[0] if filter_width is not None else 0.5,
adjacency_half_width=half_width_pixels if adjacency_filter else None,
rect=_to_pixel_rect(rect, kymograph.pixelsize[0], kymograph.line_time_seconds)
if rect
else None,
rect=(
_to_pixel_rect(rect, kymograph.pixelsize[0], kymograph.line_time_seconds)
if rect
else None
),
)

if not peaks:
Expand Down Expand Up @@ -389,9 +391,11 @@ def minimum_observable_time(track, min_length, min_duration):
track._with_minimum_time(
max(
# We can't unfilter tracks.
track._minimum_observable_duration
if track._minimum_observable_duration is not None
else 0,
(
track._minimum_observable_duration
if track._minimum_observable_duration is not None
else 0
),
minimum_observable_time(track, minimum_length, minimum_duration),
)
)
Expand Down
20 changes: 12 additions & 8 deletions lumicks/pylake/kymotracker/tests/test_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,13 +198,17 @@ def test_csv_version(version, read_with_version):
)

testfile = Path(__file__).parent / f"./data/tracks_v{version}.csv"
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():
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 All @@ -228,7 +232,7 @@ def test_bad_csv(filename, blank_kymo):

def test_min_obs_csv_regression(tmpdir_factory, blank_kymo):
"""This tests a regression where saving a freshly imported older file does not function"""
testfile = Path(__file__).parent / f"./data/tracks_v0.csv"
testfile = Path(__file__).parent / "./data/tracks_v0.csv"
imported_tracks = import_kymotrackgroup_from_csv(testfile, blank_kymo, "red", delimiter=";")
out_file = f"{tmpdir_factory.mktemp('pylake')}/no_min_lengths.csv"

Expand Down
4 changes: 2 additions & 2 deletions lumicks/pylake/nb_widgets/image_editing.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ def __init__(
self.channel = channel
self.current_frame = frame
self.num_frames = self._current_image.num_frames
self.make_title = (
lambda: make_image_title(self._current_image, self.current_frame, show_name=False)
self.make_title = lambda: (
make_image_title(self._current_image, self.current_frame, show_name=False)
if show_title
else (lambda: "")
)
Expand Down
8 changes: 5 additions & 3 deletions lumicks/pylake/nb_widgets/kymotracker_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -760,9 +760,11 @@ def _create_algorithm_sliders(self):
def threshold_slider_callback(change):
self._set_label(
"warning",
""
if change["new"] > min_threshold
else f"Tracking with threshold of {change['new']} may be slow.",
(
""
if change["new"] > min_threshold
else f"Tracking with threshold of {change['new']} may be slow."
),
)

threshold_slider.observe(
Expand Down
1 change: 1 addition & 0 deletions lumicks/pylake/population/detail/fit_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class PopulationFitInfo:
log_likelihood: float
log of the maximized value of the likelihood function of the fitted data
"""

converged: bool
n_iter: int
bic: float
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,10 +290,14 @@ def process_binning(px_per_bin, axis):

for px_per_bin in range(1, ref.image.shape[0] + 1):
ref_counts, ref_bin_widths, msg = process_binning(px_per_bin, "position")
with pytest.warns(
UserWarning,
match=msg,
) if msg else nullcontext():
with (
pytest.warns(
UserWarning,
match=msg,
)
if msg
else nullcontext()
):
kymo.plot_with_position_histogram(color_channel="red", pixels_per_bin=px_per_bin)
counts, bin_widths = get_hist_data("position")

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import pathlib

import numpy as np
import pytest

Expand Down Expand Up @@ -39,43 +37,43 @@ def test_kymo_slicing(test_kymo):
np.testing.assert_allclose(sliced.get_image("red").data, ref.image[:, :, 0])

num_lines = 2
sliced = kymo["0s":f"{num_lines * line_time}s"]
sliced = kymo["0s" :f"{num_lines * line_time}s"]
assert sliced.get_image("red").shape == (ref_pixels, num_lines)
assert sliced.shape == (ref_pixels, num_lines, 3)
np.testing.assert_allclose(sliced.get_image("red").data, ref.image[:, :num_lines, 0])

sliced = kymo["0s":f"-{line_time * 0.6}s"]
sliced = kymo["0s" :f"-{line_time * 0.6}s"]
assert sliced.get_image("red").shape == (ref_pixels, ref_lines - 1)
np.testing.assert_allclose(sliced.get_image("red").data, ref.image[:, :-1, 0])

sliced = kymo["0s":f"-{2 * line_time}s"]
sliced = kymo["0s" :f"-{2 * line_time}s"]
assert sliced.get_image("red").shape == (ref_pixels, ref_lines - 2)
np.testing.assert_allclose(sliced.get_image("red").data, ref.image[:, : (ref_lines - 2), 0])

# get a sliver of the next frame
# stop needs to be > halfway the deadtime between lines
sliced = kymo["0s":f"-{2 * line_time - deadtime_slice_offset}s"]
sliced = kymo["0s" :f"-{2 * line_time - deadtime_slice_offset}s"]
assert sliced.get_image("red").shape == (ref_pixels, ref_lines - 1)
np.testing.assert_allclose(sliced.get_image("red").data, ref.image[:, :-1, 0])

# Two full frames
sliced = kymo["0s":f"{2 * line_time}s"]
sliced = kymo["0s" :f"{2 * line_time}s"]
assert sliced.get_image("red").shape == (ref_pixels, 2)
np.testing.assert_allclose(sliced.get_image("red").data, ref.image[:, :2, 0])

# Two full frames plus a bit
sliced = kymo["0s":f"{2 * scan_time + 2 * dead_time + deadtime_slice_offset}s"]
sliced = kymo["0s" :f"{2 * scan_time + 2 * dead_time + deadtime_slice_offset}s"]
assert sliced.get_image("red").shape == (ref_pixels, 3)
np.testing.assert_allclose(sliced.get_image("red").data, ref.image[:, :3, 0])

# slice from deadtime before first line until deadtime after first line
sliced = kymo[f"{scan_time + dead_time / 2}s":f"{2 * line_time - dead_time / 2}s"]
sliced = kymo[f"{scan_time + dead_time / 2}s" :f"{2 * line_time - dead_time / 2}s"]
assert sliced.get_image("red").shape == (ref_pixels, 1)
assert sliced.shape == (ref_pixels, 1, 3)
np.testing.assert_allclose(sliced.get_image("red").data, ref.image[:, 1:2, 0])

# slice over entire kymo
sliced = kymo["0s":f"{line_time * (ref.metadata.lines_per_frame + 1)}s"]
sliced = kymo["0s" :f"{line_time * (ref.metadata.lines_per_frame + 1)}s"]
assert sliced.get_image("red").shape == (ref_pixels, ref_lines)
assert sliced.shape == (ref_pixels, ref_lines, 3)
np.testing.assert_allclose(sliced.get_image("red").data, ref.image[:, :, 0])
Expand Down Expand Up @@ -245,7 +243,7 @@ def test_kymo_slice_crop(cropping_kymo):
dead_time = (ref.timestamps.dt * ref.infowave.line_padding * 2) * 1e-9
line_time = scan_time + dead_time

sliced_cropped = kymo[f"{line_time}s":f"{5 * line_time}s"].crop_by_distance(
sliced_cropped = kymo[f"{line_time}s" :f"{5 * line_time}s"].crop_by_distance(
2 * px_size, 4 * px_size
)
np.testing.assert_equal(sliced_cropped.timestamps, ref.timestamps.data[2:4, 1:5])
Expand Down
8 changes: 5 additions & 3 deletions lumicks/pylake/tests/test_imaging_confocal/test_scan.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@ def test_scan_attrs(test_scans, test_scans_multiframe):

np.testing.assert_equal(
scan.frame_timestamp_ranges(include_dead_time=True),
ref.timestamps.timestamp_ranges # For the single frame case, there is no dead time
if scan.num_frames == 1
else ref.timestamps.timestamp_ranges_deadtime,
(
ref.timestamps.timestamp_ranges # For the single frame case, there is no dead time
if scan.num_frames == 1
else ref.timestamps.timestamp_ranges_deadtime
),
)
np.testing.assert_equal(scan.frame_timestamp_ranges(), ref.timestamps.timestamp_ranges)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -255,22 +255,22 @@ def test_scan_slicing_by_time(test_scan_slicing):
wiggle = 0.01

# start of frame 2, until frame 2 signal stop
assert isinstance(multi_frame[f"{rng[2, 0]}s":f"{rng[2, 1]}s"], EmptyScan)
assert isinstance(multi_frame[f"{rng[2, 0]}s" :f"{rng[2, 1]}s"], EmptyScan)

# start of frame 2, just over frame 2 signal stop
sliced = multi_frame[f"{rng[2, 0]}s":f"{rng[2, 1] + wiggle}s"]
sliced = multi_frame[f"{rng[2, 0]}s" :f"{rng[2, 1] + wiggle}s"]
np.testing.assert_equal(sliced.get_image("rgb"), ref.image[2])

# start of frame 2, until frame 3 signal stop
sliced = multi_frame[f"{rng[2, 0]}s":f"{rng[3, 1]}s"]
sliced = multi_frame[f"{rng[2, 0]}s" :f"{rng[3, 1]}s"]
np.testing.assert_equal(sliced.get_image("rgb"), ref.image[2])

# start of frame 2, just over frame 3 signal stop
sliced = multi_frame[f"{rng[2, 0]}s":f"{rng[3, 1] + wiggle}s"]
sliced = multi_frame[f"{rng[2, 0]}s" :f"{rng[3, 1] + wiggle}s"]
np.testing.assert_equal(sliced.get_image("rgb"), ref.image[2:4])

# start of frame 2, until start of frame 4
sliced = multi_frame[f"{rng[2, 0]}s":f"{rng[4, 0]}s"]
sliced = multi_frame[f"{rng[2, 0]}s" :f"{rng[4, 0]}s"]
np.testing.assert_equal(sliced.get_image("rgb"), ref.image[2:4])

# from start of frame 2
Expand Down Expand Up @@ -332,15 +332,15 @@ def test_scan_slicing_by_time_iterative(test_scan_slicing):
wiggle = 0.01

# first slice frame 2 through 5 (inclusive)
first_slice = multi_frame[f"{rng[2, 0]}s":f"{rng[5, 1] + wiggle}s"]
first_slice = multi_frame[f"{rng[2, 0]}s" :f"{rng[5, 1] + wiggle}s"]
ref_sliced = ref.image[2:6]

# start of frame 1, just over frame 3 signal stop
sliced = first_slice[f"{rng[1, 0]}s":f"{rng[3, 1] + wiggle}s"]
sliced = first_slice[f"{rng[1, 0]}s" :f"{rng[3, 1] + wiggle}s"]
np.testing.assert_equal(sliced.get_image("rgb"), ref_sliced[1:4])

# start of frame 1, until frame 3 signal stop
sliced = first_slice[f"{rng[1, 0]}s":f"{rng[3, 1]}s"]
sliced = first_slice[f"{rng[1, 0]}s" :f"{rng[3, 1]}s"]
np.testing.assert_equal(sliced.get_image("rgb"), ref_sliced[1:3])

# from start of frame 1
Expand Down
Loading