Skip to content

Commit 29f39b7

Browse files
authored
Merge pull request #11 from gletort/epidev
Epidev - import/export TM - Shortcuts Mac
2 parents 2db19a4 + 3d1feb3 commit 29f39b7

14 files changed

Lines changed: 2237 additions & 28 deletions

docs/Inspect.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ Division events are detected based on the tracking graph. If cells has not been
3535
(Un)Select the option `show division` to (un)display division events. You can see the total number of divisions found written in the top of the `Inspect` panel.
3636

3737
You can manually add a division event, or remove one.
38+
3839
* To remove a division, the shortcut is the same as for removing any event: by default, press <kbd>Control+Alt+Right-click</kbd> on the event to remove.
40+
3941
* To add a divison, do <kbd>Control+Shift+Left clikc</kbd> from one daughter cell to the other. EpiCure will detect automatically the most likely mother cell.
4042
If suspect events had been found on the two daughter cells or on the mother cell, they will be automatically removed when manually adding the division.
4143

docs/Start-epicure.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,20 @@ However, if some format is not correctly recongized/handled by EpiCure, you can
88

99
In the `Start EpiCure` step, you have a dedicated interface in the right part of the main interface.
1010

11+
### Raw movie
1112
The first file to choose is the movie containing the epithelial staining, with the `image file` parameter.
1213
It should be _2D(+time/channels) file_. 3D is not handled. If a file has a Z dimension and no temporal dimension, EpiCure will swap them and use the Z axis as time.
1314

1415
If the file contains several chanels, the plugin should detect it and show the `junction_chanel` parameter, to choose the chanel that contains the junction staining. The plugin will display only this chanel. If you wish to also see the other chanels, use the option `show other chanels` in the [advanced parameters](#advanced-parameters) panel.
1516

16-
The second file is the segmentation of this movie (it should only contain the segmentation), to select with the `segmentation file` parameter. It can be a binarized file of the junctions (skeletonized) or a labelled file (each cell is filled by a unique number).
17+
### Segmentation/Tracks
18+
The second file is the segmentation or tracks of this movie (it should only contain the segmentation), to select with the `segmentation file` parameter. It can be a binarized file of the junctions (skeletonized), a labelled file (each cell is filled by a unique number), or a TrackMate `.xml` file.
19+
20+
If it is a TrackMate file, the tracks and the divisions will be loaded directly from it.
1721

1822
_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._
1923

20-
If the input movie file had already been processed with EpiCure previously (and saved), EpiCure will automatically propose to load the saved file and reload the previous parameters. You can directly click `START CURE` in this case.
24+
If the input movie file had already been processed with EpiCure previously (and saved), EpiCure will **automatically propose to load the saved file** and reload the previous parameters. You can directly click `START CURE` in this case.
2125

2226
![start interface](./imgs/starting.png)
2327

docs/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ It should be _2D(+time/channel) file_.
1818
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.
1919

2020

21-
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).
21+
The second file is the segmentation of this movie (it should contain only the segmentation) or a TrackMate file (`.xml` file). It can be a binarized file of the junctions (skeletonized), a labelled file (each cell is filled by a unique number) or the `.xml` file generated by TrackMate.
2222
_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._
2323

2424
If the input movie file had already been processed with EpiCure previously (and saved), EpiCure will automatically propose to load the saved file and reload the previous parameters. You can directly click `START CURE` in this case.

pyproject.toml

Lines changed: 2 additions & 2 deletions
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.4"
7+
version = "1.4.5"
88
description = "Napari plugin to manually correct epithelia segmentation in movies"
99
license.file = "LICENSE"
1010
readme = "README.md"
@@ -40,7 +40,7 @@ dependencies = [
4040
"imagecodecs",
4141
"edt",
4242
"packaging",
43-
"laptrack",
43+
"laptrack>=0.15.0",
4444
"joblib",
4545
]
4646

src/epicure/Utils.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import numpy as np
66
import os, sys
7+
from sys import platform
78
import time
89
import math
910
from skimage.measure import label, regionprops, find_contours, regionprops_table
@@ -21,7 +22,6 @@
2122
from skimage.morphology import medial_axis
2223
import pandas as pd
2324
from epicure.laptrack_centroids import LaptrackCentroids
24-
from bioio import BioImage
2525
import tifffile as tif # type: ignore
2626
import napari
2727
from napari.utils import progress # type: ignore
@@ -79,6 +79,10 @@ def close_progress( viewer, progress_bar ):
7979
progress_bar.close()
8080
show_progress( viewer, False)
8181

