Skip to content

Commit 63968f4

Browse files
Merge pull request #634 from jungmannlab/development
v0.9.10
2 parents 382a41c + f3cbdfb commit 63968f4

File tree

22 files changed

+394
-121
lines changed

22 files changed

+394
-121
lines changed

.bumpversion.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 0.9.9
2+
current_version = 0.9.10
33
commit = True
44
tag = False
55
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(\-(?P<release>[a-z]+)(?P<build>\d+))?

changelog.rst

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,25 @@
11
Changelog
22
=========
33

4-
Last change: 10-MAR-2026 CEST
4+
Last change: 24-MAR-2026 CEST
5+
6+
0.9.10
7+
------
8+
Important updates:
9+
^^^^^^^^^^^^^^^^^^
10+
- Added support for loading BigTIFF in Picasso Localize (#631), big thanks to @boydcpeters
11+
12+
Small improvements:
13+
+++++++++++++++++++
14+
- ``picasso.aim.aim`` accepts progress as a ``lib.ProgressDialog``, ``"console"`` or ``None``
15+
- SPINNA GUI: Small adjustment to GUI when loading search space
16+
- Adjusted label in subcluster check plot
17+
- Subcluster check plot outputs p value and test statistic
18+
19+
Bug fixes:
20+
++++++++++
21+
- Fixed AIM in Localize GUI
22+
- Fixed saving search space in SPINNA for multiple-target structures
523

624
0.9.8-9
725
-------

distribution/picasso.iss

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
AppName=Picasso
33
AppPublisher=Jungmann Lab, Max Planck Institute of Biochemistry
44

5-
AppVersion=0.9.9
5+
AppVersion=0.9.10
66
DefaultDirName={commonpf}\Picasso
77
DefaultGroupName=Picasso
8-
OutputBaseFilename="Picasso-Windows-64bit-0.9.9"
8+
OutputBaseFilename="Picasso-Windows-64bit-0.9.10"
99
ArchitecturesAllowed=x64
1010
ArchitecturesInstallIn64BitMode=x64
1111

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
# The short X.Y version
2727
version = ""
2828
# The full version, including alpha/beta/rc tags
29-
release = "0.9.9"
29+
release = "0.9.10"
3030

3131
# -- General configuration ---------------------------------------------------
3232

docs/localize.rst

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,15 @@ Localize allows performing super-resolution reconstruction of image stacks. For
1111
- LQ, Gaussian (least squares)
1212
- Average of ROI (finds summed intensity of spots)
1313

14-
**Please note:** Picasso Localize supports five file formats: ``.ome.tif``, ``NDTiffStack`` with extension ``.tif``, ``.raw``, ``.ims`` and ``.nd2``. If your file has the extension ``.tiff`` or ``.ome.tiff``, it cannot be read. Usually it is enough to change the extension to ``.ome.tif``, i.e., remove the last letter.
14+
**Please note:** Picasso Localize supports file formats:
15+
16+
- ``.ome.tif``, including BigTiff (as of Picasso 0.9.10),
17+
- ``NDTiffStack`` with extension ``.tif``,
18+
- ``.raw``,
19+
- ``.ims``,
20+
- ``.nd2``.
21+
22+
There is limited support for ``.tiff`` files.
1523

1624
Identification and fitting of single-molecule spots
1725
---------------------------------------------------

docs/render.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ To account for fluorophore non-specific sticking, frame analysis is normally rec
9797

9898
The final postprocessing step is log-likelihood filtering (using the column ``p_val``). The recommended threshold is ``> 0.0015``, however, it might need to be adjusted for your data, especially in 3D this can be too conservative.
9999

100-
As a final check for overfitting (i.e., too many assigned molecules), G5M automatically saves a bar plot of the number of binding events per molecule (``n_events`` column) for sparse (with neighbors within 25 nm) and clustered (without neighbors within 80 nm) molecules. While this is only a qualitative verification, it is a simple method to spot potential overfitting issues. If the clustered molecules show fewer binding events that the sparse molecules, overfitting likely occurred. See Fig. S15 of the publication for an example of well-behaved data. As of v0.9.8, Picasso saves the plot showing relative σ values (i.e., the fitted Gaussian σ divided by the average loc. precision around the molecule). This can be used to estimate if the loc. precision values are accurate (if not, many molecules will have relative σ values close to the user-selected min./max. σ).
100+
As a final check for overfitting (i.e., too many assigned molecules), G5M automatically saves a bar plot of the number of binding events per molecule (``n_events`` column) for clustered (with neighbors within 25 nm) and sparse (without neighbors within 80 nm) molecules. If the clustered molecules show fewer binding events that the sparse molecules, overfitting likely occurred. See Fig. S15 of the publication for an example of well-behaved data. As of v0.9.8, Picasso saves the plot showing relative σ values (i.e., the fitted Gaussian σ divided by the average loc. precision around the molecule). This can be used to estimate if the loc. precision values are accurate (if not, many molecules will have relative σ values close to the user-selected min./max. σ). As of version 0.9.10, Picasso runs KS 2 sample test to compare the two distributions. The output test statistic and theoretical p value correspond to the KS test, while the permutation p value is calculated by randomly permuting the labels of clustered and sparse molecules 1,000 times and calculating the fraction of permutations that result in a KS test statistic as extreme as the one observed with the original labels.
101101

102102
If the outcome of G5M seems unsatisfactory, please check the following:
103103

picasso/__main__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,7 @@ def _undrift_aim(
514514
segmentation,
515515
intersectdist,
516516
roiradius,
517+
progress="console",
517518
)
518519
base, ext = os.path.splitext(path)
519520
io.save_locs(base + "_aim.hdf5", locs, new_info)

picasso/aim.py

Lines changed: 32 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,9 @@ def intersection_max(
383383
roi_r: float,
384384
width: int,
385385
aim_round: int = 1,
386-
progress: Callable[[int], None] | Literal["console"] | None = None,
386+
progress: (
387+
lib.ProgressDialog | lib.TqdmProgress | lib.MockProgress | None
388+
) = None,
387389
) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
388390
"""Maximize intersection (undrift) for 2D localizations.
389391
@@ -410,9 +412,10 @@ def intersection_max(
410412
as reference, the second round uses the entire dataset as
411413
reference. The impact is that in the second round, the first
412414
interval is also undrifted.
413-
progress : lib.ProgressDialog or "console" or None, optional
414-
Progress dialog. If "console", progress is displayed with tqdm.
415-
If None, progress is not displayed. Default is None.
415+
progress : lib.ProgressDialog | lib.TqdmProgress | lib.MockProgress \
416+
| None, optional
417+
Progress dialog. If TqdmProgress, progress is displayed with tqdm.
418+
If None or MockProgress, progress is not displayed. Default is None.
416419
417420
Returns
418421
-------
@@ -458,14 +461,7 @@ def intersection_max(
458461

459462
# initialize progress such that if GUI is used, tqdm is omitted
460463
start_idx = 1 if aim_round == 1 else 0
461-
if progress != "console":
462-
iterator = range(start_idx, n_segments)
463-
else:
464-
iterator = tqdm(
465-
range(start_idx, n_segments),
466-
desc=f"Undrifting ({aim_round}/2)",
467-
unit="segment",
468-
)
464+
iterator = progress.get_iterator(start_idx, n_segments)
469465

470466
# run across each segment
471467
for s in iterator:
@@ -509,10 +505,7 @@ def intersection_max(
509505
drift_y[s] = -rel_drift_y
510506

511507
# update progress
512-
if progress != "console":
513-
progress.set_value(s)
514-
else:
515-
iterator.update(s - iterator.n)
508+
progress.set_value(s)
516509

517510
# interpolate the drifts (cubic spline) for all frames
518511
t = (seg_bounds[1:] + seg_bounds[:-1]) / 2
@@ -544,7 +537,9 @@ def intersection_max_z(
544537
height: int,
545538
pixelsize: float,
546539
aim_round: int = 1,
547-
progress: Callable[[int], None] | None = None,
540+
progress: (
541+
lib.ProgressDialog | lib.TqdmProgress | lib.MockProgress | None
542+
) = None,
548543
) -> tuple[np.ndarray, np.ndarray]:
549544
"""Maximize intersection (undrift) for 3D localizations. Assumes
550545
that x and y coordinates were already undrifted. x and y are in
@@ -583,14 +578,7 @@ def intersection_max_z(
583578

584579
# initialize progress such that if GUI is used, tqdm is omitted
585580
start_idx = 1 if aim_round == 1 else 0
586-
if progress is not None:
587-
iterator = range(start_idx, n_segments)
588-
else:
589-
iterator = tqdm(
590-
range(start_idx, n_segments),
591-
desc=f"Undrifting z ({aim_round}/2)",
592-
unit="segment",
593-
)
581+
iterator = progress.get_iterator(start_idx, n_segments)
594582

595583
# run across each segment
596584
for s in iterator:
@@ -632,10 +620,7 @@ def intersection_max_z(
632620
drift_z[s] = -rel_drift_z
633621

634622
# update progress
635-
if progress is not None:
636-
progress.set_value(s)
637-
else:
638-
iterator.update(s - iterator.n)
623+
progress.set_value(s)
639624

640625
# interpolate the drifts (cubic spline) for all frames
641626
t = (seg_bounds[1:] + seg_bounds[:-1]) / 2
@@ -659,7 +644,7 @@ def aim(
659644
segmentation: int = 100,
660645
intersect_d: float = 20 / 130,
661646
roi_r: float = 60 / 130,
662-
progress: Callable[[int], None] | None = None,
647+
progress: lib.ProgressDialog | Literal["console"] | None = None,
663648
) -> tuple[pd.DataFrame, list[dict], pd.DataFrame]:
664649
"""Apply AIM undrifting to the localizations.
665650
@@ -676,9 +661,9 @@ def aim(
676661
roi_r : float
677662
Radius of the local search region in camera pixels. Should be
678663
larger than the maximum expected drift within segmentation.
679-
progress : picasso.lib.ProgressDialog, optional
680-
Progress dialog. If None, progress is displayed with into the
681-
console. Default is None.
664+
progress : picasso.lib.ProgressDialog or "console" or None, optional
665+
Progress dialog. If "console", progress is displayed in the
666+
console. If None, no progress is displayed. Default is None.
682667
683668
Returns
684669
-------
@@ -689,6 +674,16 @@ def aim(
689674
drift : pd.DataFrame
690675
Drift in x and y directions (and z if applicable).
691676
"""
677+
assert (
678+
progress is None
679+
or progress == "console"
680+
or isinstance(progress, lib.ProgressDialog)
681+
), "progress must be None, 'console', or a ProgressDialog instance."
682+
if progress is None:
683+
progress = lib.MockProgress()
684+
elif progress == "console":
685+
progress = lib.TqdmProgress(description="Undrifting by AIM (1/2)")
686+
692687
locs = locs.copy()
693688
# extract metadata
694689
width = lib.get_from_metadata(info, "Width", raise_error=True)
@@ -724,8 +719,7 @@ def aim(
724719
progress=progress,
725720
)
726721
# the second run is with the entire dataset as reference
727-
if progress is not None:
728-
progress.zero_progress(description="Undrifting by AIM (2/2)")
722+
progress.zero_progress(description="Undrifting by AIM (2/2)")
729723
x_pdc, y_pdc, drift_x2, drift_y2 = intersection_max(
730724
x_pdc,
731725
y_pdc,
@@ -754,8 +748,7 @@ def aim(
754748

755749
# 3D undrifting
756750
if "z" in locs.columns:
757-
if progress is not None:
758-
progress.zero_progress(description="Undrifting z (1/2)")
751+
progress.zero_progress(description="Undrifting z (1/2)")
759752
ref_x = x_pdc[frame <= segmentation]
760753
ref_y = y_pdc[frame <= segmentation]
761754
ref_z = locs["z"][frame <= segmentation]
@@ -776,8 +769,7 @@ def aim(
776769
aim_round=1,
777770
progress=progress,
778771
)
779-
if progress is not None:
780-
progress.zero_progress(description="Undrifting z (2/2)")
772+
progress.zero_progress(description="Undrifting z (2/2)")
781773
z_pdc, drift_z2 = intersection_max_z(
782774
x_pdc,
783775
y_pdc,
@@ -812,16 +804,12 @@ def aim(
812804
locs["y"] = y_pdc
813805
if "z" in locs.columns:
814806
locs["z"] = z_pdc
815-
816807
new_info = {
817808
"Generated by": f"Picasso v{__version__} AIM",
818809
"Intersect distance (nm)": intersect_d * pixelsize,
819810
"Segmentation": segmentation,
820811
"Search regions radius (nm)": roi_r * pixelsize,
821812
}
822813
new_info = info + [new_info]
823-
824-
if progress is not None:
825-
progress.close()
826-
814+
progress.close()
827815
return locs, new_info, drift

picasso/clusterer.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -994,9 +994,7 @@ def test_subclustering(
994994
assert sparse_dist > clustering_dist, (
995995
"The sparse distance must be larger than the clustering " "distance."
996996
)
997-
pixelsize = lib.get_from_metadata(info, "Pixelsize")
998-
if pixelsize is None:
999-
raise ValueError("Pixelsize not found in metadata.")
997+
pixelsize = lib.get_from_metadata(info, "Pixelsize", raise_error=True)
1000998

1001999
# get 1st nearest neighbor distances
10021000
if "z" in mols.columns:

picasso/gui/filter.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -558,7 +558,11 @@ def plot(self) -> None:
558558
)
559559
df.to_csv(path, index=False)
560560
fig, ax = lib.plot_subclustering_check(
561-
clustered_nevents, sparse_nevents, return_fig=True
561+
clustered_nevents,
562+
sparse_nevents,
563+
return_fig=True,
564+
clustering_dist=dist_clustered,
565+
sparse_dist=dist_sparse,
562566
)
563567
plt.show()
564568

0 commit comments

Comments
 (0)