Skip to content

Commit 2db19a4

Browse files
authored
Merge pull request #8 from gletort/epidev Tests, Open from layers, Vertices measures and Bug correction
Tests, Open from layers, Vertices measures and Bug correction Add and updated tests file Add option to open first layers in Napari with other plugins, then start EpiCure from it Add option in Output to get and measure the vertices (TCJ) Bug corrections, especially in measuring junction intensity in an other channel than the main one
2 parents adcd9d9 + 4e731e0 commit 2db19a4

14 files changed

Lines changed: 472 additions & 57 deletions

docs/Output.md

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,6 @@
11
!!! abstract "Options to measure and export segmentation/events/tracks"
22
_Measurement and plotting options are/will be proposed here, as well as options to export the segmentation/measurements to use in other analysis softwares_
33

4-
* [Apply on: define current selection](#define-current-selection)
5-
* [Export to extern plugins](#export-to-other-plugins)
6-
* [Export segmentations](#save-segmentations)
7-
* [Export events](#export-events)
8-
* [Measure cell features](#measure-cell-features)
9-
* [Measure track features](#measure-track-features)
10-
11-
124
## Define current selection
135

146
You can choose which cells to analyse/export with the `Apply on` option in the interface:
@@ -188,4 +180,22 @@ This allows to save screenshots of the current display/current view (same part o
188180
Choose the first and last frame of the screenshot movie and click on `save current view`.
189181
This will creates a `.tif` file in the `epics` folder containing the screenshots of the current view repeated on the given frames.
190182

191-
![interface of screenshot movies](./imgs/output_screenshot.png)
183+
![interface of screenshot movies](./imgs/output_screenshot.png)
184+
185+
## Measure vertices
186+
187+
Find the vertices, points of junction of several cells (Tri-Cellular junctions or more), display it and measure intensity of the raw movie channel and the number of cells joining (the connectivity) at each vertex.
188+
189+
Choose the option `measure vertices` to launch it.
190+
191+
The parameter `vertex_radius` set the radius of a vertex, to find it (depending on the resolution, it can be bigger than a single pixel) and to measure the intensity inside the vertex (you might want to measure in a small circle rather than in only one point).
192+
193+
The parameter `vertex_display` set the radius at which each point is displayed in the viewer.
194+
195+
Click on `Measure` to launch the detection of vertices and their measures. It can take a few minutes.
196+
197+
When it's finished, it will display a new layer, called "Vertices" that contained the defined vertices, colored by their connectivity measure (number of cells joining in this point) and of a size controlled by the `vertex_display` parameter (and not the one used for measurement, for clarity of visualization).
198+
199+
The measures of each vertex position, connectivity (nb of neighbor cell joining) and intensity in the raw movie channel (the junction channel) are displayed in a table in the right side of the interface that can be saved for further analysis on the vertices connectivity or intensity distributions.
200+
201+
![interface of vertices measurement](./imgs/output_vertices.png)

docs/Start-epicure.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
!!! abstract "Start EpiCure: load the movies and metadata"
22
_Choose `Start epicure` in Napari `Plugins>Epicure` to open it._
33

4+
EpiCure handles several inputs format through the `bioio` module.
5+
However, if some format is not correctly recongized/handled by EpiCure, you can open the images in Napari with any other plugin (for example [napari-bioformats](https://github.com/tlambert03/napari-bioformats), [napari-aicsimageio](https://github.com/AllenCellModeling/napari-aicsimageio)) and use the `Start from open layers` option (see more [here](#start-from-opened-layers)).
6+
47
## Loading the movies
58

69
In the `Start EpiCure` step, you have a dedicated interface in the right part of the main interface.
@@ -48,4 +51,23 @@ Depending on python/module versions and the input file itself, all the informati
4851

4952
While EpiCure is optimized for a graphical usage as its scope relies on easing manual edition of segmentation, there's a possbility to launch it without starting Napari first (or at all).
5053
This can be useful for automatic testing or batching some process.
51-
See our released [notebooks](https://github.com/gletort/Epicure/tree/main/notebooks/) for example of usage or our [test files](https://github.com/gletort/Epicure/tree/main/src/tests/).
54+
See our released [notebooks](https://github.com/gletort/Epicure/tree/main/notebooks/) for example of usage or our [test files](https://github.com/gletort/Epicure/tree/main/src/tests/).
55+
56+
## Start from opened layers
57+
58+
To make EpiCure compatible with as many input formats as possible, we added the option to start it from already opened layers, so that the images can be opened with other readers than the one proposed in EpiCure if necessary.
59+
60+
Both the raw image and the segmentation can be opened before to start EpiCure, or only the raw image.
61+
62+
In `Plugins>EpiCure`, choose the option `Start from opened layers`.
63+
64+
In the parameters interface that open on the right side of the viewer, select the layer that corresponds to your raw movie (the layer containing the junction staining) in the `movie` parameter.
65+
66+
Select the raw file of the movie in the `movie path` parameter.
67+
This will not open it as it is already opened, but is necessary to know the path and name of the image to save correspondingly the output files.
68+
69+
Select the layer containing the segmentation in the `segmentation` parameter if you have one, or select `None` otherwise.
70+
71+
![interface to start Epicure from open layers](./imgs/start_from_layers.png)
72+
73+
Once the parameters are properly filled, click `Use selected layers` and you will be back to the "normal" EpiCure [starting interface](#movie-metadata) to check the image metadata and choose the main parameters of your analysis.

docs/imgs/output_vertices.png

437 KB
Loading

docs/imgs/start_from_layers.png

320 KB
Loading

docs/index.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ You can launch `EpiCure` in Napari by going to `Plugins>epicure>Start`. It will
1313
The first file to choose is the movie containing the epithelial staining.
1414
It should be _2D(+time/channel) file_.
1515

16+
!!! Note "Input format"
17+
EpiCure relies on `bioio` module to open images, compatible with various formats.
18+
Note that if your file format is not correctly handled by EpiCure, you can first open the image with other plugins and start EpiCure from the opened layer(s). See [Start EpiCure page](./Start-epicure.md#start-from-opened-layers) for more details.
19+
20+
1621
The second file is the segmentation of this movie (it should contain only the segmentation). It can be a binarized file of the junctions (skeletonized) or a labelled file (each cell is filled by a unique number).
1722
_Note that if you haven't done the segmentation yet, there's an [additional option](./Segment-option.md) in EpiCure to directly run [EpySeg](https://github.com/baigouy/EPySeg) on the loaded movie._
1823

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "epicure"
7-
version = "1.3"
7+
version = "1.4"
88
description = "Napari plugin to manually correct epithelia segmentation in movies"
99
license.file = "LICENSE"
1010
readme = "README.md"

src/epicure/Utils.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import os, sys
77
import time
88
import math
9-
from skimage.measure import regionprops, find_contours, regionprops_table
9+
from skimage.measure import label, regionprops, find_contours, regionprops_table
1010
from skimage.segmentation import find_boundaries, expand_labels
1111
from napari.utils.translations import trans # type: ignore
1212
from napari.utils.notifications import show_info # type: ignore
@@ -17,6 +17,8 @@
1717
from scipy.ndimage import binary_opening as ndbinary_opening
1818
from scipy.ndimage import sum as ndsum
1919
from scipy.ndimage import generate_binary_structure as ndi_structure
20+
from scipy import signal
21+
from skimage.morphology import medial_axis
2022
import pandas as pd
2123
from epicure.laptrack_centroids import LaptrackCentroids
2224
from bioio import BioImage
@@ -406,6 +408,32 @@ def copy_border( skel, bin ):
406408
skel[[0, -1], :] = bin[[0, -1], :] # top and bottom borders
407409
skel[:, [0, -1]] = bin[:, [0, -1]] # left and right borders
408410
return skel
411+
412+
def draw_points(pts, imshape, radius):
413+
""" Draw circle (2D) around the given points in 2D image """
414+
image = np.zeros(imshape, dtype=bool)
415+
y, x = np.ogrid[:imshape[0], :imshape[1]]
416+
for pt in pts:
417+
# Calculate distance from pt, scaled to compare to radius
418+
distances_sq = ((y - pt[0]))**2 + ((x - pt[1]))**2
419+
image |= distances_sq <= radius**2
420+
return image
421+
422+
def get_vertices(seg, viewer=None, verbose=0, parallel=0):
423+
""" Get the vertices of the segmentation """
424+
skeleton = get_skeleton(seg, viewer, verbose, parallel)
425+
convfilter = np.array([[-1,-1,-1], [-1,3,-1],[-1,-1,-1]])
426+
novert = np.zeros(skeleton.shape, dtype=np.int8)
427+
## pure skeleton
428+
for ind, skel in enumerate(skeleton):
429+
skeleton[ind] = medial_axis(skel)
430+
novert[ind] = signal.convolve2d(skeleton[ind], convfilter, mode="same")
431+
novert[novert<=0] = 0
432+
nodeimg = skeleton - novert
433+
nodeimg[nodeimg<=0] = 0
434+
nodeimg[nodeimg>0] = 1
435+
return nodeimg
436+
409437

410438
def get_skeleton( seg, viewer=None, verbose=0, parallel=0 ) :
411439
""" convert labels movie to skeleton (thin boundaries) """
@@ -890,6 +918,10 @@ def get_most_frequent( labimg, img, label ):
890918
vals, counts = np.unique( img[mask], return_counts=True )
891919
return vals[ np.argmax(counts) ]
892920

921+
def binary_properties( labimg ):
922+
""" Returns basic label properties """
923+
return regionprops( label(labimg) )
924+
893925
def labels_properties( labimg ):
894926
""" Returns basic label properties """
895927
return regionprops( labimg )

src/epicure/_version.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,5 @@
1717
__version_tuple__: VERSION_TUPLE
1818
version_tuple: VERSION_TUPLE
1919

20-
__version__ = version = '0.1.dev93+g99627b3.d20260109'
21-
__version_tuple__ = version_tuple = (0, 1, 'dev93', 'g99627b3.d20260109')
20+
__version__ = version = '0.1.dev103+g64f1ff3.d20260123'
21+
__version_tuple__ = version_tuple = (0, 1, 'dev103', 'g64f1ff3.d20260123')

src/epicure/epicuring.py

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,44 @@ def get_resetbtn_color(self):
9494
def set_thickness(self, thick):
9595
"""Thickness of junctions (half thickness)"""
9696
self.thickness = thick
97+
98+
def movie_from_layer(self, layer, imgpath):
99+
"""Prepare the intensity movie from opened layer, and get metadata"""
100+
self.reset() ## reload everything
101+
self.epi_metadata["MovieFile"] = os.path.abspath(imgpath)
102+
## if the layer is scaled, should be the right scale
103+
self.epi_metadata["ScaleXY"] = layer.scale[2]
104+
self.img = layer.data
105+
nchan = 0
106+
if len(self.img.shape)>3:
107+
## Format TCYX in general
108+
nchan = self.img.shape[1]
109+
## transform static image to movie (add temporal dimension)
110+
if len(self.img.shape) == 2:
111+
self.img = np.expand_dims(self.img, axis=0)
112+
caxis = None
113+
cval = 0
114+
if nchan > 0 or len(self.img.shape) > 3:
115+
if nchan > 0 and len(self.img.shape) > 3:
116+
## multiple chanels and multiple slices, order axis should be TCXY
117+
caxis = 1
118+
cval = nchan
119+
else:
120+
## one image with multiple chanels
121+
minshape = min(self.img.shape)
122+
caxis = self.img.shape.index(minshape)
123+
cval = minshape
124+
self.mov = self.img
125+
126+
## display the movie: rename the layer
127+
ut.remove_layer(self.viewer, "Movie")
128+
layer.name = "Movie"
129+
130+
self.imgshape = self.viewer.layers["Movie"].data.shape
131+
self.imgshape2D = self.imgshape[1:3]
132+
self.nframes = self.imgshape[0]
133+
return caxis, cval
134+
97135

98136
def load_movie(self, imgpath):
99137
"""Load the intensity movie, and get metadata"""
@@ -208,11 +246,21 @@ def add_other_chanels(self, chan, chanaxis):
208246
mview.gamma=0.95
209247
mview.visible = False
210248

211-
def load_segmentation(self, segpath):
249+
def load_segmentation(self, seg_input):
212250
"""Load the segmentation file"""
213251
start_time = ut.start_time()
252+
## compatibility to string input, the path to the image or a dictionnary
253+
if isinstance(seg_input, dict):
254+
segpath = seg_input["File"]
255+
else:
256+
segpath = seg_input
214257
self.epi_metadata["SegmentationFile"] = segpath
215-
self.seg, _, _, _, _, _ = ut.open_image(segpath, get_metadata=False, verbose=self.verbose > 1)
258+
if isinstance(seg_input, dict) and "Layer" in seg_input:
259+
## take the segmentation data and close it
260+
self.seg = seg_input["Layer"].data
261+
ut.remove_layer(self.viewer, seg_input["Layer"])
262+
else:
263+
self.seg, _, _, _, _, _ = ut.open_image(segpath, get_metadata=False, verbose=self.verbose > 1)
216264
self.seg = np.uint32(self.seg)
217265
## transform static image to movie (add temporal dimension)
218266
if len(self.seg.shape) == 2:
@@ -289,17 +337,21 @@ def set_names(self, outdir):
289337
"""Extract default names from imgpath"""
290338
self.imgname, self.imgdir, self.outdir = ut.extract_names(self.epi_metadata["MovieFile"], outdir, mkdir=True)
291339

292-
def go_epicure(self, outdir="epics", segmentation_file=None):
340+
def go_epicure(self, outdir="epics", segmentation_input=None):
293341
"""Initialize everything and start the main widget"""
294342
self.set_names(outdir)
295-
if segmentation_file is None:
296-
segmentation_file = self.suggest_segfile(outdir)
343+
if segmentation_input is None:
344+
segmentation_input = {}
345+
segmentation_input["File"] = self.suggest_segfile(outdir)
297346
self.viewer.window._status_bar._toggle_activity_dock(True)
298347
progress_bar = progress(total=5)
299348
progress_bar.set_description("Reading segmented image")
300349
## load the segmentation
301-
self.load_segmentation(segmentation_file)
302-
self.epi_metadata["SegmentationFile"] = segmentation_file
350+
self.load_segmentation(segmentation_input)
351+
if isinstance(segmentation_input, dict):
352+
self.epi_metadata["SegmentationFile"] = segmentation_input["File"]
353+
else:
354+
self.epi_metadata["SegmentationFile"] = segmentation_input
303355
progress_bar.update(1)
304356
ut.set_active_layer(self.viewer, "Segmentation")
305357

src/epicure/napari.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ contributions:
66
- id: epicure.start
77
python_name: epicure.start_epicuring:start_epicure
88
title: Start
9+
- id: epicure.start_from_layers
10+
python_name: epicure.start_epicuring:start_from_layers
11+
title: Start from opened layers
912
- id: epicure.concatenate
1013
python_name: epicure.concatenate_movie:concatenate_movies
1114
title: Concatenate
@@ -18,6 +21,8 @@ contributions:
1821
widgets:
1922
- command: epicure.start
2023
display_name: Start EpiCure
24+
- command: epicure.start_from_layers
25+
display_name: Start from opened layer(s)
2126
- command: epicure.concatenate
2227
display_name: Concatenate EpiCured movies
2328
- command: epicure.doc

0 commit comments

Comments
 (0)