82+
def version_above( module, version ):
83+
""" Compare if python module is above a given version """
84+
return Version(module.__version__) > Version(version)
85+
8286
#### Handle versions of napari
8387
def version_napari_above( compare_version ):
8488
""" Compare if the current version of napari is above given version """
@@ -135,6 +139,7 @@ def create_text_window( name ):
135139
blabla.show()
136140
return blabla
137141

142+
138143
def napari_shortcuts():
139144
""" Write main napari shortcuts list """
140145
text = "---- Main napari default shortcuts ----\n"
@@ -259,6 +264,7 @@ def open_image(imagepath, get_metadata=False, verbose=True):
259264
print("Opening Tif image "+str(imagepath)+" with bioio-tifffile")
260265
import bioio_tifffile
261266
if version_python_minor(10):
267+
from bioio import BioImage
262268
img = BioImage(imagepath, reader=bioio_tifffile.Reader)
263269
else:
264270
## python 3.9 or under
@@ -270,6 +276,7 @@ def open_image(imagepath, get_metadata=False, verbose=True):
270276
if verbose:
271277
print("Opening "+extension+" image "+str(imagepath)+" with bioio-bioformats")
272278
if version_python_minor(10):
279+
from bioio import BioImage
273280
img = BioImage(imagepath, reader=bioio_bioformats.Reader)
274281
else:
275282
## python 3.9 or under
@@ -1111,6 +1118,13 @@ def shortcut_click_match( shortcut, event ):
11111118
if len(event.modifiers) > 0:
11121119
return False
11131120
return True
1121+
1122+
def is_darwin():
1123+
""" Test if OS is MacOS or not """
1124+
try:
1125+
return platform.lower() == "darwin"
1126+
except:
1127+
return False
11141128

11151129
def print_shortcuts( shortcut_group ):
11161130
""" Put to text the subset of shortcuts """
@@ -1123,7 +1137,13 @@ def print_shortcuts( shortcut_group ):
11231137
if "modifiers" in vals.keys():
11241138
modifiers = vals["modifiers"]
11251139
for mod in modifiers:
1126-
modif += mod+"-"
1140+
if mod == "Control":
1141+
if is_darwin():
1142+
modif += "Command"+"-"
1143+
else:
1144+
modif += mod+"-"
1145+
else:
1146+
modif += mod+"-"
11271147
text += " <"+modif+vals["button"]+"-click> "+vals["text"]+"\n"
11281148
return text
11291149

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.dev103+g64f1ff3.d20260123'
21-
__version_tuple__ = version_tuple = (0, 1, 'dev103', 'g64f1ff3.d20260123')
20+
__version__ = version = '0.1.dev135+g785d2b9.d20260216'
21+
__version_tuple__ = version_tuple = (0, 1, 'dev135', 'g785d2b9.d20260216')

src/epicure/epicuring.py

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from skimage.morphology import skeletonize
88
from skimage.measure import regionprops
99
from joblib import Parallel, delayed
10+
from pathlib import Path
1011

1112
import epicure.Utils as ut
1213
from epicure.editing import Editing
@@ -15,6 +16,7 @@
1516
from epicure.outputing import Outputing
1617
from epicure.displaying import Displaying
1718
from epicure.preferences import Preferences
19+
import epicure.tm_loader as tm
1820

1921
"""
2022
EpiCure main
@@ -246,9 +248,28 @@ def add_other_chanels(self, chan, chanaxis):
246248
mview.gamma=0.95
247249
mview.visible = False
248250

251+
def import_trackmate(self, segpath, verbose=0):
252+
""" Load segmentation and tracks from TrackMate XML file """
253+
if verbose > 1:
254+
print("Importing segmentation and tracks from TrackMate XML file")
255+
np.set_printoptions(suppress=True, floatmode="maxprec_equal")
256+
257+
img_data_tag = tm._get_ImageData_tag(segpath)
258+
metadata = tm._get_metadata(img_data_tag)
259+
seg_shape = (int(metadata["nframes"]), int(metadata["height"]), int(metadata["width"]))
260+
segmentation = np.zeros(seg_shape, dtype=np.uint16)-1
261+
positions, tracks = tm._parse_Model_tag(segpath, metadata, segmentation)
262+
label_mapping = tm._build_label_mapping(positions, tracks)
263+
positions = tm.relabel_positions(label_mapping, positions)
264+
tracks = tm.relabel_tracks(label_mapping, tracks)
265+
segmentation = tm.relabel_segmentation(label_mapping, segmentation)
266+
return segmentation, tracks
267+
268+
249269
def load_segmentation(self, seg_input):
250270
"""Load the segmentation file"""
251271
start_time = ut.start_time()
272+
self.graph = None ## no loaded graph
252273
## compatibility to string input, the path to the image or a dictionnary
253274
if isinstance(seg_input, dict):
254275
segpath = seg_input["File"]
@@ -260,7 +281,11 @@ def load_segmentation(self, seg_input):
260281
self.seg = seg_input["Layer"].data
261282
ut.remove_layer(self.viewer, seg_input["Layer"])
262283
else:
263-
self.seg, _, _, _, _, _ = ut.open_image(segpath, get_metadata=False, verbose=self.verbose > 1)
284+
if str(segpath).endswith(".xml"):
285+
## import a TrackMate file
286+
self.seg, self.graph = self.import_trackmate(segpath, verbose=self.verbose>1)
287+
else:
288+
self.seg, _, _, _, _, _ = ut.open_image(segpath, get_metadata=False, verbose=self.verbose > 1)
264289
self.seg = np.uint32(self.seg)
265290
## transform static image to movie (add temporal dimension)
266291
if len(self.seg.shape) == 2:
@@ -279,12 +304,6 @@ def load_segmentation(self, seg_input):
279304

280305
## define a reference size of the movie to scale default parameters
281306
self.reference_size = np.max(self.imgshape2D)
282-
## define the average cell radius
283-
# self.cell_avg_radius = int( math.sqrt( ut.average_area( self.seg[0])/math.pi ) )
284-
# on cell area ut.summary_labels( self.seg[0] )
285-
# if self.verbose > 1:
286-
# print("Reference size of the movie: "+str(self.reference_size))
287-
288307
self.epi_metadata["Reloading"] = True ## has been formatted to EpiCure format
289308

290309
# display the segmentation file movie
@@ -299,13 +318,16 @@ def load_segmentation(self, seg_input):
299318
if self.verbose > 0:
300319
ut.show_duration(start_time, header="Segmentation loaded in ")
301320

321+
302322
def load_tracks(self, progress_bar):
303323
"""From the segmentation, get all the metadata"""
304324
tracked = "tracked"
305325
self.tracking.init_tracks()
306326
if self.tracked == 0:
307327
tracked = "untracked"
308328
else:
329+
if self.graph is not None:
330+
self.tracking.set_graph(self.graph)
309331
if self.forbid_gaps:
310332
progress_bar.set_description("check and fix track gaps")
311333
self.handle_gaps(track_list=None, verbose=1)
@@ -562,13 +584,17 @@ def get_summary(self):
562584
summ += "Average track lengths: " + str(mean_duration) + " frames\n"
563585
summ += "Average cell area: " + str(mean_area) + " pixels^2\n"
564586
summ += "Nb suspect events: " + str(self.inspecting.nb_events(only_suspect=True)) + "\n"
565-
summ += "Nb divisions: " + str(self.inspecting.nb_type("division")) + "\n"
587+
summ += "Nb divisions: " + str(self.nb_divisions()) + "\n"
566588
summ += "Nb extrusions: " + str(self.inspecting.nb_type("extrusion")) + "\n"
567589
summ += "\n"
568590
summ += "--- Parameter infos \n"
569591
summ += "Junction thickness: " + str(self.thickness) + "\n"
570592
return summ
571593

594+
def nb_divisions(self):
595+
""" Return the number of divisions """
596+
return self.inspecting.nb_type("division")
597+
572598
def set_contour(self, width):
573599
self.seglayer.contour = width
574600

src/epicure/preferences.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ class Preferences():
5454
def __init__( self ):
5555
""" Initialise file path, load current preferences"""
5656
self.build_preferences_path()
57+
print("Running on "+platform.lower())
58+
59+
self.ctl = "Control"
60+
#self.alt = "Alt"
61+
if platform.lower() == "darwin":
62+
self.ctl = "Command"
5763

5864
self.load_default_shortcuts()
5965
self.load_default_settings()
@@ -133,7 +139,8 @@ def add_click_shortcut( self, main_type, shortname, fulltext, button, modifiers=
133139
sc["text"] = fulltext
134140
sc["button"] = button
135141
if modifiers is not None:
136-
sc["modifiers"] = modifiers
142+
sc["modifiers"] = ["Control" if item=="Command" else item for item in modifiers]
143+
137144

138145
def load_default_shortcuts( self ):
139146
""" Load all default shortcuts """
@@ -149,7 +156,7 @@ def load_default_shortcuts( self ):
149156
## Labels edition (static) shortcuts
150157
self.add_key_shortcut( "Labels", shortname="unused paint", fulltext="set the current label to unused value and go to paint mode", key="n" )
151158
self.add_key_shortcut( "Labels", shortname="unused fill", fulltext="set the current label to unused value and go to fill mode", key="Shift-n" )
152-
self.add_key_shortcut( "Labels", shortname="swap mode", fulltext="then <Control>+Left click on one cell to another to swap their values", key="w" )
159+
self.add_key_shortcut( "Labels", shortname="swap mode", fulltext="then <"+self.ctl+">+Left click on one cell to another to swap their values", key="w" )
153160

154161
self.add_click_shortcut( "Labels", shortname="erase", fulltext="erase the cell under the click", button="Right", modifiers=None )
155162
self.add_click_shortcut( "Labels", shortname="merge", fulltext="drag-click from one cell to another to merge them", button="Left", modifiers=["Control"] )
@@ -200,7 +207,7 @@ def load_default_shortcuts( self ):
200207
self.add_key_shortcut( "Display", shortname="decrease", fulltext="decrease label contour size", key="Control-d" )
201208

202209
## Info shortcuts
203-
self.add_key_shortcut( "Info", shortname="measure length", fulltext="draw and measure a line length", key="Control-i" )
210+
self.add_key_shortcut( "Info", shortname="measure length", fulltext="draw and measure a line length", key="Control"+"-i" )
204211

205212

206213
def load_default_settings( self ):
@@ -241,6 +248,10 @@ def __init__( self, napari_viewer, pref ):
241248
super().__init__()
242249

243250
layout = QVBoxLayout()
251+
self.ctl = "Control"
252+
#self.alt = "Alt"
253+
if platform.lower() == "darwin":
254+
self.ctl = "Command"
244255

245256
self.sc = pref.get_shortcuts()
246257
## choice list to choose which shortcuts to edit
@@ -284,9 +295,8 @@ def create_sc_type( self, sc_type ):
284295
cur_modif.addItem("Control")
285296
cur_modif.addItem("Shift")
286297
cur_modif.addItem("Alt")
287-
if platform == "darwin":
298+
if platform.lower() == "darwin":
288299
cur_modif.addItem("Command")
289-
cur_modif.addItem("Alt")
290300
new_line.addWidget( cur_modif )
291301
cur_modif.setCurrentText( modif )
292302
self.sc_guis[sc_type][ shortname+"modifiers"+str(ind) ] = cur_modif

src/epicure/start_epicuring.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,8 @@ def launch_napari_epyseg():
174174
scale_xy = {"widget_type": "LiteralEvalLineEdit"},
175175
timeframe = {"widget_type": "LiteralEvalLineEdit"},
176176
__ = {"widget_type": "Label"},
177+
_____ = {"widget_type": "Label"},
178+
______ = {"widget_type": "Label"},
177179
segment_with_epyseg = {"widget_type": "PushButton", "label": "Segment now with EpySeg"},
178180
___ = {"widget_type": "Label"},
179181
junction_half_thickness={"widget_type": "LiteralEvalLineEdit"},
@@ -188,8 +190,10 @@ def get_files(
188190
unit_xy = "um",
189191
timeframe = 1,
190192
unit_t = "min",
191-
__ = "Segmentation",
193+
__ = "\nSegmentation\n",
194+
_____ = "Load segmentation or TrackMateXML file",
192195
segmentation_file = pathlib.Path(cdir),
196+
______ = "OR\t",
193197
segment_with_epyseg = False,
194198
___ = "",
195199
advanced_parameters = False,

0 commit comments

Comments
 (0)