epicure.outputing
EpiCure output interface
Handles the onglet Output of EpiCure interface.
This panel offers option to export the results in various format or to analyse directly the results in the plugin and display the measures tables, plot and save it.
1""" 2 **EpiCure output interface** 3 4 Handles the onglet `Output` of EpiCure interface. 5 This panel offers option to export the results in various format or to analyse directly the results in the plugin and display the measures tables, plot and save it. 6 7""" 8import pandas as pand 9import numpy as np 10import roifile 11from skimage.measure import label 12import os, time 13import napari 14from napari.utils import progress 15import epicure.Utils as ut 16import epicure.epiwidgets as wid 17from epicure.trackmate_export import save_trackmate_xml 18from epicure.geff_export import save_geff 19import plotly.express as px 20from qtpy import QtCore 21from qtpy.QtCore import Qt 22from qtpy.QtWidgets import QHBoxLayout, QVBoxLayout, QWidget, QTableWidget, QTableWidgetItem, QGridLayout, QListWidget, QTextBrowser 23from qtpy.QtWidgets import QAbstractItemView as aiv 24from random import sample 25from joblib import Parallel, delayed 26import webbrowser 27import tempfile 28try: 29 QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_ShareOpenGLContexts, True) ## for QtWebEngine import to work on some computers 30except: 31 pass 32 33from skimage.morphology import disk 34import skimage 35if ut.version_above( skimage, "0.25" ): 36 try: 37 from skimage.morphology import erosion as binary_erosion 38 except: 39 from skimage.morphology import binary_erosion 40else: 41 try: 42 from skimage.morphology import binary_erosion 43 except: 44 from skimage.morphology import erosion as binary_erosion 45 46 47class Outputing(QWidget): 48 49 def __init__(self, napari_viewer, epic): 50 """ Initialisation of the interface """ 51 super().__init__() 52 self.viewer = napari_viewer 53 self.epicure = epic 54 self.table = None 55 self.table_selection = None 56 self.seglayer = self.viewer.layers["Segmentation"] 57 self.movlayer = self.viewer.layers["Movie"] 58 self.selection_choices = ["All cells", "Only selected cell"] 59 self.output_options = ["", "Export to extern plugins", "Export segmentations", "Measure cell features", "Measure track features", "Export/Measure events", "Save as...", "Save screenshot movie", "Measure vertices"] 60 self.tplots = None 61 62 chanlist = ["Movie"] 63 if self.epicure.others is not None: 64 for chan in self.epicure.others_chanlist: 65 chanlist.append( "MovieChannel_"+str(chan) ) 66 self.cell_features = CellFeatures( chanlist ) 67 self.event_classes = EventClass( self.epicure ) 68 69 all_layout = QVBoxLayout() 70 self.scaled_unit = wid.add_check( "Measures in scaled units", False, check_func=None, descr="Scales the output measures in the given spatio-temporal units (µm, min..)" ) 71 all_layout.addWidget( self.scaled_unit ) 72 self.choose_output = wid.listbox() 73 all_layout.addWidget(self.choose_output) 74 for option in self.output_options: 75 self.choose_output.addItem(option) 76 self.choose_output.currentIndexChanged.connect(self.show_output_option) 77 78 ## Choice of active selection 79 #layout = QVBoxLayout() 80 selection_layout, self.output_mode = wid.list_line( "Apply on", descr="Choose on which cell(s) to do the action", func=None ) 81 for sel in self.selection_choices: 82 self.output_mode.addItem(sel) 83 all_layout.addLayout(selection_layout) 84 85 ## Choice of interface 86 self.export_group, export_layout = wid.group_layout( "Export to extern plugins" ) 87 griot_btn = wid.add_button( "Current frame to Griottes", self.to_griot, "Launch(in new window) Griottes plugin on current frame" ) 88 export_layout.addWidget(griot_btn) 89 ncp_btn = wid.add_button( "Current frame to Cluster-Plotter", self.to_ncp, "Launch (in new window) cluster-plotter plugin on current frame" ) 90 export_layout.addWidget(ncp_btn) 91 self.export_group.setLayout(export_layout) 92 all_layout.addWidget(self.export_group) 93 94 ## Option to export segmentation results 95 self.export_seg_group, layout = wid.group_layout(self.output_options[2]) 96 save_line, self.save_choice = wid.button_list( "Save segmentation as", self.save_segmentation, "Save the current segmentation either as ROI, label image or skeleton" ) 97 self.save_choice.addItem( "labels" ) 98 self.save_choice.addItem( "ROI" ) 99 self.save_choice.addItem( "skeleton" ) 100 layout.addLayout( save_line ) 101 102 self.export_seg_group.setLayout(layout) 103 all_layout.addWidget(self.export_seg_group) 104 105 #### Features group 106 self.feature_group, featlayout = wid.group_layout(self.output_options[3]) 107 108 self.choose_features_btn = wid.add_button( "Choose features...", self.choose_features, "Open a window to select the features to measure" ) 109 featlayout.addWidget(self.choose_features_btn) 110 111 self.feature_table = wid.add_button( "Create features table", self.show_table, "Measure the selected features and display it as a clickable table" ) 112 featlayout.addWidget(self.feature_table) 113 self.featTable = FeaturesTable(self.viewer, self.epicure) 114 featlayout.addWidget(self.featTable) 115 116 ######## Temporal option 117 self.temp_graph = wid.add_button( "Table to temporal graphs...", self.temporal_graphs, "Open a plot interface of measured features temporal evolution" ) 118 featlayout.addWidget(self.temp_graph) 119 self.temp_graph.setEnabled(False) 120 121 ######## Drawing option 122 featmap, self.show_feature_map = wid.list_line( "Draw feature map:", descr="Add a layer with the cells colored by the selected feature value", func=self.show_feature ) 123 featlayout.addLayout(featmap) 124 orienbtn = wid.add_button( "Draw cell orientation", self.draw_orientation, "Add a layer with each cell main axis orientation and length " ) 125 featlayout.addWidget( orienbtn ) 126 127 save_tab_line, self.save_format = wid.button_list( "Save features table", self.save_measure_features, "Save the current table in a .csv file" ) 128 self.save_format.addItem( "csv" ) 129 self.save_format.addItem( "xlsx" ) 130 featlayout.addLayout(save_tab_line) 131 132 ## skrub table 133 self.stat_table = wid.add_button( "Open statistiques table...", self.skrub_features, "Open interactive table with the features statistiques (skrub library)" ) 134 featlayout.addWidget(self.stat_table) 135 136 self.feature_group.setLayout(featlayout) 137 self.feature_group.hide() 138 all_layout.addWidget(self.feature_group) 139 140 ## Track features 141 self.trackfeat_group, trackfeatlayout = wid.group_layout(self.output_options[4]) 142 self.trackfeat_table = wid.add_button( "Track features table", self.show_trackfeature_table, "Measure track-related feature and show a table by track" ) 143 trackfeatlayout.addWidget(self.trackfeat_table) 144 self.trackTable = FeaturesTable(self.viewer, self.epicure) 145 trackfeatlayout.addWidget(self.trackTable) 146 self.save_table_track = wid.add_button( "Save track table", self.save_table_tracks, "Save the current table in a .csv file" ) 147 trackfeatlayout.addWidget(self.save_table_track) 148 149 self.trackfeat_group.setLayout(trackfeatlayout) 150 self.trackfeat_group.hide() 151 all_layout.addWidget(self.trackfeat_group) 152 153 ## Option to export/measure events (Fiji ROI or table), + graphs ? 154 self.handle_event_group, elayout = wid.group_layout(self.output_options[5]) 155 self.choose_events_btn = wid.add_button( "Choose events...", self.choose_events, "Open a window to select the events to export/measure" ) 156 elayout.addWidget( self.choose_events_btn ) 157 save_evt_line, self.save_evt_choice = wid.button_list( "Export events as", self.export_events, "Save the checked events as Fiji ROIs or .csv table" ) 158 self.save_evt_choice.addItem( "Fiji ROI" ) 159 self.save_evt_choice.addItem( "CSV File" ) 160 elayout.addLayout( save_evt_line ) 161 count_evt_btn = wid.add_button( "Count events", self.temporal_graphs_events, descr="Create temporal plot of number of events" ) 162 elayout.addWidget( count_evt_btn ) 163 164 self.handle_event_group.setLayout( elayout ) 165 self.handle_event_group.hide() 166 all_layout.addWidget( self.handle_event_group ) 167 168 ## Save TrackMate XML option 169 self.save_as_group, save_as_layout = wid.group_layout( "Save as..." ) 170 self.save_tm_btn = wid.add_button( "Save as TrackMate XML", self.save_tm_xml, "Save the current segmentation and the optional tracking in a TrackMate XML file" ) 171 self.save_geff_btn = wid.add_button( "Save as GEFF", self.save_geff, "Save the segmentation and tracks to GEFF" ) 172 save_as_layout.addWidget( self.save_tm_btn ) 173 save_as_layout.addWidget( self.save_geff_btn ) 174 175 self.save_as_group.setLayout( save_as_layout ) 176 self.save_as_group.hide() 177 all_layout.addWidget( self.save_as_group ) 178 179 ## Save screenshots option 180 current_frame = ut.current_frame( self.epicure.viewer ) 181 self.screenshot_group, screenshot_layout = wid.group_layout( "Save screenshot movie" ) 182 self.show_scalebar = wid.add_check_tolayout( screenshot_layout, "With the scale bar", True, check_func=None, descr="Show the scale bar in the screenshots" ) 183 sframe_line, self.sframe = wid.slider_line( "From frame", 0, self.epicure.nframes, 1, value=current_frame, show_value=True, slidefunc=None, descr="Frame from which to start saving screenshots" ) 184 eframe_line, self.eframe = wid.slider_line( "To frame", 0, self.epicure.nframes, 1, value=current_frame+1, show_value=True, slidefunc=None, descr="Frame until which to save screenshots" ) 185 screenshot_layout.addLayout( sframe_line ) 186 screenshot_layout.addLayout( eframe_line ) 187 savescreen_btn = wid.add_button( "Save current view", self.screenshot_movie, "Save the current view (with current display parameters) for frame between the two specified frames in a movie" ) 188 189 screenshot_layout.addWidget(savescreen_btn) 190 self.screenshot_group.setLayout(screenshot_layout) 191 all_layout.addWidget(self.screenshot_group) 192 self.screenshot_group.hide() 193 194 ## Measure vertex options 195 self.vertex_group, vertices_layout = wid.group_layout( "Measure vertices" ) 196 radius_line, self.vertice_radius = wid.value_line("Vertex radius", "1.25", descr="Radius of a vertex (TCJ) to consider as one point and measure intensities") 197 display_radius_line, self.vertice_display_radius = wid.value_line("Display radius", "3", descr="Radius of a vertex for DISPLAY only (size of drawing in the layer)") 198 vertices_layout.addLayout(radius_line) 199 vertices_layout.addLayout(display_radius_line) 200 self.vertices_btn = wid.add_button( "Measure", self.show_vertices_table, "Measure the vertices (connectivity, intensity)" ) 201 vertices_layout.addWidget( self.vertices_btn ) 202 self.verticesTable = FeaturesTable(self.viewer, self.epicure) 203 vertices_layout.addWidget(self.verticesTable) 204 self.save_table_vertices = wid.add_button( "Save vertices table", self.save_vertices_table, "Save the current table in a .csv file" ) 205 vertices_layout.addWidget(self.save_table_vertices) 206 207 self.vertex_group.setLayout( vertices_layout ) 208 all_layout.addWidget(self.vertex_group) 209 self.vertex_group.hide() 210 211 ## Finished 212 self.setLayout(all_layout) 213 self.show_output_option() 214 215 def get_current_settings( self ): 216 """ Returns current settings of the widget """ 217 disp = {} 218 disp["Apply on"] = self.output_mode.currentText() 219 disp["Current option"] = self.choose_output.currentText() 220 disp["Show scalebar"] = self.show_scalebar.isChecked() 221 disp = self.cell_features.get_current_settings( disp ) 222 disp = self.event_classes.get_current_settings( disp ) 223 return disp 224 225 def apply_settings( self, settings ): 226 """ Set the current state of the widget from preferences if any """ 227 for setting, val in settings.items(): 228 if setting == "Apply on": 229 self.output_mode.setCurrentText( val ) 230 if setting == "Current option": 231 self.choose_output.setCurrentText( val ) 232 if setting == "Show scalebar": 233 self.show_scalebar.setChecked( val ) 234 235 self.cell_features.apply_settings( settings ) 236 self.event_classes.apply_settings( settings ) 237 238 def screenshot_movie( self ): 239 """ Save screenshots of the current view """ 240 scale_visibility = self.viewer.scale_bar.visible 241 current_frame = ut.current_frame( self.epicure.viewer ) 242 self.viewer.scale_bar.visible = self.show_scalebar.isChecked() 243 start_frame = max( self.sframe.value(), 0 ) 244 end_frame = min( self.eframe.value(), self.epicure.nframes ) 245 outname = os.path.join( self.epicure.outdir, self.epicure.imgname+"_screenshots_f"+str(start_frame)+"-"+str(end_frame)+".tif" ) 246 if os.path.exists(outname): 247 os.remove(outname) 248 if start_frame > end_frame: 249 ut.show_warning("From frame > to frame, no screenshot saved") 250 return 251 for frame in range(start_frame, end_frame+1): 252 self.viewer.dims.set_point(0, frame) 253 shot = self.viewer.screenshot( canvas_only=True, flash=False ) 254 ut.appendToTif( shot, outname ) 255 self.viewer.scale_bar.visible = scale_visibility 256 self.viewer.dims.set_point(0, current_frame) 257 ut.show_info( "Screenshot movie saved in "+outname ) 258 259 def events_select( self, event, check ): 260 """ Check/Uncheck the event in event types list """ 261 if event in self.event_classes.evt_classes: 262 self.event_classes.evt_classes[ event ][0].setChecked( check ) 263 else: 264 print(event+" not found in possible event types to export") 265 266 def show_output_option(self): 267 """ Show selected output panel """ 268 cur_option = self.choose_output.currentText() 269 self.export_group.setVisible( cur_option == "Export to extern plugins" ) 270 self.export_seg_group.setVisible( cur_option == "Export segmentations" ) 271 self.feature_group.setVisible( cur_option == "Measure cell features" ) 272 self.vertex_group.setVisible( cur_option == "Measure vertices" ) 273 self.trackfeat_group.setVisible( cur_option == "Measure track features" ) 274 self.handle_event_group.setVisible( cur_option == "Export/Measure events" ) 275 self.save_as_group.setVisible( cur_option == "Save as..." ) 276 self.screenshot_group.setVisible( cur_option == "Save screenshot movie" ) 277 278 def get_current_labels( self ): 279 """ Get the cell labels to process according to current selection of apply on""" 280 if self.output_mode.currentText() == "Only selected cell": 281 lab = self.epicure.seglayer.selected_label 282 return [lab] 283 if self.output_mode.currentText() == "All cells": 284 return self.epicure.get_labels() 285 else: 286 group = self.output_mode.currentText() 287 label_group = self.epicure.groups[group] 288 return label_group 289 290 291 def get_selection_name(self): 292 if self.output_mode.currentText() == "Only selected cell": 293 lab = self.epicure.seglayer.selected_label 294 return "_cell_"+str(lab) 295 #if self.output_mode.currentText() == "Only checked cells": 296 # return "_checked_cells" 297 if self.output_mode.currentText() == "All cells": 298 return "" 299 return "_"+self.output_mode.currentText() 300 301 def skrub_features( self ): 302 """ Open html table interactive and stats with skrub module """ 303 try: 304 from skrub import TableReport 305 except: 306 ut.show_error( "Needs skrub library for this option. Install it (`pip install skrub`) before" ) 307 return 308 if self.table is None: 309 ut.show_warning( "Create/update the table before" ) 310 return 311 report = TableReport( self.table ) 312 report.open() 313 314 315 def save_measure_features(self): 316 """ Save measures table to file whether it was created or not """ 317 if self.table is None or self.table_selection is None or self.selection_changed() : 318 ut.show_warning("Create/update the table before") 319 return 320 ext = self.save_format.currentText() 321 outfile = self.epicure.outname()+"_features"+self.get_selection_name()+"."+ext 322 if ext == "xlsx": 323 self.table.to_excel( outfile, sheet_name='EpiCureMeasures' ) 324 else: 325 self.table.to_csv( outfile, index=False ) 326 if self.epicure.verbose > 0: 327 ut.show_info("Measures saved in "+outfile) 328 329 def save_table_tracks(self): 330 """ Save tracks table to file whether it was created or not """ 331 if self.table is None or self.table_selection is None or self.selection_changed() : 332 ut.show_warning("Create/update the table before") 333 return 334 outfile = self.epicure.outname()+"_trackfeatures"+self.get_selection_name()+".xlsx" 335 self.table.to_excel( outfile, sheet_name='EpiCureTrackMeasures' ) 336 if self.epicure.verbose > 0: 337 ut.show_info("Track measures saved in "+outfile) 338 339 340 def save_one_roi(self, lab): 341 """ Save the Rois of cell with label lab """ 342 keep = self.seglayer.data == lab 343 rois = [] 344 if np.sum(keep) > 0: 345 ## add 2D case 346 for iframe, frame in enumerate(keep): 347 if np.sum(frame) > 0: 348 contour = ut.get_contours(frame) 349 roi = self.create_roi(contour[0], iframe, lab) 350 rois.append(roi) 351 352 roifile.roiwrite(self.epicure.outname()+"_rois_cell_"+str(lab)+".zip", rois, mode='w') 353 354 def create_roi(self, contour, frame, label): 355 croi = roifile.ImagejRoi() 356 croi.version = 227 357 croi.roitype = roifile.ROI_TYPE(0) ## polygon 358 croi.n_coordinates = len(contour) 359 croi.position = frame + 1 360 croi.t_position = frame+1 361 coords = [] 362 cent0 = 0 363 cent1 = 0 364 for cont in contour: 365 coords.append([int(cont[1]), int(cont[0])]) 366 cent0 += cont[1] 367 cent1 += cont[0] 368 croi.integer_coordinates = np.array(coords) 369 #croi.top = int(np.min(coords[0])) 370 #croi.left = int(np.min(coords[1])) 371 croi.name = str(frame+1).zfill(4)+'-'+str(int(cent0/len(contour))).zfill(4)+"-"+str(int(cent1/len(contour))).zfill(4) 372 return croi 373 374 def save_segmentation( self ): 375 """ Save current segmentation in selected format """ 376 if self.output_mode.currentText() == "Only selected cell": 377 ## output only the selected cell 378 lab = self.seglayer.selected_label 379 if self.save_choice.currentText() == "ROI": 380 self.save_one_roi(lab) 381 if self.epicure.verbose > 0: 382 ut.show_info("Cell "+str(lab)+" saved to Fiji ROI") 383 return 384 else: 385 tosave = np.zeros(self.seglayer.data.shape, dtype=self.epicure.dtype) 386 if np.sum(self.seglayer.data==lab) > 0: 387 tosave[self.seglayer.data==lab] = lab 388 endname = "_"+self.save_choice.currentText()+"_"+str(lab)+".tif" 389 else: 390 ## output all cells 391 if self.output_mode.currentText() == "All cells": 392 if self.save_choice.currentText() == "ROI": 393 self.save_all_rois() 394 return 395 tosave = self.seglayer.data 396 endname = "_"+self.save_choice.currentText()+".tif" 397 else: 398 ## or output only selected group 399 group = self.output_mode.currentText() 400 label_group = self.epicure.groups[group] 401 if self.save_choice.currentText() == "ROI": 402 ncells = 0 403 for lab in label_group: 404 self.save_one_roi(lab) 405 ncells += 1 406 if self.epicure.verbose > 0: 407 ut.show_info(str(ncells)+" cells saved to Fiji ROIs") 408 return 409 tosave = np.zeros(self.seglayer.data.shape, dtype=self.epicure.dtype) 410 endname = "_"+self.save_choice.currentText()+"_"+self.output_mode.currentText()+".tif" 411 for lab in label_group: 412 tosave[self.seglayer.data==lab] = lab 413 414 ## save filled image (for label or skeleton) to file 415 outname = os.path.join( self.epicure.outdir, self.epicure.imgname+endname ) 416 if self.save_choice.currentText() == "skeleton": 417 parallel = 0 418 if self.epicure.process_parallel: 419 parallel = self.epicure.nparallel 420 tosave = ut.get_skeleton( tosave, viewer=self.viewer, verbose=self.epicure.verbose, parallel=parallel ) 421 ut.writeTif( tosave, outname, self.epicure.epi_metadata["ScaleXY"], 'uint8', what="Skeleton" ) 422 else: 423 ut.writeTif(tosave, outname, self.epicure.epi_metadata["ScaleXY"], 'float32', what="Segmentation") 424 425 def save_all_rois( self ): 426 """ Save all cells to ROI format """ 427 ncells = 0 428 for lab in np.unique(self.epicure.seglayer.data): 429 self.save_one_roi(lab) 430 ncells += 1 431 if self.epicure.verbose > 0: 432 ut.show_info(str(ncells)+" cells saved to Fiji ROIs") 433 434 def choose_features( self ): 435 """ Pop-up widget to choose the features to measure """ 436 self.cell_features.choose() 437 438 def show_vertices_table(self): 439 """ Show the measurement of vertices table """ 440 self.measure_vertices() 441 self.verticesTable.set_table(self.table) 442 443 def save_vertices_table(self): 444 """ Save vertices table to file whether it was created or not """ 445 if self.table is None: 446 ut.show_warning("Create/update the table before") 447 return 448 outfile = self.epicure.outname()+"_vertices"+".xlsx" 449 self.table.to_excel( outfile, sheet_name='EpiCureVerticesMeasures' ) 450 if self.epicure.verbose > 0: 451 ut.show_info("Vertices measures saved in "+outfile) 452 453 454 def measure_vertices(self): 455 """ Get all vertices (TCJ) and measure their properties """ 456 def nb_neighbors(regionmask, labimg): 457 """ Measure the nb of neighbors (labels) around each point """ 458 #footprint = disk(radius=8) 459 #dilated = binary_dilation(regionmask, footprint) 460 labels = np.unique(labimg[regionmask]).tolist() 461 nb_nei = len(labels) 462 if 0 in labels: 463 nb_nei = nb_nei - 1 464 return nb_nei 465 466 self.table = None 467 radius = float(self.vertice_radius.text()) 468 display_radius = float(self.vertice_display_radius.text()) 469 ## difference between the measured radius and the displayed radius 470 diff_radius = display_radius - radius 471 if diff_radius < 0: 472 diff_radius = 0 473 parallel = 0 474 if self.epicure.process_parallel: 475 parallel = self.epicure.nparallel 476 ## Get the vertices: junctions of several skeleton lines 477 vertex_img = ut.get_vertices( self.epicure.seg, viewer=None, verbose=self.epicure.verbose, parallel=parallel ) 478 vertices_img = np.zeros(vertex_img.shape, dtype=np.int8) 479 ## Individualise, measure, draw 480 for ind, frame in enumerate(vertex_img): 481 props = ut.binary_properties(frame) 482 nvertex = len(props) 483 vertices = [] 484 for prop in props: 485 if prop.label > 0: 486 pt = prop.centroid 487 vertices.append(pt) 488 vert_img = ut.draw_points(vertices, vertex_img.shape[1:], radius=radius) 489 props = ut.binary_properties(vert_img) 490 if nvertex != len(props): 491 ## one or more vertices had been merged 492 vertices = [] 493 for prop in props: 494 if prop.label > 0: 495 pt = prop.centroid 496 vertices.append(pt) 497 vert_img = ut.draw_points(vertices, vertex_img.shape[1:], radius=radius) 498 #vertices_img[ind] = vert_img 499 lbl_img = label(vert_img) 500 int_measures = pand.DataFrame(ut.regionprops_table(lbl_img, self.movlayer.data[ind], properties=["label", "centroid", "intensity_mean"])) 501 ## expand to measure neighbors 502 exp_lbl = ut.touching_labels(lbl_img, expand=3) 503 measures = pand.DataFrame(ut.regionprops_table(exp_lbl, self.epicure.seg[ind], properties=["label"], extra_properties=[nb_neighbors] )) 504 ## Color the vertices by their number of neighbors 505 for lab in measures["label"]: 506 vertices_img[ind][lbl_img==lab] = int(measures.loc[measures["label"]==lab,"nb_neighbors"].iloc[0]) 507 df = pand.merge(int_measures, measures, on="label", how="inner") 508 df["Frame"] = ind 509 510 if self.table is None: 511 self.table = df 512 else: 513 self.table = pand.concat([self.table, df]) 514 515 ## Expand for display only 516 vertices_img[ind] = ut.touching_labels(vertices_img[ind], expand=diff_radius) 517 518 ## Display the vertices in a new layer 519 ut.remove_layer(self.viewer, "Vertices") # in case already present 520 self.viewer.add_labels(vertices_img, blending="additive", name="Vertices", scale=self.viewer.layers["Movie"].scale, opacity=1) 521 522 def measure_features(self): 523 """ Measure features and put them to table """ 524 thick = self.epicure.thickness 525 526 def intensity_junction_cytoplasm(regionmask, intensity): 527 """ Measure the intensity only on the contour of regionmask """ 528 footprint = disk(radius=thick) 529 inside = binary_erosion(regionmask, footprint) 530 inside_intensity = ut.mean_nonzero(intensity*inside) 531 periph_intensity = ut.mean_nonzero(intensity*(regionmask^inside)) 532 return inside_intensity, periph_intensity 533 534 if self.epicure.verbose > 0: 535 print("Measuring features") 536 #self.viewer.window._status_bar._toggle_activity_dock(True) 537 pb = ut.start_progress( self.viewer, total=2, descr="Measuring cells in all movie" ) 538 start_time = time.time() 539 if self.output_mode.currentText() == "Only selected cell": 540 meas = np.zeros(self.epicure.seglayer.data.shape, self.epicure.dtype) 541 lab = self.epicure.seglayer.selected_label 542 meas[self.epicure.seglayer.data==lab] = lab 543 else: 544 if self.output_mode.currentText() == "All cells": 545 meas = self.epicure.seglayer.data 546 else: 547 group = self.output_mode.currentText() 548 meas = np.zeros(self.epicure.seglayer.data.shape, self.epicure.dtype) 549 label_group = self.epicure.groups[group] 550 for lab in label_group: 551 meas[self.epicure.seglayer.data==lab] = lab 552 553 properties, prop_extra, other_features, int_feat, int_extrafeat = self.cell_features.get_features() 554 do_channels = self.cell_features.get_channels() 555 extra_prop = [] 556 if "intensity_junction_cytoplasm" in int_extrafeat: 557 extra_prop = extra_prop + [intensity_junction_cytoplasm] 558 559 extra_properties = [] 560 if (do_channels is not None) and ("Movie" in do_channels): 561 properties = properties + int_feat 562 for extra in int_extrafeat: 563 if extra == "intensity_junction_cytoplasm": 564 extra_properties = extra_properties + [intensity_junction_cytoplasm] 565 566 pb.update() 567 labgroups = self.epicure.group_of_labels() 568 pb.total = self.epicure.nframes 569 chan_dict = dict() 570 if ( do_channels is not None ): 571 for chan in do_channels: 572 if chan == "Movie": 573 continue 574 chan_dict[chan] = self.viewer.layers[chan].data 575 seg = self.epicure.seg 576 mov = self.movlayer.data 577 578 def measure_one_frame_collect( img, frame ): 579 """ Measure on one frame and return a list of dicts for each label """ 580 #pb.update() 581 intimg = mov[frame] 582 frame_table = pand.DataFrame( ut.labels_table(img, intensity_image=intimg, properties=properties, extra_properties=extra_properties) ) 583 if "group" in other_features: 584 frame_table["group"] = frame_table["label"].map(labgroups).fillna("Ungrouped") 585 frame_table["frame"] = frame 586 587 # Boundary 588 if "Boundary" in other_features: 589 boundimg = seg[frame] 590 bds = ut.get_boundary_cells(boundimg) 591 frame_table["Boundary"] = frame_table["label"].isin(bds).astype(int) 592 # Border 593 if "Border" in other_features: 594 bds = ut.get_border_cells(img) 595 frame_table["Border"] = frame_table["label"].isin(bds).astype(int) 596 597 # Intensity features in other channels 598 for chan, intimg_chan in chan_dict.items(): 599 intimg_frame = intimg_chan[frame] 600 frame_tab = ut.labels_table(img, intensity_image=intimg_frame, properties=int_feat, extra_properties=extra_prop) 601 for add_prop in int_feat: 602 frame_table[add_prop+"_"+str(chan)] = frame_tab[add_prop] 603 if "intensity_junction_cytoplasm-0" in frame_tab.keys(): 604 frame_table["intensity_cytoplasm_"+str(chan)] = frame_tab["intensity_junction_cytoplasm-0"] 605 frame_table["intensity_junction_"+str(chan)] = frame_tab["intensity_junction_cytoplasm-1"] 606 607 if prop_extra != []: 608 if "shape_index" in prop_extra: 609 frame_table["shape_index"] = frame_table["perimeter"] / np.sqrt(frame_table["area"]) 610 if "roundness" in prop_extra: 611 frame_table["roundness"] = 4*frame_table["area"] /(np.pi * np.power(frame_table["axis_major_length"],2) ) 612 if "aspect_ratio" in prop_extra: 613 frame_table["aspect_ratio"] = frame_table["axis_major_length"] / frame_table["axis_minor_length"] 614 615 # Neighbor features 616 do_neighbor = "NbNeighbors" in other_features 617 get_neighbor = "Neighbors" in other_features 618 if do_neighbor or get_neighbor: 619 nimg = seg[frame] 620 graph = ut.get_neighbor_graph(nimg, distance=3) 621 all_neighbors = {label: list(graph.adj[label]) for label in graph.nodes} 622 frame_table["neighborlist"] = frame_table["label"].map(lambda l: all_neighbors.get(l, [])) 623 624 if do_neighbor: 625 frame_table["NbNeighbors"] = frame_table["neighborlist"].apply( 626 lambda x: len(x) if x else -1 627 ) 628 if get_neighbor: 629 frame_table["Neighbors"] = frame_table["neighborlist"].apply( 630 lambda x: "&".join(map(str, x)) if x else "" 631 ) 632 if do_neighbor or get_neighbor: 633 frame_table.drop(columns="neighborlist", inplace=True) 634 return pand.DataFrame( frame_table.to_dict(orient="records") ) 635 636 if self.epicure.process_parallel: 637 frame_tables = Parallel( n_jobs=self.epicure.nparallel ) ( 638 delayed( measure_one_frame_collect ) ( frame, iframe ) for iframe, frame in enumerate(meas) ) 639 else: 640 frame_tables = [ 641 measure_one_frame_collect( frame, iframe ) 642 for iframe, frame in (enumerate(meas)) 643 ] 644 self.table = pand.concat(frame_tables, ignore_index=True) 645 646 if "intensity_junction_cytoplasm-0" in self.table.columns: 647 self.table = self.table.rename(columns={"intensity_junction_cytoplasm-0": "intensity_cytoplasm", "intensity_junction_cytoplasm-1":"intensity_junction"}) 648 self.table_selection = self.selection_choices.index(self.output_mode.currentText()) 649 ut.close_progress( self.viewer, pb ) 650 #self.viewer.window._status_bar._toggle_activity_dock(False) 651 if self.epicure.verbose > 0: 652 ut.show_info("Features measured in "+"{:.3f}".format((time.time()-start_time)/60)+" min") 653 654 def measure_one_frame(self, img, properties, extra_properties, other_features, channels, int_feat, int_extrafeat, frame, labgroups, prop_extra ): 655 """ Measure on one frame """ 656 if frame is not None: 657 intimg = self.movlayer.data[frame] 658 else: 659 intimg = self.movlayer.data 660 first = "label" not in self.table.keys() 661 nrows = len(self.table["label"]) if "label" in self.table.keys() else 0 662 663 ## add the basic label measures 664 frame_table = ut.labels_table( img, intensity_image=intimg, properties=properties, extra_properties=extra_properties ) 665 ndata = len(frame_table["label"]) 666 for key, value in frame_table.items(): 667 if first: 668 self.table[key] = [] 669 self.table[key].extend(list(value)) 670 671 ## add the frame column 672 if frame is not None: 673 if first: 674 self.table["frame"] = [] 675 self.table["frame"].extend([frame]*ndata) 676 677 ## add info of the cell group 678 if "group" in other_features: 679 frame_group = [ labgroups[label] if label in labgroups.keys() else "Ungrouped" for label in frame_table["label"] ] 680 if first: 681 self.table["group"] = [] 682 self.table["group"].extend( frame_group ) 683 684 ## add the extra shape features 685 if prop_extra != []: 686 if "shape_index" in prop_extra: 687 si = frame_table["perimeter"] /np.sqrt( frame_table["area"] ) 688 if first: 689 self.table["shape_index"] = [] 690 self.table["shape_index"].extend( si ) 691 if "roundness" in prop_extra: 692 rou = 4*frame_table["area"] /(np.pi * np.power(frame_table["axis_major_length"],2) ) 693 if first: 694 self.table["roundness"] = [] 695 self.table["roundness"].extend( rou ) 696 if "aspect_ratio" in prop_extra: 697 ar = list( np.array(frame_table["axis_major_length"])/np.array(frame_table["axis_minor_length"]) ) 698 if first: 699 self.table["aspect_ratio"] = [] 700 self.table["aspect_ratio"].extend( ar ) 701 702 ### Measure intensity features in other chanels if option is on 703 if (channels is not None): 704 for chan in channels: 705 ## if it's movie, already measured in the general measure 706 if chan == "Movie": 707 continue 708 ## otherwise, do a new measure on the selected channels 709 if frame is not None: 710 intimg = self.viewer.layers[chan].data[frame] 711 else: 712 intimg = self.viewer.layers[chan].data 713 frame_tab = ut.labels_table( img, intensity_image=intimg, properties=int_feat, extra_properties=int_extrafeat ) 714 for add_prop in int_feat: 715 if first: 716 self.table[add_prop+"_"+chan] = [] 717 self.table[add_prop+"_"+chan].extend( list(frame_tab[add_prop]) ) 718 if "intensity_junction_cytoplasm-0" in frame_tab.keys(): 719 if first: 720 self.table["intensity_cytoplasm_"+chan] = [] 721 self.table["intensity_junction_"+str(chan)] = [] 722 self.table["intensity_cytoplasm_"+chan].extend( list(frame_tab["intensity_junction_cytoplasm-0"]) ) 723 self.table["intensity_junction_"+str(chan)].extend( list(frame_tab["intensity_junction_cytoplasm-1"]) ) 724 725 726 ## add features of neighbors relationship with graph 727 do_neighbor = "NbNeighbors" in other_features 728 get_neighbor = "Neighbors" in other_features 729 if do_neighbor or get_neighbor: 730 if frame is not None: 731 nimg = self.epicure.seg[frame] 732 else: 733 nimg = self.epicure.seg 734 #start_time = ut.start_time() 735 graph = ut.get_neighbor_graph( nimg, distance=3 ) 736 737 if first: 738 if do_neighbor: 739 self.table["NbNeighbors"] = [] 740 if get_neighbor: 741 self.table["Neighbors"] = [] 742 if do_neighbor: 743 self.table["NbNeighbors"].extend( [-1]*ndata ) 744 if get_neighbor: 745 self.table["Neighbors"].extend( [""]*ndata ) 746 747 for label in np.unique(frame_table["label"]): 748 if label in graph.nodes: 749 rlabel = np.where( (frame_table["label"] == label) )[0] 750 nneighbor = len(graph.adj[label]) 751 for ind in rlabel: 752 if do_neighbor: 753 self.table["NbNeighbors"][ind+nrows] = nneighbor 754 if get_neighbor: 755 self.table["Neighbors"][ind+nrows] = "" 756 sep = "" 757 for key in graph.adj[label].keys(): 758 self.table["Neighbors"][ind+nrows] += sep + str(key) 759 sep = "&" 760 #ut.show_duration( start_time, "Neighborhoods measured" ) 761 762 ## measure cells on boundary 763 if "Boundary" in other_features: 764 if frame is not None: 765 boundimg = self.epicure.seg[frame] 766 else: 767 boundimg = self.epicure.seg 768 bounds = ut.get_boundary_cells( boundimg ) 769 if first: 770 self.table["Boundary"] = [] 771 self.table["Boundary"].extend( [0]*ndata ) 772 for label in np.unique(frame_table["label"]): 773 if label in bounds: 774 rlabel = np.where( (frame_table["label"] == label) )[0] 775 for ind in rlabel: 776 self.table["Boundary"][ind+nrows] = 1 777 778 ## measure cells on border 779 if "Border" in other_features: 780 bounds = ut.get_border_cells( img ) 781 if first: 782 self.table["Border"] = [] 783 self.table["Border"].extend( [0]*ndata ) 784 for label in bounds: 785 rlabel = np.where( (frame_table["label"] == label) )[0] 786 for ind in rlabel: 787 self.table["Border"][ind+nrows] = 1 788 789 790 def selection_changed(self): 791 if self.table_selection is None: 792 return True 793 return self.output_mode.currentText() != self.selection_choices[self.table_selection] 794 795 def update_selection_list(self): 796 """ Update the possible selection from group cell list """ 797 self.selection_choices = ["Only selected cell", "All cells"] 798 for group in self.epicure.groups.keys(): 799 self.selection_choices.append(group) 800 self.output_mode.clear() 801 for sel in self.selection_choices: 802 self.output_mode.addItem(sel) 803 804 def show_table(self): 805 """ Show the measurement table """ 806 #disable automatic update (slow) 807 #if self.table is None: 808 ## create the table and connect action to update it automatically 809 #self.output_mode.currentIndexChanged.connect(self.show_table) 810 #self.measure_other_chanels_cbox.stateChanged.connect(self.show_table) 811 #self.feature_graph_cbox.stateChanged.connect(self.show_table) 812 #self.feature_intensity_cbox.stateChanged.connect(self.show_table) 813 #self.feature_shape_cbox.stateChanged.connect(self.show_table) 814 815 ut.set_active_layer( self.viewer, "Segmentation" ) 816 self.show_feature_map.clear() 817 self.show_feature_map.addItem("") 818 laynames = [lay.name for lay in self.viewer.layers] 819 for lay in laynames: 820 if lay.startswith("Map_"): 821 ut.remove_layer(self.viewer, lay) 822 self.measure_features() 823 featlist = self.table.keys() 824 ## Scaling the features 825 if self.scaled_unit.isChecked(): 826 for feat in featlist: 827 feat_scale, scaled = self.scale_feature( feat, self.table[feat] ) 828 if feat_scale is not None: 829 if (feat_scale[0:4] != "Time") and (feat_scale[0:9] != "centroid-"): 830 del self.table[feat] 831 self.table[feat_scale] = scaled 832 featlist = self.table.keys() 833 ## Adding the list to the feature maps 834 for feat in featlist: 835 self.show_feature_map.addItem(feat) 836 self.featTable.set_table(self.table) 837 self.temp_graph.setEnabled(True) 838 if self.tplots is not None: 839 self.tplots.update_table(self.table) 840 841 def scale_feature( self, feat, featVals ): 842 """ Scale if necessary the feature values """ 843 dist_feats = ["centroid-0", "centroid-1", "perimeter", "axis_major_length", "axis_minor_length", "feret_diameter_max", "equivalent_diameter_area" ] 844 if feat in dist_feats: 845 return feat+"_"+self.epicure.epi_metadata["UnitXY"], np.array(featVals)*self.epicure.epi_metadata["ScaleXY"] 846 area_feats = ["area", "area_convex"] 847 if feat in area_feats: 848 return feat+"_"+self.epicure.epi_metadata["UnitXY"]+"²", np.array(featVals)*self.epicure.epi_metadata["ScaleXY"] * self.epicure.epi_metadata["ScaleXY"] 849 if feat == "frame": 850 return "Time_"+self.epicure.epi_metadata["UnitT"], np.array(featVals)*self.epicure.epi_metadata["ScaleT"] 851 return None, None 852 853 854 def show_feature(self): 855 """ Add the image map of the selected feature """ 856 feat = self.show_feature_map.currentText() 857 if (feat is not None) and (feat != ""): 858 if feat in self.table.keys(): 859 values = list(self.table[feat]) 860 if feat == "group": 861 for i, val in enumerate(values): 862 if (val is None) or (val == 'None'): 863 values[i] = 0 864 else: 865 values[i] = list(self.epicure.groups.keys()).index(val) + 1 866 labels = list(self.table["label"]) 867 frames = None 868 if "frame" in self.table: 869 frames = list(self.table["frame"]) 870 self.draw_map(labels, values, frames, feat) 871 872 def draw_map(self, labels, values, frames, featname): 873 """ Add image layer of values by label """ 874 ## special feature: orientation, draw the axis instead 875 self.viewer.window._status_bar._toggle_activity_dock(True) 876 labels = np.array(labels) 877 values = np.array(values) 878 frames = np.array(frames) 879 def map_frame( iframe, segframe ): 880 """ Draw one frame of the map """ 881 mask = np.where(frames==iframe)[0] 882 labs = labels[mask] 883 vals = values[mask] 884 mapping = np.zeros(segframe.max()+1) 885 mapping[:] = np.nan 886 mapping[labs] = vals 887 return mapping[segframe] 888 889 if frames is not None: 890 ## Plotting a movie 891 if self.epicure.process_parallel: 892 mapfeat = Parallel( n_jobs=self.epicure.nparallel) ( 893 delayed ( map_frame )(iframe, frame ) for iframe, frame in enumerate(self.seglayer.data) 894 ) 895 mapfeat = np.array(mapfeat) 896 else: 897 mapfeat = np.empty(self.epicure.seg.shape, dtype="float16") 898 mapfeat[:] = np.nan 899 for iframe in np.unique(frames): 900 segdata = self.seglayer.data[iframe] 901 mapfeat[iframe] = map_frame( iframe, segdata ) 902 else: 903 mapfeat = np.empty(self.epicure.seg.shape, dtype="float16") 904 mapfeat[:] = np.nan 905 for lab, val in progress(zip(labels, values)): 906 cell = self.seglayer.data==lab 907 mapfeat[cell] = val 908 ut.remove_layer(self.viewer, "Map_"+featname) 909 self.viewer.add_image(mapfeat, name="Map_"+featname, scale=self.viewer.layers["Segmentation"].scale ) 910 self.viewer.window._status_bar._toggle_activity_dock(False) 911 912 def draw_orientation( self ): 913 """ Display the cells orientation axis in a new layer """ 914 ## check that necessary features are measured 915 ut.remove_layer( self.viewer, "CellOrientation" ) 916 feats = ["centroid-0", "centroid-1", "orientation"] 917 if self.table is None: 918 print("Features centroid and orientation necessary to draw orientation, but are not measured yet") 919 return 920 for feat in feats: 921 if feat not in self.table.keys(): 922 print("Feature "+feat+" necessary to draw orientation, but was not measured") 923 return 924 ## ok, can work now 925 self.viewer.window._status_bar._toggle_activity_dock(True) 926 927 ## get the coordinates of the axis lines by getting the cell centroid, main orientation 928 xs = np.array( self.table["centroid-0"] ) 929 ys = np.array( self.table["centroid-1"] ) 930 angles = np.array( self.table["orientation"] ) 931 lens = np.array( [10]*len(angles) ) 932 oriens = np.zeros( (self.epicure.seg.shape), dtype="uint8" ) 933 934 ## draw axis length depending on the eccentricity 935 if "eccentricity" in self.table.keys(): 936 lens = np.array(self.table["eccentricity"]*16) 937 938 if "frame" in self.table: 939 frames = np.array( self.table["frame"] ).astype(int) 940 else: 941 frames = np.array( [0]*len(angles) ) 942 943 ## draw the lines in between the two extreme points (using Shape layer is too slow on display for big movies) 944 npts = 30 945 xmax = oriens.shape[1]-1 946 ymax = oriens.shape[2]-1 947 for i in range(npts): 948 xas = np.clip(xs - lens/2 * np.cos( angles ) * i/float(npts), 0, xmax).astype(int) 949 xbs = np.clip(xs + lens/2 * np.cos( angles ) * i/float(npts), 0, xmax).astype(int) 950 yas = np.clip(ys - lens/2 * np.sin( angles ) * i/float(npts), 0, ymax).astype(int) 951 ybs = np.clip(ys + lens/2 * np.sin( angles ) * i/float(npts), 0, ymax).astype(int) 952 oriens[ (frames, xas, yas) ] = 255 953 oriens[ (frames, xbs, ybs) ] = 255 954 955 self.viewer.add_image( oriens, name="CellOrientation", blending="additive", opacity=1, scale=self.viewer.layers["Segmentation"].scale ) 956 self.viewer.window._status_bar._toggle_activity_dock(False) 957 958 ################### Export to other plugins 959 960 def to_griot(self): 961 """ Export current frame to new viewer and makes it ready for Griotte plugin """ 962 try: 963 from napari_griottes import make_graph 964 except: 965 ut.show_error("Plugin napari-griottes is not installed") 966 return 967 gview = napari.Viewer() 968 tframe = ut.current_frame(self.viewer) 969 segt = self.epicure.seglayer.data[tframe] 970 touching_frame = self.touching_labels(segt) 971 gview.add_labels(touching_frame, name="TouchingCells", opacity=1) 972 gview.window.add_dock_widget(make_graph(), name="Griottes") 973 974 def touching_labels(self, labs): 975 """ Dilate labels so that they all touch """ 976 from skimage.segmentation import find_boundaries 977 from skimage.morphology import skeletonize 978 from skimage.morphology import binary_closing, binary_opening 979 if self.epicure.verbose > 0: 980 print("********** Generate touching labels image ***********") 981 982 ## skeletonize it 983 skel = skeletonize( binary_closing( find_boundaries(labs), footprint=np.ones((10,10)) ) ) 984 ext = np.zeros(labs.shape, dtype="uint8") 985 ext[labs==0] = 1 986 ext = binary_opening(ext, footprint=np.ones((2,2))) 987 newimg = ut.touching_labels(labs, expand=4) 988 newimg[ext>0] = 0 989 return newimg 990 991 def to_ncp(self): 992 """ Export current frame to new viewer and makes it ready for napari-cluster-plots plugin """ 993 try: 994 import napari_skimage_regionprops as nsr 995 except: 996 ut.show_error("Plugin napari-skimage-regionprops is not installed") 997 return 998 gview = napari.Viewer() 999 tframe = ut.current_frame(self.viewer) 1000 segt = self.epicure.seglayer.data[tframe] 1001 moviet = self.epicure.viewer.layers["Movie"].data[tframe] 1002 lab = gview.add_labels(segt, name="Segmentation[t="+str(tframe)+"]", blending="additive") 1003 im = gview.add_image(moviet, name="Movie[t="+str(tframe)+"]", blending="additive") 1004 if self.epicure.verbose > 0: 1005 print("Measure features with napari-skimage-regionprops plugin...") 1006 nsr.regionprops_table(im.data, lab.data, size=True, intensity=True, perimeter=True, shape=True, position=True, moments=True, napari_viewer=gview) 1007 try: 1008 import napari_clusters_plotter as ncp 1009 except: 1010 ut.show_error("Plugin napari-clusters-plotter is not installed") 1011 return 1012 gview.window.add_dock_widget( ncp.ClusteringWidget(gview) ) 1013 gview.window.add_dock_widget( ncp.PlotterWidget(gview) ) 1014 1015 ################### Temporal graphs 1016 def temporal_graphs_events( self ): 1017 """ New window with temporal graph of event counts """ 1018 if self.tplots is not None: 1019 self.tplots.close() 1020 self.tplots = TemporalPlots( self.viewer, self.epicure ) 1021 evt_table = self.count_events() 1022 self.tplots.setTable( evt_table ) 1023 self.tplots.show() 1024 self.viewer.dims.events.current_step.connect(self.position_verticalline) 1025 1026 1027 def temporal_graphs(self): 1028 """ New window with temporal graph of the current table selection """ 1029 #self.temporal_viewer = napari.Viewer() 1030 self.tplots = TemporalPlots( self.viewer, self.epicure ) 1031 self.tplots.setTable(self.table) 1032 self.tplots.show() 1033 #self.plot_wid = self.viewer.window.add_dock_widget( self.tplots, name="Plots" ) 1034 self.viewer.dims.events.current_step.connect(self.position_verticalline) 1035 1036 def on_close_viewer(self): 1037 """ Temporal plots window is closed """ 1038 if self.epicure.verbose > 1: 1039 print("Closed viewer") 1040 self.viewer.dims.events.current_step.disconnect(self.position_verticalline) 1041 self.temporal_viewer = None 1042 self.tplots = None 1043 1044 def position_verticalline(self): 1045 """ Place the vertical line in the temporal graph to the current frame """ 1046 #try: 1047 # wid = self.tplots 1048 #except: 1049 # self.on_close_viewer() 1050 if self.tplots is not None: 1051 self.tplots.move_framepos(self.viewer.dims.current_step[0]) 1052 1053 ############### track features 1054 1055 def show_trackfeature_table(self): 1056 """ Show the measurement of tracks table """ 1057 self.measure_track_features() 1058 self.trackTable.set_table( self.table ) 1059 1060 def measure_track_features(self): 1061 """ Measure track features and put them to table """ 1062 if self.epicure.verbose > 0: 1063 print("Measuring track features") 1064 self.viewer.window._status_bar._toggle_activity_dock(True) 1065 start_time = time.time() 1066 1067 if self.output_mode.currentText() == "Only selected cell": 1068 track_ids = self.epicure.seglayer.selected_label 1069 else: 1070 if self.output_mode.currentText() == "All cells": 1071 track_ids = self.epicure.tracking.get_track_list() 1072 else: 1073 group = self.output_mode.currentText() 1074 track_ids = [] 1075 label_group = self.epicure.groups[group] 1076 for lab in label_group: 1077 track_ids.append(lab) 1078 1079 properties = ["label", "area", "centroid"] 1080 self.table = None 1081 1082 if type(track_ids) == np.ndarray or type(track_ids)==np.array: 1083 track_ids = track_ids.tolist() 1084 if not type(track_ids) == list: 1085 track_ids = [track_ids] 1086 1087 labgroups = self.epicure.group_of_labels() 1088 frame_group = [ labgroups[label] if label in labgroups.keys() else "Ungrouped" for label in track_ids ] 1089 for itrack, track_id in progress(enumerate(track_ids)): 1090 track_frame = self.measure_one_track( track_id ) 1091 track_frame["Group"] = frame_group[itrack] 1092 if self.table is None: 1093 self.table = pand.DataFrame([track_frame]) 1094 else: 1095 self.table = pand.concat([self.table, pand.DataFrame([track_frame])]) 1096 1097 self.table_selection = self.selection_choices.index(self.output_mode.currentText()) 1098 self.viewer.window._status_bar._toggle_activity_dock(False) 1099 if self.epicure.verbose > 0: 1100 ut.show_info("Features measured in "+"{:.3f}".format((time.time()-start_time)/60)+" min") 1101 1102 def measure_one_track( self, track_id ): 1103 """ Measure features of one track """ 1104 track_features = self.epicure.tracking.measure_track_features( track_id, self.scaled_unit.isChecked() ) 1105 return track_features 1106 1107 ############## Events functions 1108 1109 def choose_events( self ): 1110 """ Pop-up widget to choose the event types to measure/export """ 1111 self.event_classes.choose() 1112 1113 def count_events( self ): 1114 """ Count events of selected types """ 1115 evt_types = self.event_classes.get_evt_classes() 1116 if self.epicure.verbose > 2: 1117 print("Counting events of type "+str(evt_types)+" " ) 1118 1119 ## keep only events related to selected cells 1120 labels = self.get_current_labels() 1121 ## count each type of event 1122 table = np.zeros( (self.epicure.nframes,len(evt_types)), dtype="uint8" ) 1123 for itype, evt_type in enumerate( evt_types ): 1124 evts = self.epicure.inspecting.get_events_from_type( evt_type ) 1125 if len( evts ) > 0: 1126 for evt_sid in evts: 1127 pos, label = self.epicure.inspecting.get_event_infos( evt_sid ) 1128 if label in labels: 1129 table[ pos[0], itype ] += 1 1130 df = pand.DataFrame( data=table, columns=evt_types ) 1131 df["frame"] = range(len(df)) 1132 df["label"] = [0]*len(df) 1133 return df 1134 1135 def export_events( self ): 1136 """ Export events of selected types """ 1137 evt_types = self.event_classes.get_evt_classes() 1138 export_type = self.save_evt_choice.currentText() 1139 if self.epicure.verbose > 2: 1140 print("Exporting events of type "+str(evt_types)+" to "+export_type ) 1141 self.export_events_type_format( evt_types, export_type ) 1142 1143 def export_events_type_format( self, evt_types, export_type ): 1144 """ Export events of selected types in selected format """ 1145 ## keep only events related to selected cells 1146 labels = self.get_current_labels() 1147 groups = self.epicure.get_groups( labels ) 1148 if export_type == "CSV File": 1149 res = pand.DataFrame( columns=["label", "frame", "posY", "posX", "EventClass", "Group"] ) 1150 ## export each type of event in separate files 1151 for itype, evt_type in enumerate( evt_types ): 1152 evts = self.epicure.inspecting.get_events_from_type( evt_type ) 1153 if len( evts ) > 0: 1154 rois = [] 1155 for evt_sid in evts: 1156 pos, label = self.epicure.inspecting.get_event_infos( evt_sid ) 1157 ind_lab = np.where( labels==label ) 1158 if len( ind_lab[0] ) > 0: 1159 grp = groups[ int(ind_lab[0][0]) ] 1160 if export_type == "Fiji ROI": 1161 roi = self.create_point_roi( pos, itype ) 1162 rois.append( roi ) 1163 if export_type == "CSV File": 1164 new_event = pand.DataFrame( [[label, pos[0], pos[1], pos[2], evt_type, grp ]], columns=res.columns ) 1165 res = pand.concat( [res, new_event], ignore_index=True ) 1166 if export_type == "Fiji ROI": 1167 outfile = self.epicure.outname()+"_rois_"+evt_type +""+self.get_selection_name()+".zip" 1168 roifile.roiwrite(outfile, rois, mode='w') 1169 if self.epicure.verbose > 0: 1170 print( "Events "+str( evt_type )+" saved in ROI file: "+outfile ) 1171 ## dont save anything if empty, just print info to user 1172 else: 1173 if self.epicure.verbose > 0: 1174 print( "No events of type "+str(evt_type)+"" ) 1175 1176 if export_type == "CSV File": 1177 outfile = self.epicure.outname()+"_events"+self.get_selection_name()+".csv" 1178 res.to_csv( outfile, sep='\t', header=True, index=False ) 1179 if self.epicure.verbose > 0: 1180 print( "Events data "+" saved in CSV file: "+outfile ) 1181 1182 1183 def create_point_roi( self, pos, cat=0 ): 1184 """ Create a point Fiji ROI """ 1185 croi = roifile.ImagejRoi() 1186 croi.version = 227 1187 croi.roitype = roifile.ROI_TYPE(10) 1188 croi.name = str(pos[0]+1).zfill(4)+'-'+str(pos[1]).zfill(4)+"-"+str(pos[2]).zfill(4) 1189 croi.n_coordinates = 1 1190 croi.left = int(pos[2]) 1191 croi.top = int(pos[1]) 1192 croi.z_position = 1 1193 croi.t_position = pos[0]+1 1194 croi.c_position = 1 1195 croi.integer_coordinates = np.array( [[0,0]] ) 1196 croi.stroke_width=3 1197 ncolors = 3 1198 if cat%ncolors == 0: ## color type 0 1199 croi.stroke_color = b'\xff\x00\x00\xff' 1200 if cat%ncolors == 1: ## color type 1 1201 croi.stroke_color = b'\xff\x00\xff\x00' 1202 if cat%ncolors == 2: ## color type 2 1203 croi.stroke_color = b'\xff\xff\x00\x00' 1204 return croi 1205 1206 def save_tm_xml( self ): 1207 """ Save current segmentation and tracking in TrackMate XML format """ 1208 outname = os.path.join( self.epicure.outdir, self.epicure.imgname+".xml" ) 1209 save_trackmate_xml( self.epicure, outname ) 1210 if self.epicure.verbose > 0: 1211 ut.show_info("TrackMate XML saved in "+outname) 1212 1213 def save_geff( self ): 1214 """ Save current segmentation and tracking in GEFF format """ 1215 ## save the label segmentation if it's not saved 1216 labelname = os.path.join( self.epicure.outdir, self.epicure.imgname + "_labels.tif" ) 1217 ut.writeTif( self.epicure.seg, labelname, self.epicure.epi_metadata["ScaleXY"], "float32", what="Segmentation" ) 1218 ## then export the GEFF file 1219 if self.epicure.tracking.graph is None: 1220 self.epicure.tracking.graph = {} 1221 outname = os.path.join( self.epicure.outdir, self.epicure.imgname+".geff" ) 1222 save_geff( self.epicure, outname ) 1223 if self.epicure.verbose > 0: 1224 ut.show_info("GEFF file saved in "+outname) 1225 1226 1227class CellFeatures(QWidget): 1228 """ Choice of features to measure """ 1229 def __init__(self, chanlist): 1230 super().__init__() 1231 layout = QVBoxLayout() 1232 1233 self.required = ["label"] 1234 self.features = {} 1235 self.chan_list = None 1236 1237 other_list = ["group", "NbNeighbors", "Neighbors", "Boundary", "Border"] 1238 feat_layout = self.add_feature_group( other_list, "other" ) 1239 layout.addLayout( feat_layout ) 1240 sel_all_b = wid.add_button( "Select spatial features", lambda: self.select_all("other"), "Select all spatial features" ) 1241 desel_all_b = wid.add_button( "Deselect spatial features", lambda: self.deselect_all("other"), "Deselect all spatial features" ) 1242 sel_line_b = wid.hlayout() 1243 sel_line_b.addWidget( sel_all_b ) 1244 sel_line_b.addWidget( desel_all_b ) 1245 layout.addLayout( sel_line_b ) 1246 1247 1248 ## Add shape features 1249 shape_list = ["centroid", "area", "area_convex", "axis_major_length", "axis_minor_length", "feret_diameter_max", "equivalent_diameter_area", "eccentricity", "orientation", "perimeter", "solidity"] 1250 other_shape_list = ["shape_index", "roundness", "aspect_ratio"] 1251 feat_layout = self.add_feature_group( shape_list, "prop" ) 1252 feat_extra_layout = self.add_feature_group( other_shape_list, "prop_extra" ) 1253 layout.addLayout( feat_layout ) 1254 layout.addLayout( feat_extra_layout ) 1255 sel_all = wid.add_button( "Select morphology features", lambda: self.select_all("props"), "Select all morphology features" ) 1256 desel_all = wid.add_button( "Deselect morphology features", lambda: self.deselect_all("props"), "Deselect all morphology features" ) 1257 sel_line = wid.hlayout() 1258 sel_line.addWidget( sel_all ) 1259 sel_line.addWidget( desel_all ) 1260 layout.addLayout( sel_line ) 1261 1262 int_lab = wid.label_line( "Intensity features:") 1263 layout.addWidget( int_lab ) 1264 intensity_list = ["intensity_mean", "intensity_min", "intensity_max"] 1265 extra_list = ["intensity_junction_cytoplasm"] 1266 feat_layout = self.add_feature_group( intensity_list, "intensity_prop" ) 1267 layout.addLayout( feat_layout ) 1268 feat_layout = self.add_feature_group( extra_list, "intensity_extra" ) 1269 layout.addLayout( feat_layout ) 1270 if len(chanlist) > 1: 1271 chan_lab = wid.label_line( "Measure intensity in channels:" ) 1272 layout.addWidget( chan_lab ) 1273 self.chan_list = QListWidget() 1274 self.chan_list.addItems( chanlist ) 1275 self.chan_list.setSelectionMode(aiv.MultiSelection) 1276 self.chan_list.item(0).setSelected(True) 1277 layout.addWidget( self.chan_list ) 1278 1279 sel_all_int = wid.add_button( "Select intensity features", lambda: self.select_all("intensity"), "Select all spatial features" ) 1280 desel_all_int = wid.add_button( "Deselect intensity features", lambda: self.deselect_all("intensity"), "Deselect all spatial features" ) 1281 sel_line_int = wid.hlayout() 1282 sel_line_int.addWidget( sel_all_int ) 1283 sel_line_int.addWidget( desel_all_int ) 1284 layout.addLayout( sel_line_int ) 1285 1286 bye = wid.add_button( "Ok", self.close, "Close the window" ) 1287 layout.addWidget( bye ) 1288 self.setLayout( layout ) 1289 1290 def select_all( self, feat ): 1291 """ Select all features of type feat """ 1292 if feat == "intensity": 1293 self.select_all( "intensity_prop" ) 1294 self.select_all( "intensity_extra" ) 1295 return 1296 if feat == "props": 1297 self.select_all( "prop" ) 1298 self.select_all( "prop_extra" ) 1299 return 1300 for featy, feat_val in self.features.items(): 1301 if feat_val[1] == feat: 1302 feat_val[0].setChecked( True ) 1303 1304 def deselect_all( self, feat ): 1305 """ Deselect all features of type feat """ 1306 if feat == "intensity": 1307 self.deselect_all( "intensity_prop" ) 1308 self.deselect_all( "intensity_extra" ) 1309 return 1310 if feat == "props": 1311 self.deselect_all( "prop" ) 1312 self.deselect_all( "prop_extra" ) 1313 return 1314 for featy, feat_val in self.features.items(): 1315 if feat_val[1] == feat: 1316 feat_val[0].setChecked( False ) 1317 1318 1319 def add_feature_group( self, feat_list, feat_type ): 1320 """ Add features to the GUI """ 1321 layout = QVBoxLayout() 1322 ncols = 3 1323 for i, feat in enumerate(feat_list): 1324 if i%ncols == 0: 1325 line = QHBoxLayout() 1326 feature_check = wid.add_check( ""+feat, True, None, descr="" ) 1327 line.addWidget(feature_check) 1328 self.features[ feat ] = [feature_check, feat_type] 1329 if i%ncols == (ncols-1): 1330 layout.addLayout( line ) 1331 line = None 1332 if line is not None: 1333 layout.addLayout( line ) 1334 return layout 1335 1336 1337 def close( self ): 1338 """ Close the pop-up window """ 1339 self.hide() 1340 1341 def choose( self ): 1342 """ Show the interface to select the choices """ 1343 self.show() 1344 1345 def get_current_settings( self, setting ): 1346 """ Get current settings of check or not of features """ 1347 for feat, feat_cbox in self.features.items(): 1348 setting[feat] = feat_cbox[0].isChecked() 1349 return setting 1350 1351 def apply_settings( self, settings ): 1352 """ Set the checkboxes from preferenced settings """ 1353 for feat, checked in settings.items(): 1354 if feat in self.features.keys(): 1355 self.features[feat][0].setChecked( checked ) 1356 1357 def get_features( self ): 1358 """ Returns the list of features to measure """ 1359 feats = self.required 1360 feats_extra = [] 1361 int_extra_feats = [] 1362 int_feats = [] 1363 other_feats = [] 1364 self.do_intensity = False 1365 for feat, feat_cbox in self.features.items(): 1366 if feat_cbox[0].isChecked(): 1367 if feat_cbox[1] == "prop": 1368 feats.append( feat ) 1369 if feat_cbox[1] == "prop_extra": 1370 feats_extra.append( feat ) 1371 if feat == "shape_index": 1372 if "perimeter" not in feats: 1373 feats.append("perimeter") 1374 if "area" not in feats: 1375 feats.append("area") 1376 if feat == "roundness": 1377 if "area" not in feats: 1378 feats.append("area") 1379 if "axis_major_length" not in feats: 1380 feats.append("axis_major_length") 1381 if feat == "aspect_ratio": 1382 if "axis_major_length" not in feats: 1383 feats.append("axis_major_length") 1384 if "axis_minor_length" not in feats: 1385 feats.append("axis_minor_length") 1386 if feat_cbox[1] == "other": 1387 other_feats.append( feat ) 1388 if feat_cbox[1] == "intensity_prop": 1389 int_feats.append( feat ) 1390 self.do_intensity = True 1391 if feat_cbox[1] == "intensity_extra": 1392 int_extra_feats.append( feat ) 1393 self.do_intensity = True 1394 return feats, feats_extra, other_feats, int_feats, int_extra_feats 1395 1396 def get_channels( self ): 1397 """ Returns the list of channels to measure """ 1398 if self.do_intensity: 1399 if self.chan_list is not None: 1400 wid_channels = self.chan_list.selectedItems() 1401 channels = [] 1402 for chan in wid_channels: 1403 channels.append( chan.text() ) 1404 else: 1405 channels = ["Movie"] 1406 return channels 1407 return None 1408 1409class EventClass( QWidget ): 1410 """ Choice of event types to export/measure """ 1411 def __init__( self, epicure ): 1412 super().__init__() 1413 layout = QVBoxLayout() 1414 1415 self.evt_classes = {} 1416 possible_classes = epicure.event_class 1417 event_layout = self.add_events( possible_classes ) 1418 layout.addLayout( event_layout ) 1419 1420 bye = wid.add_button( "Ok", self.close, "Close the window" ) 1421 layout.addWidget( bye ) 1422 self.setLayout( layout ) 1423 1424 def add_events( self, event_list ): 1425 """ Add events to the GUI """ 1426 layout = QVBoxLayout() 1427 ncols = 3 1428 for i, event in enumerate( event_list ): 1429 if i%ncols == 0: 1430 line = QHBoxLayout() 1431 event_check = wid.add_check_tolayout( line, ""+event, checked=True, descr="") 1432 self.evt_classes[ event ] = [ event_check ] 1433 if i%ncols == (ncols-1): 1434 layout.addLayout( line ) 1435 line = None 1436 if line is not None: 1437 layout.addLayout( line ) 1438 return layout 1439 1440 1441 def close( self ): 1442 """ Close the pop-up window """ 1443 self.hide() 1444 1445 def choose( self ): 1446 """ Show the interface to select the choices """ 1447 self.show() 1448 1449 def get_current_settings( self, setting ): 1450 """ Get current settings of check or not of features """ 1451 for event, event_cbox in self.evt_classes.items(): 1452 setting[event] = event_cbox[0].isChecked() 1453 return setting 1454 1455 def apply_settings( self, settings ): 1456 """ Set the checkboxes from preferenced settings """ 1457 for evt, checked in settings.items(): 1458 if evt in self.evt_classes.keys(): 1459 self.evt_classes[evt][0].setChecked( checked ) 1460 1461 def get_evt_classes( self ): 1462 """ Returns the list of events to measure """ 1463 events = [] 1464 for evt, evt_cbox in self.evt_classes.items(): 1465 if evt_cbox[0].isChecked(): 1466 events.append( evt ) 1467 return events 1468 1469class FeaturesTable(QWidget): 1470 """ Widget to visualize and interact with the measurement table """ 1471 1472 def __init__(self, napari_viewer, epicure): 1473 super().__init__() 1474 self.viewer = napari_viewer 1475 self.epicure = epicure 1476 self.wid_table = QTableWidget() 1477 self.wid_table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers) 1478 self.setLayout(QGridLayout()) 1479 self.layout().addWidget(self.wid_table) 1480 self.wid_table.clicked.connect(self.show_label) 1481 self.wid_table.setSortingEnabled(True) 1482 1483 def show_label(self): 1484 """ When click on the table, show selected cell """ 1485 if self.wid_table is not None: 1486 row = self.wid_table.currentRow() 1487 self.epicure.seglayer.show_selected_label = False 1488 headers = [self.wid_table.horizontalHeaderItem(ind).text() for ind in range(self.wid_table.columnCount()) ] 1489 labelind = None 1490 if "label" in headers: 1491 labelind = headers.index("label") 1492 if "Label" in headers: 1493 labelind = headers.index("Label") 1494 frameind = None 1495 if "frame" in headers: 1496 frameind = headers.index("frame") 1497 if labelind is not None and labelind >= 0: 1498 lab = int(self.wid_table.item(row, labelind).text()) 1499 if frameind is not None: 1500 ## set current frame to the selected row 1501 frame = int(self.wid_table.item(row, frameind).text()) 1502 ut.set_frame(self.viewer, frame) 1503 else: 1504 ## set current frame to the first frame where label or track is present 1505 frame = self.epicure.tracking.get_first_frame( lab ) 1506 if frame is not None: 1507 ut.set_frame(self.viewer, frame) 1508 self.epicure.seglayer.selected_label = lab 1509 self.epicure.seglayer.show_selected_label = True 1510 1511 1512 def get_features_list(self): 1513 """ Return list of measured features """ 1514 return [ self.wid_table.horizontalHeaderItem(ind).text() for ind in range(self.wid_table.columnCount()) ] 1515 1516 def set_table(self, table): 1517 self.wid_table.clear() 1518 self.wid_table.setRowCount(table.shape[0]) 1519 self.wid_table.setColumnCount(table.shape[1]) 1520 1521 for c, column in enumerate(table.keys()): 1522 column_name = column 1523 self.wid_table.setHorizontalHeaderItem(c, QTableWidgetItem(column_name)) 1524 for r, value in enumerate(table.get(column)): 1525 item = QTableWidgetItem() 1526 item.setData( Qt.EditRole, value) 1527 self.wid_table.setItem(r, c, item) 1528 1529class TemporalPlots(QWidget): 1530 """ Widget to visualize and interact with temporal plots """ 1531 1532 def __init__(self, napari_viewer, epicure): 1533 super().__init__() 1534 self.viewer = napari_viewer 1535 self.epicure = epicure 1536 self.features_list = ["frame"] 1537 self.parameter_gui() 1538 self.vline = None 1539 self.ymin = None 1540 #self.viewer.window.add_dock_widget( self.plot_wid, name="Temporal plot" ) 1541 1542 def parameter_gui(self): 1543 """ add widget to choose plotting parameters """ 1544 1545 layout = QVBoxLayout() 1546 1547 ## choice of feature to plot 1548 feat_choice, self.feature_choice = wid.list_line( label="Plot feature", descr="Choose the feature to plot", func=self.plot_feature ) 1549 layout.addLayout(feat_choice) 1550 ## option to average by group 1551 ck_line, self.avg_group, self.smooth = wid.double_check( "Average by groups", False, self.plot_feature, "Show a line by cell or a line by group", "Smooth lines", False, self.plot_feature, "Smooth temporally (moving average) the plotted lines" ) 1552 layout.addLayout(ck_line) 1553 ## show the plot 1554 self.plot_wid = self.create_plotwidget() 1555 layout.addWidget(self.plot_wid) 1556 ## save plot or save data of the plot 1557 line = wid.double_button( "Save plot image", self.save_plot_image, "Save the grapic in a PNG file", "Save plot data", self.save_plot_data, "Save the value used for the plot in .csv file" ) 1558 self.by_label = wid.add_check( "Arranged data by label", False, None, "Save the data with one column by label" ) 1559 line.addWidget( self.by_label ) 1560 layout.addLayout( line ) 1561 self.setLayout(layout) 1562 self.resize(1000,800) 1563 1564 def setTable(self, table): 1565 """ Data table to plot """ 1566 self.table = table 1567 self.features_list = self.table.keys() 1568 self.update_feature_list() 1569 1570 def update_table(self, table): 1571 """ Update the current plot with the updated table """ 1572 self.table = table 1573 curchoice = self.feature_choice.currentText() 1574 self.features_list = self.table.keys() 1575 self.update_feature_list() 1576 if curchoice in self.features_list: 1577 ind = list(self.features_list).index(curchoice) 1578 self.feature_choice.setCurrentIndex(ind) 1579 self.plot_feature() 1580 1581 def update_feature_list(self): 1582 """ Update the list of feature in the GUI """ 1583 self.feature_choice.clear() 1584 for feat in self.features_list: 1585 self.feature_choice.addItem(feat) 1586 if "division" in self.features_list and "extrusion" in self.features_list: 1587 self.feature_choice.addItem( "division&extrusion" ) 1588 1589 def plot_feature(self): 1590 """ Plot the selected feature in the temporal graph """ 1591 feat = self.feature_choice.currentText() 1592 if feat == "label": 1593 return 1594 if feat == "": 1595 return 1596 if feat == "division&extrusion": 1597 feat = ["division", "extrusion"] 1598 else: 1599 feat = [feat] 1600 1601 tab = list( zip(self.table["frame"]) ) 1602 labname = [] 1603 for ft in feat: 1604 tab = [ (*t, v) for t, v in zip( tab, self.table[ft]) ] 1605 tab = [ (*t, v) for t, v in zip( tab, self.table["label"]) ] 1606 labname.append("label") 1607 if "group" in self.table: 1608 tab = [ (*t, v) for t, v in zip( tab, self.table["group"]) ] 1609 labname.append("group") 1610 1611 self.df = pand.DataFrame( tab, columns=["frame"] + feat + labname ) 1612 shape = "linear" 1613 if self.smooth.isChecked(): 1614 shape = "spline" 1615 if "group" in self.table and self.avg_group.isChecked(): 1616 self.dfmean = self.df.groupby(['group', 'frame'])[feat].mean().reset_index() 1617 self.df.columns.name = 'group' 1618 self.fig = px.line( self.dfmean, x='frame', y=feat, color='group', labels={'frame': 'Time (frame)'}, line_shape=shape, render_mode="svg" ) 1619 else: 1620 if len( np.unique(self.df["label"]) ) > 1000: 1621 ut.show_warning( "Too many lines to plot; Using a random subset instead" ) 1622 subset = sample( np.unique(self.df["label"]).tolist(), 1000) 1623 subdf = self.df[self.df["label"].isin(subset)] 1624 self.fig = px.line( subdf, x="frame", y=feat[0], color="label", labels={'frame': 'Time (frame)'}, line_shape = shape, render_mode="svg") 1625 if len(feat) > 1: 1626 addfig = px.line(subdf, x="frame", y=feat[1], color="label", line_shape = shape ) 1627 addfig.update_traces( patch={"line": {"dash":"dot"}} ) 1628 self.fig.add_trace( addfig.data[0] ) 1629 else: 1630 self.fig = px.line( self.df, x="frame", y=feat[0], color="label", labels={'frame': 'Time (frame)'}, line_shape = shape, render_mode="svg") 1631 if len(feat) > 1: 1632 addfig = px.line(self.df, x="frame", y=feat[1], color="label", line_shape = shape ) 1633 addfig.update_traces( patch={"line": {"dash":"dot"}} ) 1634 self.fig.add_trace( addfig.data[0] ) 1635 1636 if self.webengine: 1637 self.browser.setHtml( self.fig.to_html(include_plotlyjs='cdn')) 1638 else: 1639 self.show_plot_in_browser( self.fig.to_html(include_plotlyjs='cdn')) 1640 1641 def show_plot_in_browser(self,html): 1642 with tempfile.NamedTemporaryFile(mode='w', suffix='.html', delete=False) as f: 1643 f.write(html) 1644 url = 'file://' + f.name 1645 webbrowser.open(url) 1646 1647 def smooth_df( self, df ): 1648 """ Smooth temporally the dataframe by label or by group """ 1649 rollsize = 20 1650 ## average on a smaller scale if only few frames 1651 if np.max( self.table["frame"] ) <= 20: 1652 rollsize = 5 1653 if feat+"_smooth" in self.df.columns: 1654 feat = feat+"_smooth" 1655 else: 1656 self.df[feat+"_smooth"] = self.df[feat].rolling(rollsize, center=True).mean() 1657 #print(self.df) 1658 feat = feat+"_smooth" 1659 1660 def save_plot_image( self ): 1661 """ Save current plot graphic to PNG image """ 1662 feat = self.feature_choice.currentText() 1663 outfile = self.epicure.outname()+"_plot_"+feat+".png" 1664 if self.fig is not None: 1665 self.fig.write_image( outfile ) 1666 if self.epicure.verbose > 0: 1667 ut.show_info("Measures saved in "+outfile) 1668 1669 def save_plot_data( self ): 1670 """ Save the raw data to redraw the current plot to csv file """ 1671 feat = self.feature_choice.currentText() 1672 outfile = self.epicure.outname()+"_time_"+feat+".csv" 1673 if self.avg_group.isChecked(): 1674 data = self.dfmean.reset_index()[["frame", "group", feat]] 1675 if self.by_label.isChecked(): 1676 df = pand.pivot_table( data, columns="label", index="frame", values=feat ) 1677 df.to_csv( outfile, sep='\t', header=True, index=True ) 1678 else: 1679 data[["frame", "group", feat]].to_csv( outfile, sep='\t', header=True, index=False ) 1680 else: 1681 data = self.df.reset_index()[["frame", "label", feat]] 1682 if self.by_label.isChecked(): 1683 df = pand.pivot_table( data, columns="label", index="frame", values=feat ) 1684 df.to_csv( outfile, sep='\t', header=True, index=True ) 1685 else: 1686 data[["frame", "label", feat]].to_csv( outfile, sep='\t', header=True, index=False ) 1687 1688 def move_framepos(self, frame): 1689 """ Move the vertical line showing the current frame position in the main window """ 1690 return 1691 #if self.fig is not None: 1692 # self.fig.add_vline( x=frame, line_dash="dash", line_color="gray" ) 1693 # self.browser.setHtml( self.fig.to_html(include_plotlyjs='cdn')) 1694 1695 1696 1697 def import_webengineview(self): 1698 """Return QWebEngineView from whichever Qt is available.""" 1699 import importlib 1700 try: 1701 # Fall back to Qt5 1702 mod = importlib.import_module("PyQt5.QtWebEngineWidgets") 1703 self.browser = mod.QWebEngineView(self) 1704 return 1705 except Exception: 1706 pass 1707 1708 try: 1709 # Try Qt6 first 1710 view = importlib.import_module("PyQt6.QtWebEngineWidgets") 1711 self.browser = view.QWebEngineView( parent=self ) 1712 return 1713 except Exception as e: 1714 print(e) 1715 pass 1716 1717 raise ImportError( 1718 "No QtWebEngine found. Install PyQt6-WebEngine or PyQtWebEngine." 1719 ) 1720 1721 1722 def create_plotwidget(self): 1723 """ Create plot window """ 1724 try: 1725 self.import_webengineview() 1726 self.webengine = True 1727 except: 1728 self.webengine = False 1729 self.browser = NoEngineViewer() 1730 return self.browser 1731 print(self.webengine) 1732 return self.browser 1733 1734class NoEngineViewer(QWidget): 1735 def __init__(self): 1736 super().__init__() 1737 layout = QVBoxLayout() 1738 self.text_browser = QTextBrowser() 1739 self.text_browser.setHtml("<h2>Plots will be redirected to web browser</h2>" \ 1740 "" \ 1741 "Your napari installation is using pyside6 that doesn't have the necessary dependency to show the plot in this window interactively. To have this option, reinstall napari with pyqt5 or pyqt6." \ 1742 "" \ 1743 "Otherwise, you can still see the plot, it will open in your web browser, but will be slower to display, reload the web page if nothing appears." \ 1744 "") 1745 layout.addWidget(self.text_browser) 1746 self.setLayout(layout)
48class Outputing(QWidget): 49 50 def __init__(self, napari_viewer, epic): 51 """ Initialisation of the interface """ 52 super().__init__() 53 self.viewer = napari_viewer 54 self.epicure = epic 55 self.table = None 56 self.table_selection = None 57 self.seglayer = self.viewer.layers["Segmentation"] 58 self.movlayer = self.viewer.layers["Movie"] 59 self.selection_choices = ["All cells", "Only selected cell"] 60 self.output_options = ["", "Export to extern plugins", "Export segmentations", "Measure cell features", "Measure track features", "Export/Measure events", "Save as...", "Save screenshot movie", "Measure vertices"] 61 self.tplots = None 62 63 chanlist = ["Movie"] 64 if self.epicure.others is not None: 65 for chan in self.epicure.others_chanlist: 66 chanlist.append( "MovieChannel_"+str(chan) ) 67 self.cell_features = CellFeatures( chanlist ) 68 self.event_classes = EventClass( self.epicure ) 69 70 all_layout = QVBoxLayout() 71 self.scaled_unit = wid.add_check( "Measures in scaled units", False, check_func=None, descr="Scales the output measures in the given spatio-temporal units (µm, min..)" ) 72 all_layout.addWidget( self.scaled_unit ) 73 self.choose_output = wid.listbox() 74 all_layout.addWidget(self.choose_output) 75 for option in self.output_options: 76 self.choose_output.addItem(option) 77 self.choose_output.currentIndexChanged.connect(self.show_output_option) 78 79 ## Choice of active selection 80 #layout = QVBoxLayout() 81 selection_layout, self.output_mode = wid.list_line( "Apply on", descr="Choose on which cell(s) to do the action", func=None ) 82 for sel in self.selection_choices: 83 self.output_mode.addItem(sel) 84 all_layout.addLayout(selection_layout) 85 86 ## Choice of interface 87 self.export_group, export_layout = wid.group_layout( "Export to extern plugins" ) 88 griot_btn = wid.add_button( "Current frame to Griottes", self.to_griot, "Launch(in new window) Griottes plugin on current frame" ) 89 export_layout.addWidget(griot_btn) 90 ncp_btn = wid.add_button( "Current frame to Cluster-Plotter", self.to_ncp, "Launch (in new window) cluster-plotter plugin on current frame" ) 91 export_layout.addWidget(ncp_btn) 92 self.export_group.setLayout(export_layout) 93 all_layout.addWidget(self.export_group) 94 95 ## Option to export segmentation results 96 self.export_seg_group, layout = wid.group_layout(self.output_options[2]) 97 save_line, self.save_choice = wid.button_list( "Save segmentation as", self.save_segmentation, "Save the current segmentation either as ROI, label image or skeleton" ) 98 self.save_choice.addItem( "labels" ) 99 self.save_choice.addItem( "ROI" ) 100 self.save_choice.addItem( "skeleton" ) 101 layout.addLayout( save_line ) 102 103 self.export_seg_group.setLayout(layout) 104 all_layout.addWidget(self.export_seg_group) 105 106 #### Features group 107 self.feature_group, featlayout = wid.group_layout(self.output_options[3]) 108 109 self.choose_features_btn = wid.add_button( "Choose features...", self.choose_features, "Open a window to select the features to measure" ) 110 featlayout.addWidget(self.choose_features_btn) 111 112 self.feature_table = wid.add_button( "Create features table", self.show_table, "Measure the selected features and display it as a clickable table" ) 113 featlayout.addWidget(self.feature_table) 114 self.featTable = FeaturesTable(self.viewer, self.epicure) 115 featlayout.addWidget(self.featTable) 116 117 ######## Temporal option 118 self.temp_graph = wid.add_button( "Table to temporal graphs...", self.temporal_graphs, "Open a plot interface of measured features temporal evolution" ) 119 featlayout.addWidget(self.temp_graph) 120 self.temp_graph.setEnabled(False) 121 122 ######## Drawing option 123 featmap, self.show_feature_map = wid.list_line( "Draw feature map:", descr="Add a layer with the cells colored by the selected feature value", func=self.show_feature ) 124 featlayout.addLayout(featmap) 125 orienbtn = wid.add_button( "Draw cell orientation", self.draw_orientation, "Add a layer with each cell main axis orientation and length " ) 126 featlayout.addWidget( orienbtn ) 127 128 save_tab_line, self.save_format = wid.button_list( "Save features table", self.save_measure_features, "Save the current table in a .csv file" ) 129 self.save_format.addItem( "csv" ) 130 self.save_format.addItem( "xlsx" ) 131 featlayout.addLayout(save_tab_line) 132 133 ## skrub table 134 self.stat_table = wid.add_button( "Open statistiques table...", self.skrub_features, "Open interactive table with the features statistiques (skrub library)" ) 135 featlayout.addWidget(self.stat_table) 136 137 self.feature_group.setLayout(featlayout) 138 self.feature_group.hide() 139 all_layout.addWidget(self.feature_group) 140 141 ## Track features 142 self.trackfeat_group, trackfeatlayout = wid.group_layout(self.output_options[4]) 143 self.trackfeat_table = wid.add_button( "Track features table", self.show_trackfeature_table, "Measure track-related feature and show a table by track" ) 144 trackfeatlayout.addWidget(self.trackfeat_table) 145 self.trackTable = FeaturesTable(self.viewer, self.epicure) 146 trackfeatlayout.addWidget(self.trackTable) 147 self.save_table_track = wid.add_button( "Save track table", self.save_table_tracks, "Save the current table in a .csv file" ) 148 trackfeatlayout.addWidget(self.save_table_track) 149 150 self.trackfeat_group.setLayout(trackfeatlayout) 151 self.trackfeat_group.hide() 152 all_layout.addWidget(self.trackfeat_group) 153 154 ## Option to export/measure events (Fiji ROI or table), + graphs ? 155 self.handle_event_group, elayout = wid.group_layout(self.output_options[5]) 156 self.choose_events_btn = wid.add_button( "Choose events...", self.choose_events, "Open a window to select the events to export/measure" ) 157 elayout.addWidget( self.choose_events_btn ) 158 save_evt_line, self.save_evt_choice = wid.button_list( "Export events as", self.export_events, "Save the checked events as Fiji ROIs or .csv table" ) 159 self.save_evt_choice.addItem( "Fiji ROI" ) 160 self.save_evt_choice.addItem( "CSV File" ) 161 elayout.addLayout( save_evt_line ) 162 count_evt_btn = wid.add_button( "Count events", self.temporal_graphs_events, descr="Create temporal plot of number of events" ) 163 elayout.addWidget( count_evt_btn ) 164 165 self.handle_event_group.setLayout( elayout ) 166 self.handle_event_group.hide() 167 all_layout.addWidget( self.handle_event_group ) 168 169 ## Save TrackMate XML option 170 self.save_as_group, save_as_layout = wid.group_layout( "Save as..." ) 171 self.save_tm_btn = wid.add_button( "Save as TrackMate XML", self.save_tm_xml, "Save the current segmentation and the optional tracking in a TrackMate XML file" ) 172 self.save_geff_btn = wid.add_button( "Save as GEFF", self.save_geff, "Save the segmentation and tracks to GEFF" ) 173 save_as_layout.addWidget( self.save_tm_btn ) 174 save_as_layout.addWidget( self.save_geff_btn ) 175 176 self.save_as_group.setLayout( save_as_layout ) 177 self.save_as_group.hide() 178 all_layout.addWidget( self.save_as_group ) 179 180 ## Save screenshots option 181 current_frame = ut.current_frame( self.epicure.viewer ) 182 self.screenshot_group, screenshot_layout = wid.group_layout( "Save screenshot movie" ) 183 self.show_scalebar = wid.add_check_tolayout( screenshot_layout, "With the scale bar", True, check_func=None, descr="Show the scale bar in the screenshots" ) 184 sframe_line, self.sframe = wid.slider_line( "From frame", 0, self.epicure.nframes, 1, value=current_frame, show_value=True, slidefunc=None, descr="Frame from which to start saving screenshots" ) 185 eframe_line, self.eframe = wid.slider_line( "To frame", 0, self.epicure.nframes, 1, value=current_frame+1, show_value=True, slidefunc=None, descr="Frame until which to save screenshots" ) 186 screenshot_layout.addLayout( sframe_line ) 187 screenshot_layout.addLayout( eframe_line ) 188 savescreen_btn = wid.add_button( "Save current view", self.screenshot_movie, "Save the current view (with current display parameters) for frame between the two specified frames in a movie" ) 189 190 screenshot_layout.addWidget(savescreen_btn) 191 self.screenshot_group.setLayout(screenshot_layout) 192 all_layout.addWidget(self.screenshot_group) 193 self.screenshot_group.hide() 194 195 ## Measure vertex options 196 self.vertex_group, vertices_layout = wid.group_layout( "Measure vertices" ) 197 radius_line, self.vertice_radius = wid.value_line("Vertex radius", "1.25", descr="Radius of a vertex (TCJ) to consider as one point and measure intensities") 198 display_radius_line, self.vertice_display_radius = wid.value_line("Display radius", "3", descr="Radius of a vertex for DISPLAY only (size of drawing in the layer)") 199 vertices_layout.addLayout(radius_line) 200 vertices_layout.addLayout(display_radius_line) 201 self.vertices_btn = wid.add_button( "Measure", self.show_vertices_table, "Measure the vertices (connectivity, intensity)" ) 202 vertices_layout.addWidget( self.vertices_btn ) 203 self.verticesTable = FeaturesTable(self.viewer, self.epicure) 204 vertices_layout.addWidget(self.verticesTable) 205 self.save_table_vertices = wid.add_button( "Save vertices table", self.save_vertices_table, "Save the current table in a .csv file" ) 206 vertices_layout.addWidget(self.save_table_vertices) 207 208 self.vertex_group.setLayout( vertices_layout ) 209 all_layout.addWidget(self.vertex_group) 210 self.vertex_group.hide() 211 212 ## Finished 213 self.setLayout(all_layout) 214 self.show_output_option() 215 216 def get_current_settings( self ): 217 """ Returns current settings of the widget """ 218 disp = {} 219 disp["Apply on"] = self.output_mode.currentText() 220 disp["Current option"] = self.choose_output.currentText() 221 disp["Show scalebar"] = self.show_scalebar.isChecked() 222 disp = self.cell_features.get_current_settings( disp ) 223 disp = self.event_classes.get_current_settings( disp ) 224 return disp 225 226 def apply_settings( self, settings ): 227 """ Set the current state of the widget from preferences if any """ 228 for setting, val in settings.items(): 229 if setting == "Apply on": 230 self.output_mode.setCurrentText( val ) 231 if setting == "Current option": 232 self.choose_output.setCurrentText( val ) 233 if setting == "Show scalebar": 234 self.show_scalebar.setChecked( val ) 235 236 self.cell_features.apply_settings( settings ) 237 self.event_classes.apply_settings( settings ) 238 239 def screenshot_movie( self ): 240 """ Save screenshots of the current view """ 241 scale_visibility = self.viewer.scale_bar.visible 242 current_frame = ut.current_frame( self.epicure.viewer ) 243 self.viewer.scale_bar.visible = self.show_scalebar.isChecked() 244 start_frame = max( self.sframe.value(), 0 ) 245 end_frame = min( self.eframe.value(), self.epicure.nframes ) 246 outname = os.path.join( self.epicure.outdir, self.epicure.imgname+"_screenshots_f"+str(start_frame)+"-"+str(end_frame)+".tif" ) 247 if os.path.exists(outname): 248 os.remove(outname) 249 if start_frame > end_frame: 250 ut.show_warning("From frame > to frame, no screenshot saved") 251 return 252 for frame in range(start_frame, end_frame+1): 253 self.viewer.dims.set_point(0, frame) 254 shot = self.viewer.screenshot( canvas_only=True, flash=False ) 255 ut.appendToTif( shot, outname ) 256 self.viewer.scale_bar.visible = scale_visibility 257 self.viewer.dims.set_point(0, current_frame) 258 ut.show_info( "Screenshot movie saved in "+outname ) 259 260 def events_select( self, event, check ): 261 """ Check/Uncheck the event in event types list """ 262 if event in self.event_classes.evt_classes: 263 self.event_classes.evt_classes[ event ][0].setChecked( check ) 264 else: 265 print(event+" not found in possible event types to export") 266 267 def show_output_option(self): 268 """ Show selected output panel """ 269 cur_option = self.choose_output.currentText() 270 self.export_group.setVisible( cur_option == "Export to extern plugins" ) 271 self.export_seg_group.setVisible( cur_option == "Export segmentations" ) 272 self.feature_group.setVisible( cur_option == "Measure cell features" ) 273 self.vertex_group.setVisible( cur_option == "Measure vertices" ) 274 self.trackfeat_group.setVisible( cur_option == "Measure track features" ) 275 self.handle_event_group.setVisible( cur_option == "Export/Measure events" ) 276 self.save_as_group.setVisible( cur_option == "Save as..." ) 277 self.screenshot_group.setVisible( cur_option == "Save screenshot movie" ) 278 279 def get_current_labels( self ): 280 """ Get the cell labels to process according to current selection of apply on""" 281 if self.output_mode.currentText() == "Only selected cell": 282 lab = self.epicure.seglayer.selected_label 283 return [lab] 284 if self.output_mode.currentText() == "All cells": 285 return self.epicure.get_labels() 286 else: 287 group = self.output_mode.currentText() 288 label_group = self.epicure.groups[group] 289 return label_group 290 291 292 def get_selection_name(self): 293 if self.output_mode.currentText() == "Only selected cell": 294 lab = self.epicure.seglayer.selected_label 295 return "_cell_"+str(lab) 296 #if self.output_mode.currentText() == "Only checked cells": 297 # return "_checked_cells" 298 if self.output_mode.currentText() == "All cells": 299 return "" 300 return "_"+self.output_mode.currentText() 301 302 def skrub_features( self ): 303 """ Open html table interactive and stats with skrub module """ 304 try: 305 from skrub import TableReport 306 except: 307 ut.show_error( "Needs skrub library for this option. Install it (`pip install skrub`) before" ) 308 return 309 if self.table is None: 310 ut.show_warning( "Create/update the table before" ) 311 return 312 report = TableReport( self.table ) 313 report.open() 314 315 316 def save_measure_features(self): 317 """ Save measures table to file whether it was created or not """ 318 if self.table is None or self.table_selection is None or self.selection_changed() : 319 ut.show_warning("Create/update the table before") 320 return 321 ext = self.save_format.currentText() 322 outfile = self.epicure.outname()+"_features"+self.get_selection_name()+"."+ext 323 if ext == "xlsx": 324 self.table.to_excel( outfile, sheet_name='EpiCureMeasures' ) 325 else: 326 self.table.to_csv( outfile, index=False ) 327 if self.epicure.verbose > 0: 328 ut.show_info("Measures saved in "+outfile) 329 330 def save_table_tracks(self): 331 """ Save tracks table to file whether it was created or not """ 332 if self.table is None or self.table_selection is None or self.selection_changed() : 333 ut.show_warning("Create/update the table before") 334 return 335 outfile = self.epicure.outname()+"_trackfeatures"+self.get_selection_name()+".xlsx" 336 self.table.to_excel( outfile, sheet_name='EpiCureTrackMeasures' ) 337 if self.epicure.verbose > 0: 338 ut.show_info("Track measures saved in "+outfile) 339 340 341 def save_one_roi(self, lab): 342 """ Save the Rois of cell with label lab """ 343 keep = self.seglayer.data == lab 344 rois = [] 345 if np.sum(keep) > 0: 346 ## add 2D case 347 for iframe, frame in enumerate(keep): 348 if np.sum(frame) > 0: 349 contour = ut.get_contours(frame) 350 roi = self.create_roi(contour[0], iframe, lab) 351 rois.append(roi) 352 353 roifile.roiwrite(self.epicure.outname()+"_rois_cell_"+str(lab)+".zip", rois, mode='w') 354 355 def create_roi(self, contour, frame, label): 356 croi = roifile.ImagejRoi() 357 croi.version = 227 358 croi.roitype = roifile.ROI_TYPE(0) ## polygon 359 croi.n_coordinates = len(contour) 360 croi.position = frame + 1 361 croi.t_position = frame+1 362 coords = [] 363 cent0 = 0 364 cent1 = 0 365 for cont in contour: 366 coords.append([int(cont[1]), int(cont[0])]) 367 cent0 += cont[1] 368 cent1 += cont[0] 369 croi.integer_coordinates = np.array(coords) 370 #croi.top = int(np.min(coords[0])) 371 #croi.left = int(np.min(coords[1])) 372 croi.name = str(frame+1).zfill(4)+'-'+str(int(cent0/len(contour))).zfill(4)+"-"+str(int(cent1/len(contour))).zfill(4) 373 return croi 374 375 def save_segmentation( self ): 376 """ Save current segmentation in selected format """ 377 if self.output_mode.currentText() == "Only selected cell": 378 ## output only the selected cell 379 lab = self.seglayer.selected_label 380 if self.save_choice.currentText() == "ROI": 381 self.save_one_roi(lab) 382 if self.epicure.verbose > 0: 383 ut.show_info("Cell "+str(lab)+" saved to Fiji ROI") 384 return 385 else: 386 tosave = np.zeros(self.seglayer.data.shape, dtype=self.epicure.dtype) 387 if np.sum(self.seglayer.data==lab) > 0: 388 tosave[self.seglayer.data==lab] = lab 389 endname = "_"+self.save_choice.currentText()+"_"+str(lab)+".tif" 390 else: 391 ## output all cells 392 if self.output_mode.currentText() == "All cells": 393 if self.save_choice.currentText() == "ROI": 394 self.save_all_rois() 395 return 396 tosave = self.seglayer.data 397 endname = "_"+self.save_choice.currentText()+".tif" 398 else: 399 ## or output only selected group 400 group = self.output_mode.currentText() 401 label_group = self.epicure.groups[group] 402 if self.save_choice.currentText() == "ROI": 403 ncells = 0 404 for lab in label_group: 405 self.save_one_roi(lab) 406 ncells += 1 407 if self.epicure.verbose > 0: 408 ut.show_info(str(ncells)+" cells saved to Fiji ROIs") 409 return 410 tosave = np.zeros(self.seglayer.data.shape, dtype=self.epicure.dtype) 411 endname = "_"+self.save_choice.currentText()+"_"+self.output_mode.currentText()+".tif" 412 for lab in label_group: 413 tosave[self.seglayer.data==lab] = lab 414 415 ## save filled image (for label or skeleton) to file 416 outname = os.path.join( self.epicure.outdir, self.epicure.imgname+endname ) 417 if self.save_choice.currentText() == "skeleton": 418 parallel = 0 419 if self.epicure.process_parallel: 420 parallel = self.epicure.nparallel 421 tosave = ut.get_skeleton( tosave, viewer=self.viewer, verbose=self.epicure.verbose, parallel=parallel ) 422 ut.writeTif( tosave, outname, self.epicure.epi_metadata["ScaleXY"], 'uint8', what="Skeleton" ) 423 else: 424 ut.writeTif(tosave, outname, self.epicure.epi_metadata["ScaleXY"], 'float32', what="Segmentation") 425 426 def save_all_rois( self ): 427 """ Save all cells to ROI format """ 428 ncells = 0 429 for lab in np.unique(self.epicure.seglayer.data): 430 self.save_one_roi(lab) 431 ncells += 1 432 if self.epicure.verbose > 0: 433 ut.show_info(str(ncells)+" cells saved to Fiji ROIs") 434 435 def choose_features( self ): 436 """ Pop-up widget to choose the features to measure """ 437 self.cell_features.choose() 438 439 def show_vertices_table(self): 440 """ Show the measurement of vertices table """ 441 self.measure_vertices() 442 self.verticesTable.set_table(self.table) 443 444 def save_vertices_table(self): 445 """ Save vertices table to file whether it was created or not """ 446 if self.table is None: 447 ut.show_warning("Create/update the table before") 448 return 449 outfile = self.epicure.outname()+"_vertices"+".xlsx" 450 self.table.to_excel( outfile, sheet_name='EpiCureVerticesMeasures' ) 451 if self.epicure.verbose > 0: 452 ut.show_info("Vertices measures saved in "+outfile) 453 454 455 def measure_vertices(self): 456 """ Get all vertices (TCJ) and measure their properties """ 457 def nb_neighbors(regionmask, labimg): 458 """ Measure the nb of neighbors (labels) around each point """ 459 #footprint = disk(radius=8) 460 #dilated = binary_dilation(regionmask, footprint) 461 labels = np.unique(labimg[regionmask]).tolist() 462 nb_nei = len(labels) 463 if 0 in labels: 464 nb_nei = nb_nei - 1 465 return nb_nei 466 467 self.table = None 468 radius = float(self.vertice_radius.text()) 469 display_radius = float(self.vertice_display_radius.text()) 470 ## difference between the measured radius and the displayed radius 471 diff_radius = display_radius - radius 472 if diff_radius < 0: 473 diff_radius = 0 474 parallel = 0 475 if self.epicure.process_parallel: 476 parallel = self.epicure.nparallel 477 ## Get the vertices: junctions of several skeleton lines 478 vertex_img = ut.get_vertices( self.epicure.seg, viewer=None, verbose=self.epicure.verbose, parallel=parallel ) 479 vertices_img = np.zeros(vertex_img.shape, dtype=np.int8) 480 ## Individualise, measure, draw 481 for ind, frame in enumerate(vertex_img): 482 props = ut.binary_properties(frame) 483 nvertex = len(props) 484 vertices = [] 485 for prop in props: 486 if prop.label > 0: 487 pt = prop.centroid 488 vertices.append(pt) 489 vert_img = ut.draw_points(vertices, vertex_img.shape[1:], radius=radius) 490 props = ut.binary_properties(vert_img) 491 if nvertex != len(props): 492 ## one or more vertices had been merged 493 vertices = [] 494 for prop in props: 495 if prop.label > 0: 496 pt = prop.centroid 497 vertices.append(pt) 498 vert_img = ut.draw_points(vertices, vertex_img.shape[1:], radius=radius) 499 #vertices_img[ind] = vert_img 500 lbl_img = label(vert_img) 501 int_measures = pand.DataFrame(ut.regionprops_table(lbl_img, self.movlayer.data[ind], properties=["label", "centroid", "intensity_mean"])) 502 ## expand to measure neighbors 503 exp_lbl = ut.touching_labels(lbl_img, expand=3) 504 measures = pand.DataFrame(ut.regionprops_table(exp_lbl, self.epicure.seg[ind], properties=["label"], extra_properties=[nb_neighbors] )) 505 ## Color the vertices by their number of neighbors 506 for lab in measures["label"]: 507 vertices_img[ind][lbl_img==lab] = int(measures.loc[measures["label"]==lab,"nb_neighbors"].iloc[0]) 508 df = pand.merge(int_measures, measures, on="label", how="inner") 509 df["Frame"] = ind 510 511 if self.table is None: 512 self.table = df 513 else: 514 self.table = pand.concat([self.table, df]) 515 516 ## Expand for display only 517 vertices_img[ind] = ut.touching_labels(vertices_img[ind], expand=diff_radius) 518 519 ## Display the vertices in a new layer 520 ut.remove_layer(self.viewer, "Vertices") # in case already present 521 self.viewer.add_labels(vertices_img, blending="additive", name="Vertices", scale=self.viewer.layers["Movie"].scale, opacity=1) 522 523 def measure_features(self): 524 """ Measure features and put them to table """ 525 thick = self.epicure.thickness 526 527 def intensity_junction_cytoplasm(regionmask, intensity): 528 """ Measure the intensity only on the contour of regionmask """ 529 footprint = disk(radius=thick) 530 inside = binary_erosion(regionmask, footprint) 531 inside_intensity = ut.mean_nonzero(intensity*inside) 532 periph_intensity = ut.mean_nonzero(intensity*(regionmask^inside)) 533 return inside_intensity, periph_intensity 534 535 if self.epicure.verbose > 0: 536 print("Measuring features") 537 #self.viewer.window._status_bar._toggle_activity_dock(True) 538 pb = ut.start_progress( self.viewer, total=2, descr="Measuring cells in all movie" ) 539 start_time = time.time() 540 if self.output_mode.currentText() == "Only selected cell": 541 meas = np.zeros(self.epicure.seglayer.data.shape, self.epicure.dtype) 542 lab = self.epicure.seglayer.selected_label 543 meas[self.epicure.seglayer.data==lab] = lab 544 else: 545 if self.output_mode.currentText() == "All cells": 546 meas = self.epicure.seglayer.data 547 else: 548 group = self.output_mode.currentText() 549 meas = np.zeros(self.epicure.seglayer.data.shape, self.epicure.dtype) 550 label_group = self.epicure.groups[group] 551 for lab in label_group: 552 meas[self.epicure.seglayer.data==lab] = lab 553 554 properties, prop_extra, other_features, int_feat, int_extrafeat = self.cell_features.get_features() 555 do_channels = self.cell_features.get_channels() 556 extra_prop = [] 557 if "intensity_junction_cytoplasm" in int_extrafeat: 558 extra_prop = extra_prop + [intensity_junction_cytoplasm] 559 560 extra_properties = [] 561 if (do_channels is not None) and ("Movie" in do_channels): 562 properties = properties + int_feat 563 for extra in int_extrafeat: 564 if extra == "intensity_junction_cytoplasm": 565 extra_properties = extra_properties + [intensity_junction_cytoplasm] 566 567 pb.update() 568 labgroups = self.epicure.group_of_labels() 569 pb.total = self.epicure.nframes 570 chan_dict = dict() 571 if ( do_channels is not None ): 572 for chan in do_channels: 573 if chan == "Movie": 574 continue 575 chan_dict[chan] = self.viewer.layers[chan].data 576 seg = self.epicure.seg 577 mov = self.movlayer.data 578 579 def measure_one_frame_collect( img, frame ): 580 """ Measure on one frame and return a list of dicts for each label """ 581 #pb.update() 582 intimg = mov[frame] 583 frame_table = pand.DataFrame( ut.labels_table(img, intensity_image=intimg, properties=properties, extra_properties=extra_properties) ) 584 if "group" in other_features: 585 frame_table["group"] = frame_table["label"].map(labgroups).fillna("Ungrouped") 586 frame_table["frame"] = frame 587 588 # Boundary 589 if "Boundary" in other_features: 590 boundimg = seg[frame] 591 bds = ut.get_boundary_cells(boundimg) 592 frame_table["Boundary"] = frame_table["label"].isin(bds).astype(int) 593 # Border 594 if "Border" in other_features: 595 bds = ut.get_border_cells(img) 596 frame_table["Border"] = frame_table["label"].isin(bds).astype(int) 597 598 # Intensity features in other channels 599 for chan, intimg_chan in chan_dict.items(): 600 intimg_frame = intimg_chan[frame] 601 frame_tab = ut.labels_table(img, intensity_image=intimg_frame, properties=int_feat, extra_properties=extra_prop) 602 for add_prop in int_feat: 603 frame_table[add_prop+"_"+str(chan)] = frame_tab[add_prop] 604 if "intensity_junction_cytoplasm-0" in frame_tab.keys(): 605 frame_table["intensity_cytoplasm_"+str(chan)] = frame_tab["intensity_junction_cytoplasm-0"] 606 frame_table["intensity_junction_"+str(chan)] = frame_tab["intensity_junction_cytoplasm-1"] 607 608 if prop_extra != []: 609 if "shape_index" in prop_extra: 610 frame_table["shape_index"] = frame_table["perimeter"] / np.sqrt(frame_table["area"]) 611 if "roundness" in prop_extra: 612 frame_table["roundness"] = 4*frame_table["area"] /(np.pi * np.power(frame_table["axis_major_length"],2) ) 613 if "aspect_ratio" in prop_extra: 614 frame_table["aspect_ratio"] = frame_table["axis_major_length"] / frame_table["axis_minor_length"] 615 616 # Neighbor features 617 do_neighbor = "NbNeighbors" in other_features 618 get_neighbor = "Neighbors" in other_features 619 if do_neighbor or get_neighbor: 620 nimg = seg[frame] 621 graph = ut.get_neighbor_graph(nimg, distance=3) 622 all_neighbors = {label: list(graph.adj[label]) for label in graph.nodes} 623 frame_table["neighborlist"] = frame_table["label"].map(lambda l: all_neighbors.get(l, [])) 624 625 if do_neighbor: 626 frame_table["NbNeighbors"] = frame_table["neighborlist"].apply( 627 lambda x: len(x) if x else -1 628 ) 629 if get_neighbor: 630 frame_table["Neighbors"] = frame_table["neighborlist"].apply( 631 lambda x: "&".join(map(str, x)) if x else "" 632 ) 633 if do_neighbor or get_neighbor: 634 frame_table.drop(columns="neighborlist", inplace=True) 635 return pand.DataFrame( frame_table.to_dict(orient="records") ) 636 637 if self.epicure.process_parallel: 638 frame_tables = Parallel( n_jobs=self.epicure.nparallel ) ( 639 delayed( measure_one_frame_collect ) ( frame, iframe ) for iframe, frame in enumerate(meas) ) 640 else: 641 frame_tables = [ 642 measure_one_frame_collect( frame, iframe ) 643 for iframe, frame in (enumerate(meas)) 644 ] 645 self.table = pand.concat(frame_tables, ignore_index=True) 646 647 if "intensity_junction_cytoplasm-0" in self.table.columns: 648 self.table = self.table.rename(columns={"intensity_junction_cytoplasm-0": "intensity_cytoplasm", "intensity_junction_cytoplasm-1":"intensity_junction"}) 649 self.table_selection = self.selection_choices.index(self.output_mode.currentText()) 650 ut.close_progress( self.viewer, pb ) 651 #self.viewer.window._status_bar._toggle_activity_dock(False) 652 if self.epicure.verbose > 0: 653 ut.show_info("Features measured in "+"{:.3f}".format((time.time()-start_time)/60)+" min") 654 655 def measure_one_frame(self, img, properties, extra_properties, other_features, channels, int_feat, int_extrafeat, frame, labgroups, prop_extra ): 656 """ Measure on one frame """ 657 if frame is not None: 658 intimg = self.movlayer.data[frame] 659 else: 660 intimg = self.movlayer.data 661 first = "label" not in self.table.keys() 662 nrows = len(self.table["label"]) if "label" in self.table.keys() else 0 663 664 ## add the basic label measures 665 frame_table = ut.labels_table( img, intensity_image=intimg, properties=properties, extra_properties=extra_properties ) 666 ndata = len(frame_table["label"]) 667 for key, value in frame_table.items(): 668 if first: 669 self.table[key] = [] 670 self.table[key].extend(list(value)) 671 672 ## add the frame column 673 if frame is not None: 674 if first: 675 self.table["frame"] = [] 676 self.table["frame"].extend([frame]*ndata) 677 678 ## add info of the cell group 679 if "group" in other_features: 680 frame_group = [ labgroups[label] if label in labgroups.keys() else "Ungrouped" for label in frame_table["label"] ] 681 if first: 682 self.table["group"] = [] 683 self.table["group"].extend( frame_group ) 684 685 ## add the extra shape features 686 if prop_extra != []: 687 if "shape_index" in prop_extra: 688 si = frame_table["perimeter"] /np.sqrt( frame_table["area"] ) 689 if first: 690 self.table["shape_index"] = [] 691 self.table["shape_index"].extend( si ) 692 if "roundness" in prop_extra: 693 rou = 4*frame_table["area"] /(np.pi * np.power(frame_table["axis_major_length"],2) ) 694 if first: 695 self.table["roundness"] = [] 696 self.table["roundness"].extend( rou ) 697 if "aspect_ratio" in prop_extra: 698 ar = list( np.array(frame_table["axis_major_length"])/np.array(frame_table["axis_minor_length"]) ) 699 if first: 700 self.table["aspect_ratio"] = [] 701 self.table["aspect_ratio"].extend( ar ) 702 703 ### Measure intensity features in other chanels if option is on 704 if (channels is not None): 705 for chan in channels: 706 ## if it's movie, already measured in the general measure 707 if chan == "Movie": 708 continue 709 ## otherwise, do a new measure on the selected channels 710 if frame is not None: 711 intimg = self.viewer.layers[chan].data[frame] 712 else: 713 intimg = self.viewer.layers[chan].data 714 frame_tab = ut.labels_table( img, intensity_image=intimg, properties=int_feat, extra_properties=int_extrafeat ) 715 for add_prop in int_feat: 716 if first: 717 self.table[add_prop+"_"+chan] = [] 718 self.table[add_prop+"_"+chan].extend( list(frame_tab[add_prop]) ) 719 if "intensity_junction_cytoplasm-0" in frame_tab.keys(): 720 if first: 721 self.table["intensity_cytoplasm_"+chan] = [] 722 self.table["intensity_junction_"+str(chan)] = [] 723 self.table["intensity_cytoplasm_"+chan].extend( list(frame_tab["intensity_junction_cytoplasm-0"]) ) 724 self.table["intensity_junction_"+str(chan)].extend( list(frame_tab["intensity_junction_cytoplasm-1"]) ) 725 726 727 ## add features of neighbors relationship with graph 728 do_neighbor = "NbNeighbors" in other_features 729 get_neighbor = "Neighbors" in other_features 730 if do_neighbor or get_neighbor: 731 if frame is not None: 732 nimg = self.epicure.seg[frame] 733 else: 734 nimg = self.epicure.seg 735 #start_time = ut.start_time() 736 graph = ut.get_neighbor_graph( nimg, distance=3 ) 737 738 if first: 739 if do_neighbor: 740 self.table["NbNeighbors"] = [] 741 if get_neighbor: 742 self.table["Neighbors"] = [] 743 if do_neighbor: 744 self.table["NbNeighbors"].extend( [-1]*ndata ) 745 if get_neighbor: 746 self.table["Neighbors"].extend( [""]*ndata ) 747 748 for label in np.unique(frame_table["label"]): 749 if label in graph.nodes: 750 rlabel = np.where( (frame_table["label"] == label) )[0] 751 nneighbor = len(graph.adj[label]) 752 for ind in rlabel: 753 if do_neighbor: 754 self.table["NbNeighbors"][ind+nrows] = nneighbor 755 if get_neighbor: 756 self.table["Neighbors"][ind+nrows] = "" 757 sep = "" 758 for key in graph.adj[label].keys(): 759 self.table["Neighbors"][ind+nrows] += sep + str(key) 760 sep = "&" 761 #ut.show_duration( start_time, "Neighborhoods measured" ) 762 763 ## measure cells on boundary 764 if "Boundary" in other_features: 765 if frame is not None: 766 boundimg = self.epicure.seg[frame] 767 else: 768 boundimg = self.epicure.seg 769 bounds = ut.get_boundary_cells( boundimg ) 770 if first: 771 self.table["Boundary"] = [] 772 self.table["Boundary"].extend( [0]*ndata ) 773 for label in np.unique(frame_table["label"]): 774 if label in bounds: 775 rlabel = np.where( (frame_table["label"] == label) )[0] 776 for ind in rlabel: 777 self.table["Boundary"][ind+nrows] = 1 778 779 ## measure cells on border 780 if "Border" in other_features: 781 bounds = ut.get_border_cells( img ) 782 if first: 783 self.table["Border"] = [] 784 self.table["Border"].extend( [0]*ndata ) 785 for label in bounds: 786 rlabel = np.where( (frame_table["label"] == label) )[0] 787 for ind in rlabel: 788 self.table["Border"][ind+nrows] = 1 789 790 791 def selection_changed(self): 792 if self.table_selection is None: 793 return True 794 return self.output_mode.currentText() != self.selection_choices[self.table_selection] 795 796 def update_selection_list(self): 797 """ Update the possible selection from group cell list """ 798 self.selection_choices = ["Only selected cell", "All cells"] 799 for group in self.epicure.groups.keys(): 800 self.selection_choices.append(group) 801 self.output_mode.clear() 802 for sel in self.selection_choices: 803 self.output_mode.addItem(sel) 804 805 def show_table(self): 806 """ Show the measurement table """ 807 #disable automatic update (slow) 808 #if self.table is None: 809 ## create the table and connect action to update it automatically 810 #self.output_mode.currentIndexChanged.connect(self.show_table) 811 #self.measure_other_chanels_cbox.stateChanged.connect(self.show_table) 812 #self.feature_graph_cbox.stateChanged.connect(self.show_table) 813 #self.feature_intensity_cbox.stateChanged.connect(self.show_table) 814 #self.feature_shape_cbox.stateChanged.connect(self.show_table) 815 816 ut.set_active_layer( self.viewer, "Segmentation" ) 817 self.show_feature_map.clear() 818 self.show_feature_map.addItem("") 819 laynames = [lay.name for lay in self.viewer.layers] 820 for lay in laynames: 821 if lay.startswith("Map_"): 822 ut.remove_layer(self.viewer, lay) 823 self.measure_features() 824 featlist = self.table.keys() 825 ## Scaling the features 826 if self.scaled_unit.isChecked(): 827 for feat in featlist: 828 feat_scale, scaled = self.scale_feature( feat, self.table[feat] ) 829 if feat_scale is not None: 830 if (feat_scale[0:4] != "Time") and (feat_scale[0:9] != "centroid-"): 831 del self.table[feat] 832 self.table[feat_scale] = scaled 833 featlist = self.table.keys() 834 ## Adding the list to the feature maps 835 for feat in featlist: 836 self.show_feature_map.addItem(feat) 837 self.featTable.set_table(self.table) 838 self.temp_graph.setEnabled(True) 839 if self.tplots is not None: 840 self.tplots.update_table(self.table) 841 842 def scale_feature( self, feat, featVals ): 843 """ Scale if necessary the feature values """ 844 dist_feats = ["centroid-0", "centroid-1", "perimeter", "axis_major_length", "axis_minor_length", "feret_diameter_max", "equivalent_diameter_area" ] 845 if feat in dist_feats: 846 return feat+"_"+self.epicure.epi_metadata["UnitXY"], np.array(featVals)*self.epicure.epi_metadata["ScaleXY"] 847 area_feats = ["area", "area_convex"] 848 if feat in area_feats: 849 return feat+"_"+self.epicure.epi_metadata["UnitXY"]+"²", np.array(featVals)*self.epicure.epi_metadata["ScaleXY"] * self.epicure.epi_metadata["ScaleXY"] 850 if feat == "frame": 851 return "Time_"+self.epicure.epi_metadata["UnitT"], np.array(featVals)*self.epicure.epi_metadata["ScaleT"] 852 return None, None 853 854 855 def show_feature(self): 856 """ Add the image map of the selected feature """ 857 feat = self.show_feature_map.currentText() 858 if (feat is not None) and (feat != ""): 859 if feat in self.table.keys(): 860 values = list(self.table[feat]) 861 if feat == "group": 862 for i, val in enumerate(values): 863 if (val is None) or (val == 'None'): 864 values[i] = 0 865 else: 866 values[i] = list(self.epicure.groups.keys()).index(val) + 1 867 labels = list(self.table["label"]) 868 frames = None 869 if "frame" in self.table: 870 frames = list(self.table["frame"]) 871 self.draw_map(labels, values, frames, feat) 872 873 def draw_map(self, labels, values, frames, featname): 874 """ Add image layer of values by label """ 875 ## special feature: orientation, draw the axis instead 876 self.viewer.window._status_bar._toggle_activity_dock(True) 877 labels = np.array(labels) 878 values = np.array(values) 879 frames = np.array(frames) 880 def map_frame( iframe, segframe ): 881 """ Draw one frame of the map """ 882 mask = np.where(frames==iframe)[0] 883 labs = labels[mask] 884 vals = values[mask] 885 mapping = np.zeros(segframe.max()+1) 886 mapping[:] = np.nan 887 mapping[labs] = vals 888 return mapping[segframe] 889 890 if frames is not None: 891 ## Plotting a movie 892 if self.epicure.process_parallel: 893 mapfeat = Parallel( n_jobs=self.epicure.nparallel) ( 894 delayed ( map_frame )(iframe, frame ) for iframe, frame in enumerate(self.seglayer.data) 895 ) 896 mapfeat = np.array(mapfeat) 897 else: 898 mapfeat = np.empty(self.epicure.seg.shape, dtype="float16") 899 mapfeat[:] = np.nan 900 for iframe in np.unique(frames): 901 segdata = self.seglayer.data[iframe] 902 mapfeat[iframe] = map_frame( iframe, segdata ) 903 else: 904 mapfeat = np.empty(self.epicure.seg.shape, dtype="float16") 905 mapfeat[:] = np.nan 906 for lab, val in progress(zip(labels, values)): 907 cell = self.seglayer.data==lab 908 mapfeat[cell] = val 909 ut.remove_layer(self.viewer, "Map_"+featname) 910 self.viewer.add_image(mapfeat, name="Map_"+featname, scale=self.viewer.layers["Segmentation"].scale ) 911 self.viewer.window._status_bar._toggle_activity_dock(False) 912 913 def draw_orientation( self ): 914 """ Display the cells orientation axis in a new layer """ 915 ## check that necessary features are measured 916 ut.remove_layer( self.viewer, "CellOrientation" ) 917 feats = ["centroid-0", "centroid-1", "orientation"] 918 if self.table is None: 919 print("Features centroid and orientation necessary to draw orientation, but are not measured yet") 920 return 921 for feat in feats: 922 if feat not in self.table.keys(): 923 print("Feature "+feat+" necessary to draw orientation, but was not measured") 924 return 925 ## ok, can work now 926 self.viewer.window._status_bar._toggle_activity_dock(True) 927 928 ## get the coordinates of the axis lines by getting the cell centroid, main orientation 929 xs = np.array( self.table["centroid-0"] ) 930 ys = np.array( self.table["centroid-1"] ) 931 angles = np.array( self.table["orientation"] ) 932 lens = np.array( [10]*len(angles) ) 933 oriens = np.zeros( (self.epicure.seg.shape), dtype="uint8" ) 934 935 ## draw axis length depending on the eccentricity 936 if "eccentricity" in self.table.keys(): 937 lens = np.array(self.table["eccentricity"]*16) 938 939 if "frame" in self.table: 940 frames = np.array( self.table["frame"] ).astype(int) 941 else: 942 frames = np.array( [0]*len(angles) ) 943 944 ## draw the lines in between the two extreme points (using Shape layer is too slow on display for big movies) 945 npts = 30 946 xmax = oriens.shape[1]-1 947 ymax = oriens.shape[2]-1 948 for i in range(npts): 949 xas = np.clip(xs - lens/2 * np.cos( angles ) * i/float(npts), 0, xmax).astype(int) 950 xbs = np.clip(xs + lens/2 * np.cos( angles ) * i/float(npts), 0, xmax).astype(int) 951 yas = np.clip(ys - lens/2 * np.sin( angles ) * i/float(npts), 0, ymax).astype(int) 952 ybs = np.clip(ys + lens/2 * np.sin( angles ) * i/float(npts), 0, ymax).astype(int) 953 oriens[ (frames, xas, yas) ] = 255 954 oriens[ (frames, xbs, ybs) ] = 255 955 956 self.viewer.add_image( oriens, name="CellOrientation", blending="additive", opacity=1, scale=self.viewer.layers["Segmentation"].scale ) 957 self.viewer.window._status_bar._toggle_activity_dock(False) 958 959 ################### Export to other plugins 960 961 def to_griot(self): 962 """ Export current frame to new viewer and makes it ready for Griotte plugin """ 963 try: 964 from napari_griottes import make_graph 965 except: 966 ut.show_error("Plugin napari-griottes is not installed") 967 return 968 gview = napari.Viewer() 969 tframe = ut.current_frame(self.viewer) 970 segt = self.epicure.seglayer.data[tframe] 971 touching_frame = self.touching_labels(segt) 972 gview.add_labels(touching_frame, name="TouchingCells", opacity=1) 973 gview.window.add_dock_widget(make_graph(), name="Griottes") 974 975 def touching_labels(self, labs): 976 """ Dilate labels so that they all touch """ 977 from skimage.segmentation import find_boundaries 978 from skimage.morphology import skeletonize 979 from skimage.morphology import binary_closing, binary_opening 980 if self.epicure.verbose > 0: 981 print("********** Generate touching labels image ***********") 982 983 ## skeletonize it 984 skel = skeletonize( binary_closing( find_boundaries(labs), footprint=np.ones((10,10)) ) ) 985 ext = np.zeros(labs.shape, dtype="uint8") 986 ext[labs==0] = 1 987 ext = binary_opening(ext, footprint=np.ones((2,2))) 988 newimg = ut.touching_labels(labs, expand=4) 989 newimg[ext>0] = 0 990 return newimg 991 992 def to_ncp(self): 993 """ Export current frame to new viewer and makes it ready for napari-cluster-plots plugin """ 994 try: 995 import napari_skimage_regionprops as nsr 996 except: 997 ut.show_error("Plugin napari-skimage-regionprops is not installed") 998 return 999 gview = napari.Viewer() 1000 tframe = ut.current_frame(self.viewer) 1001 segt = self.epicure.seglayer.data[tframe] 1002 moviet = self.epicure.viewer.layers["Movie"].data[tframe] 1003 lab = gview.add_labels(segt, name="Segmentation[t="+str(tframe)+"]", blending="additive") 1004 im = gview.add_image(moviet, name="Movie[t="+str(tframe)+"]", blending="additive") 1005 if self.epicure.verbose > 0: 1006 print("Measure features with napari-skimage-regionprops plugin...") 1007 nsr.regionprops_table(im.data, lab.data, size=True, intensity=True, perimeter=True, shape=True, position=True, moments=True, napari_viewer=gview) 1008 try: 1009 import napari_clusters_plotter as ncp 1010 except: 1011 ut.show_error("Plugin napari-clusters-plotter is not installed") 1012 return 1013 gview.window.add_dock_widget( ncp.ClusteringWidget(gview) ) 1014 gview.window.add_dock_widget( ncp.PlotterWidget(gview) ) 1015 1016 ################### Temporal graphs 1017 def temporal_graphs_events( self ): 1018 """ New window with temporal graph of event counts """ 1019 if self.tplots is not None: 1020 self.tplots.close() 1021 self.tplots = TemporalPlots( self.viewer, self.epicure ) 1022 evt_table = self.count_events() 1023 self.tplots.setTable( evt_table ) 1024 self.tplots.show() 1025 self.viewer.dims.events.current_step.connect(self.position_verticalline) 1026 1027 1028 def temporal_graphs(self): 1029 """ New window with temporal graph of the current table selection """ 1030 #self.temporal_viewer = napari.Viewer() 1031 self.tplots = TemporalPlots( self.viewer, self.epicure ) 1032 self.tplots.setTable(self.table) 1033 self.tplots.show() 1034 #self.plot_wid = self.viewer.window.add_dock_widget( self.tplots, name="Plots" ) 1035 self.viewer.dims.events.current_step.connect(self.position_verticalline) 1036 1037 def on_close_viewer(self): 1038 """ Temporal plots window is closed """ 1039 if self.epicure.verbose > 1: 1040 print("Closed viewer") 1041 self.viewer.dims.events.current_step.disconnect(self.position_verticalline) 1042 self.temporal_viewer = None 1043 self.tplots = None 1044 1045 def position_verticalline(self): 1046 """ Place the vertical line in the temporal graph to the current frame """ 1047 #try: 1048 # wid = self.tplots 1049 #except: 1050 # self.on_close_viewer() 1051 if self.tplots is not None: 1052 self.tplots.move_framepos(self.viewer.dims.current_step[0]) 1053 1054 ############### track features 1055 1056 def show_trackfeature_table(self): 1057 """ Show the measurement of tracks table """ 1058 self.measure_track_features() 1059 self.trackTable.set_table( self.table ) 1060 1061 def measure_track_features(self): 1062 """ Measure track features and put them to table """ 1063 if self.epicure.verbose > 0: 1064 print("Measuring track features") 1065 self.viewer.window._status_bar._toggle_activity_dock(True) 1066 start_time = time.time() 1067 1068 if self.output_mode.currentText() == "Only selected cell": 1069 track_ids = self.epicure.seglayer.selected_label 1070 else: 1071 if self.output_mode.currentText() == "All cells": 1072 track_ids = self.epicure.tracking.get_track_list() 1073 else: 1074 group = self.output_mode.currentText() 1075 track_ids = [] 1076 label_group = self.epicure.groups[group] 1077 for lab in label_group: 1078 track_ids.append(lab) 1079 1080 properties = ["label", "area", "centroid"] 1081 self.table = None 1082 1083 if type(track_ids) == np.ndarray or type(track_ids)==np.array: 1084 track_ids = track_ids.tolist() 1085 if not type(track_ids) == list: 1086 track_ids = [track_ids] 1087 1088 labgroups = self.epicure.group_of_labels() 1089 frame_group = [ labgroups[label] if label in labgroups.keys() else "Ungrouped" for label in track_ids ] 1090 for itrack, track_id in progress(enumerate(track_ids)): 1091 track_frame = self.measure_one_track( track_id ) 1092 track_frame["Group"] = frame_group[itrack] 1093 if self.table is None: 1094 self.table = pand.DataFrame([track_frame]) 1095 else: 1096 self.table = pand.concat([self.table, pand.DataFrame([track_frame])]) 1097 1098 self.table_selection = self.selection_choices.index(self.output_mode.currentText()) 1099 self.viewer.window._status_bar._toggle_activity_dock(False) 1100 if self.epicure.verbose > 0: 1101 ut.show_info("Features measured in "+"{:.3f}".format((time.time()-start_time)/60)+" min") 1102 1103 def measure_one_track( self, track_id ): 1104 """ Measure features of one track """ 1105 track_features = self.epicure.tracking.measure_track_features( track_id, self.scaled_unit.isChecked() ) 1106 return track_features 1107 1108 ############## Events functions 1109 1110 def choose_events( self ): 1111 """ Pop-up widget to choose the event types to measure/export """ 1112 self.event_classes.choose() 1113 1114 def count_events( self ): 1115 """ Count events of selected types """ 1116 evt_types = self.event_classes.get_evt_classes() 1117 if self.epicure.verbose > 2: 1118 print("Counting events of type "+str(evt_types)+" " ) 1119 1120 ## keep only events related to selected cells 1121 labels = self.get_current_labels() 1122 ## count each type of event 1123 table = np.zeros( (self.epicure.nframes,len(evt_types)), dtype="uint8" ) 1124 for itype, evt_type in enumerate( evt_types ): 1125 evts = self.epicure.inspecting.get_events_from_type( evt_type ) 1126 if len( evts ) > 0: 1127 for evt_sid in evts: 1128 pos, label = self.epicure.inspecting.get_event_infos( evt_sid ) 1129 if label in labels: 1130 table[ pos[0], itype ] += 1 1131 df = pand.DataFrame( data=table, columns=evt_types ) 1132 df["frame"] = range(len(df)) 1133 df["label"] = [0]*len(df) 1134 return df 1135 1136 def export_events( self ): 1137 """ Export events of selected types """ 1138 evt_types = self.event_classes.get_evt_classes() 1139 export_type = self.save_evt_choice.currentText() 1140 if self.epicure.verbose > 2: 1141 print("Exporting events of type "+str(evt_types)+" to "+export_type ) 1142 self.export_events_type_format( evt_types, export_type ) 1143 1144 def export_events_type_format( self, evt_types, export_type ): 1145 """ Export events of selected types in selected format """ 1146 ## keep only events related to selected cells 1147 labels = self.get_current_labels() 1148 groups = self.epicure.get_groups( labels ) 1149 if export_type == "CSV File": 1150 res = pand.DataFrame( columns=["label", "frame", "posY", "posX", "EventClass", "Group"] ) 1151 ## export each type of event in separate files 1152 for itype, evt_type in enumerate( evt_types ): 1153 evts = self.epicure.inspecting.get_events_from_type( evt_type ) 1154 if len( evts ) > 0: 1155 rois = [] 1156 for evt_sid in evts: 1157 pos, label = self.epicure.inspecting.get_event_infos( evt_sid ) 1158 ind_lab = np.where( labels==label ) 1159 if len( ind_lab[0] ) > 0: 1160 grp = groups[ int(ind_lab[0][0]) ] 1161 if export_type == "Fiji ROI": 1162 roi = self.create_point_roi( pos, itype ) 1163 rois.append( roi ) 1164 if export_type == "CSV File": 1165 new_event = pand.DataFrame( [[label, pos[0], pos[1], pos[2], evt_type, grp ]], columns=res.columns ) 1166 res = pand.concat( [res, new_event], ignore_index=True ) 1167 if export_type == "Fiji ROI": 1168 outfile = self.epicure.outname()+"_rois_"+evt_type +""+self.get_selection_name()+".zip" 1169 roifile.roiwrite(outfile, rois, mode='w') 1170 if self.epicure.verbose > 0: 1171 print( "Events "+str( evt_type )+" saved in ROI file: "+outfile ) 1172 ## dont save anything if empty, just print info to user 1173 else: 1174 if self.epicure.verbose > 0: 1175 print( "No events of type "+str(evt_type)+"" ) 1176 1177 if export_type == "CSV File": 1178 outfile = self.epicure.outname()+"_events"+self.get_selection_name()+".csv" 1179 res.to_csv( outfile, sep='\t', header=True, index=False ) 1180 if self.epicure.verbose > 0: 1181 print( "Events data "+" saved in CSV file: "+outfile ) 1182 1183 1184 def create_point_roi( self, pos, cat=0 ): 1185 """ Create a point Fiji ROI """ 1186 croi = roifile.ImagejRoi() 1187 croi.version = 227 1188 croi.roitype = roifile.ROI_TYPE(10) 1189 croi.name = str(pos[0]+1).zfill(4)+'-'+str(pos[1]).zfill(4)+"-"+str(pos[2]).zfill(4) 1190 croi.n_coordinates = 1 1191 croi.left = int(pos[2]) 1192 croi.top = int(pos[1]) 1193 croi.z_position = 1 1194 croi.t_position = pos[0]+1 1195 croi.c_position = 1 1196 croi.integer_coordinates = np.array( [[0,0]] ) 1197 croi.stroke_width=3 1198 ncolors = 3 1199 if cat%ncolors == 0: ## color type 0 1200 croi.stroke_color = b'\xff\x00\x00\xff' 1201 if cat%ncolors == 1: ## color type 1 1202 croi.stroke_color = b'\xff\x00\xff\x00' 1203 if cat%ncolors == 2: ## color type 2 1204 croi.stroke_color = b'\xff\xff\x00\x00' 1205 return croi 1206 1207 def save_tm_xml( self ): 1208 """ Save current segmentation and tracking in TrackMate XML format """ 1209 outname = os.path.join( self.epicure.outdir, self.epicure.imgname+".xml" ) 1210 save_trackmate_xml( self.epicure, outname ) 1211 if self.epicure.verbose > 0: 1212 ut.show_info("TrackMate XML saved in "+outname) 1213 1214 def save_geff( self ): 1215 """ Save current segmentation and tracking in GEFF format """ 1216 ## save the label segmentation if it's not saved 1217 labelname = os.path.join( self.epicure.outdir, self.epicure.imgname + "_labels.tif" ) 1218 ut.writeTif( self.epicure.seg, labelname, self.epicure.epi_metadata["ScaleXY"], "float32", what="Segmentation" ) 1219 ## then export the GEFF file 1220 if self.epicure.tracking.graph is None: 1221 self.epicure.tracking.graph = {} 1222 outname = os.path.join( self.epicure.outdir, self.epicure.imgname+".geff" ) 1223 save_geff( self.epicure, outname ) 1224 if self.epicure.verbose > 0: 1225 ut.show_info("GEFF file saved in "+outname)
QWidget(parent: Optional[QWidget] = None, flags: Qt.WindowType = Qt.WindowFlags())
50 def __init__(self, napari_viewer, epic): 51 """ Initialisation of the interface """ 52 super().__init__() 53 self.viewer = napari_viewer 54 self.epicure = epic 55 self.table = None 56 self.table_selection = None 57 self.seglayer = self.viewer.layers["Segmentation"] 58 self.movlayer = self.viewer.layers["Movie"] 59 self.selection_choices = ["All cells", "Only selected cell"] 60 self.output_options = ["", "Export to extern plugins", "Export segmentations", "Measure cell features", "Measure track features", "Export/Measure events", "Save as...", "Save screenshot movie", "Measure vertices"] 61 self.tplots = None 62 63 chanlist = ["Movie"] 64 if self.epicure.others is not None: 65 for chan in self.epicure.others_chanlist: 66 chanlist.append( "MovieChannel_"+str(chan) ) 67 self.cell_features = CellFeatures( chanlist ) 68 self.event_classes = EventClass( self.epicure ) 69 70 all_layout = QVBoxLayout() 71 self.scaled_unit = wid.add_check( "Measures in scaled units", False, check_func=None, descr="Scales the output measures in the given spatio-temporal units (µm, min..)" ) 72 all_layout.addWidget( self.scaled_unit ) 73 self.choose_output = wid.listbox() 74 all_layout.addWidget(self.choose_output) 75 for option in self.output_options: 76 self.choose_output.addItem(option) 77 self.choose_output.currentIndexChanged.connect(self.show_output_option) 78 79 ## Choice of active selection 80 #layout = QVBoxLayout() 81 selection_layout, self.output_mode = wid.list_line( "Apply on", descr="Choose on which cell(s) to do the action", func=None ) 82 for sel in self.selection_choices: 83 self.output_mode.addItem(sel) 84 all_layout.addLayout(selection_layout) 85 86 ## Choice of interface 87 self.export_group, export_layout = wid.group_layout( "Export to extern plugins" ) 88 griot_btn = wid.add_button( "Current frame to Griottes", self.to_griot, "Launch(in new window) Griottes plugin on current frame" ) 89 export_layout.addWidget(griot_btn) 90 ncp_btn = wid.add_button( "Current frame to Cluster-Plotter", self.to_ncp, "Launch (in new window) cluster-plotter plugin on current frame" ) 91 export_layout.addWidget(ncp_btn) 92 self.export_group.setLayout(export_layout) 93 all_layout.addWidget(self.export_group) 94 95 ## Option to export segmentation results 96 self.export_seg_group, layout = wid.group_layout(self.output_options[2]) 97 save_line, self.save_choice = wid.button_list( "Save segmentation as", self.save_segmentation, "Save the current segmentation either as ROI, label image or skeleton" ) 98 self.save_choice.addItem( "labels" ) 99 self.save_choice.addItem( "ROI" ) 100 self.save_choice.addItem( "skeleton" ) 101 layout.addLayout( save_line ) 102 103 self.export_seg_group.setLayout(layout) 104 all_layout.addWidget(self.export_seg_group) 105 106 #### Features group 107 self.feature_group, featlayout = wid.group_layout(self.output_options[3]) 108 109 self.choose_features_btn = wid.add_button( "Choose features...", self.choose_features, "Open a window to select the features to measure" ) 110 featlayout.addWidget(self.choose_features_btn) 111 112 self.feature_table = wid.add_button( "Create features table", self.show_table, "Measure the selected features and display it as a clickable table" ) 113 featlayout.addWidget(self.feature_table) 114 self.featTable = FeaturesTable(self.viewer, self.epicure) 115 featlayout.addWidget(self.featTable) 116 117 ######## Temporal option 118 self.temp_graph = wid.add_button( "Table to temporal graphs...", self.temporal_graphs, "Open a plot interface of measured features temporal evolution" ) 119 featlayout.addWidget(self.temp_graph) 120 self.temp_graph.setEnabled(False) 121 122 ######## Drawing option 123 featmap, self.show_feature_map = wid.list_line( "Draw feature map:", descr="Add a layer with the cells colored by the selected feature value", func=self.show_feature ) 124 featlayout.addLayout(featmap) 125 orienbtn = wid.add_button( "Draw cell orientation", self.draw_orientation, "Add a layer with each cell main axis orientation and length " ) 126 featlayout.addWidget( orienbtn ) 127 128 save_tab_line, self.save_format = wid.button_list( "Save features table", self.save_measure_features, "Save the current table in a .csv file" ) 129 self.save_format.addItem( "csv" ) 130 self.save_format.addItem( "xlsx" ) 131 featlayout.addLayout(save_tab_line) 132 133 ## skrub table 134 self.stat_table = wid.add_button( "Open statistiques table...", self.skrub_features, "Open interactive table with the features statistiques (skrub library)" ) 135 featlayout.addWidget(self.stat_table) 136 137 self.feature_group.setLayout(featlayout) 138 self.feature_group.hide() 139 all_layout.addWidget(self.feature_group) 140 141 ## Track features 142 self.trackfeat_group, trackfeatlayout = wid.group_layout(self.output_options[4]) 143 self.trackfeat_table = wid.add_button( "Track features table", self.show_trackfeature_table, "Measure track-related feature and show a table by track" ) 144 trackfeatlayout.addWidget(self.trackfeat_table) 145 self.trackTable = FeaturesTable(self.viewer, self.epicure) 146 trackfeatlayout.addWidget(self.trackTable) 147 self.save_table_track = wid.add_button( "Save track table", self.save_table_tracks, "Save the current table in a .csv file" ) 148 trackfeatlayout.addWidget(self.save_table_track) 149 150 self.trackfeat_group.setLayout(trackfeatlayout) 151 self.trackfeat_group.hide() 152 all_layout.addWidget(self.trackfeat_group) 153 154 ## Option to export/measure events (Fiji ROI or table), + graphs ? 155 self.handle_event_group, elayout = wid.group_layout(self.output_options[5]) 156 self.choose_events_btn = wid.add_button( "Choose events...", self.choose_events, "Open a window to select the events to export/measure" ) 157 elayout.addWidget( self.choose_events_btn ) 158 save_evt_line, self.save_evt_choice = wid.button_list( "Export events as", self.export_events, "Save the checked events as Fiji ROIs or .csv table" ) 159 self.save_evt_choice.addItem( "Fiji ROI" ) 160 self.save_evt_choice.addItem( "CSV File" ) 161 elayout.addLayout( save_evt_line ) 162 count_evt_btn = wid.add_button( "Count events", self.temporal_graphs_events, descr="Create temporal plot of number of events" ) 163 elayout.addWidget( count_evt_btn ) 164 165 self.handle_event_group.setLayout( elayout ) 166 self.handle_event_group.hide() 167 all_layout.addWidget( self.handle_event_group ) 168 169 ## Save TrackMate XML option 170 self.save_as_group, save_as_layout = wid.group_layout( "Save as..." ) 171 self.save_tm_btn = wid.add_button( "Save as TrackMate XML", self.save_tm_xml, "Save the current segmentation and the optional tracking in a TrackMate XML file" ) 172 self.save_geff_btn = wid.add_button( "Save as GEFF", self.save_geff, "Save the segmentation and tracks to GEFF" ) 173 save_as_layout.addWidget( self.save_tm_btn ) 174 save_as_layout.addWidget( self.save_geff_btn ) 175 176 self.save_as_group.setLayout( save_as_layout ) 177 self.save_as_group.hide() 178 all_layout.addWidget( self.save_as_group ) 179 180 ## Save screenshots option 181 current_frame = ut.current_frame( self.epicure.viewer ) 182 self.screenshot_group, screenshot_layout = wid.group_layout( "Save screenshot movie" ) 183 self.show_scalebar = wid.add_check_tolayout( screenshot_layout, "With the scale bar", True, check_func=None, descr="Show the scale bar in the screenshots" ) 184 sframe_line, self.sframe = wid.slider_line( "From frame", 0, self.epicure.nframes, 1, value=current_frame, show_value=True, slidefunc=None, descr="Frame from which to start saving screenshots" ) 185 eframe_line, self.eframe = wid.slider_line( "To frame", 0, self.epicure.nframes, 1, value=current_frame+1, show_value=True, slidefunc=None, descr="Frame until which to save screenshots" ) 186 screenshot_layout.addLayout( sframe_line ) 187 screenshot_layout.addLayout( eframe_line ) 188 savescreen_btn = wid.add_button( "Save current view", self.screenshot_movie, "Save the current view (with current display parameters) for frame between the two specified frames in a movie" ) 189 190 screenshot_layout.addWidget(savescreen_btn) 191 self.screenshot_group.setLayout(screenshot_layout) 192 all_layout.addWidget(self.screenshot_group) 193 self.screenshot_group.hide() 194 195 ## Measure vertex options 196 self.vertex_group, vertices_layout = wid.group_layout( "Measure vertices" ) 197 radius_line, self.vertice_radius = wid.value_line("Vertex radius", "1.25", descr="Radius of a vertex (TCJ) to consider as one point and measure intensities") 198 display_radius_line, self.vertice_display_radius = wid.value_line("Display radius", "3", descr="Radius of a vertex for DISPLAY only (size of drawing in the layer)") 199 vertices_layout.addLayout(radius_line) 200 vertices_layout.addLayout(display_radius_line) 201 self.vertices_btn = wid.add_button( "Measure", self.show_vertices_table, "Measure the vertices (connectivity, intensity)" ) 202 vertices_layout.addWidget( self.vertices_btn ) 203 self.verticesTable = FeaturesTable(self.viewer, self.epicure) 204 vertices_layout.addWidget(self.verticesTable) 205 self.save_table_vertices = wid.add_button( "Save vertices table", self.save_vertices_table, "Save the current table in a .csv file" ) 206 vertices_layout.addWidget(self.save_table_vertices) 207 208 self.vertex_group.setLayout( vertices_layout ) 209 all_layout.addWidget(self.vertex_group) 210 self.vertex_group.hide() 211 212 ## Finished 213 self.setLayout(all_layout) 214 self.show_output_option()
Initialisation of the interface
216 def get_current_settings( self ): 217 """ Returns current settings of the widget """ 218 disp = {} 219 disp["Apply on"] = self.output_mode.currentText() 220 disp["Current option"] = self.choose_output.currentText() 221 disp["Show scalebar"] = self.show_scalebar.isChecked() 222 disp = self.cell_features.get_current_settings( disp ) 223 disp = self.event_classes.get_current_settings( disp ) 224 return disp
Returns current settings of the widget
226 def apply_settings( self, settings ): 227 """ Set the current state of the widget from preferences if any """ 228 for setting, val in settings.items(): 229 if setting == "Apply on": 230 self.output_mode.setCurrentText( val ) 231 if setting == "Current option": 232 self.choose_output.setCurrentText( val ) 233 if setting == "Show scalebar": 234 self.show_scalebar.setChecked( val ) 235 236 self.cell_features.apply_settings( settings ) 237 self.event_classes.apply_settings( settings )
Set the current state of the widget from preferences if any
239 def screenshot_movie( self ): 240 """ Save screenshots of the current view """ 241 scale_visibility = self.viewer.scale_bar.visible 242 current_frame = ut.current_frame( self.epicure.viewer ) 243 self.viewer.scale_bar.visible = self.show_scalebar.isChecked() 244 start_frame = max( self.sframe.value(), 0 ) 245 end_frame = min( self.eframe.value(), self.epicure.nframes ) 246 outname = os.path.join( self.epicure.outdir, self.epicure.imgname+"_screenshots_f"+str(start_frame)+"-"+str(end_frame)+".tif" ) 247 if os.path.exists(outname): 248 os.remove(outname) 249 if start_frame > end_frame: 250 ut.show_warning("From frame > to frame, no screenshot saved") 251 return 252 for frame in range(start_frame, end_frame+1): 253 self.viewer.dims.set_point(0, frame) 254 shot = self.viewer.screenshot( canvas_only=True, flash=False ) 255 ut.appendToTif( shot, outname ) 256 self.viewer.scale_bar.visible = scale_visibility 257 self.viewer.dims.set_point(0, current_frame) 258 ut.show_info( "Screenshot movie saved in "+outname )
Save screenshots of the current view
260 def events_select( self, event, check ): 261 """ Check/Uncheck the event in event types list """ 262 if event in self.event_classes.evt_classes: 263 self.event_classes.evt_classes[ event ][0].setChecked( check ) 264 else: 265 print(event+" not found in possible event types to export")
Check/Uncheck the event in event types list
267 def show_output_option(self): 268 """ Show selected output panel """ 269 cur_option = self.choose_output.currentText() 270 self.export_group.setVisible( cur_option == "Export to extern plugins" ) 271 self.export_seg_group.setVisible( cur_option == "Export segmentations" ) 272 self.feature_group.setVisible( cur_option == "Measure cell features" ) 273 self.vertex_group.setVisible( cur_option == "Measure vertices" ) 274 self.trackfeat_group.setVisible( cur_option == "Measure track features" ) 275 self.handle_event_group.setVisible( cur_option == "Export/Measure events" ) 276 self.save_as_group.setVisible( cur_option == "Save as..." ) 277 self.screenshot_group.setVisible( cur_option == "Save screenshot movie" )
Show selected output panel
279 def get_current_labels( self ): 280 """ Get the cell labels to process according to current selection of apply on""" 281 if self.output_mode.currentText() == "Only selected cell": 282 lab = self.epicure.seglayer.selected_label 283 return [lab] 284 if self.output_mode.currentText() == "All cells": 285 return self.epicure.get_labels() 286 else: 287 group = self.output_mode.currentText() 288 label_group = self.epicure.groups[group] 289 return label_group
Get the cell labels to process according to current selection of apply on
292 def get_selection_name(self): 293 if self.output_mode.currentText() == "Only selected cell": 294 lab = self.epicure.seglayer.selected_label 295 return "_cell_"+str(lab) 296 #if self.output_mode.currentText() == "Only checked cells": 297 # return "_checked_cells" 298 if self.output_mode.currentText() == "All cells": 299 return "" 300 return "_"+self.output_mode.currentText()
302 def skrub_features( self ): 303 """ Open html table interactive and stats with skrub module """ 304 try: 305 from skrub import TableReport 306 except: 307 ut.show_error( "Needs skrub library for this option. Install it (`pip install skrub`) before" ) 308 return 309 if self.table is None: 310 ut.show_warning( "Create/update the table before" ) 311 return 312 report = TableReport( self.table ) 313 report.open()
Open html table interactive and stats with skrub module
316 def save_measure_features(self): 317 """ Save measures table to file whether it was created or not """ 318 if self.table is None or self.table_selection is None or self.selection_changed() : 319 ut.show_warning("Create/update the table before") 320 return 321 ext = self.save_format.currentText() 322 outfile = self.epicure.outname()+"_features"+self.get_selection_name()+"."+ext 323 if ext == "xlsx": 324 self.table.to_excel( outfile, sheet_name='EpiCureMeasures' ) 325 else: 326 self.table.to_csv( outfile, index=False ) 327 if self.epicure.verbose > 0: 328 ut.show_info("Measures saved in "+outfile)
Save measures table to file whether it was created or not
330 def save_table_tracks(self): 331 """ Save tracks table to file whether it was created or not """ 332 if self.table is None or self.table_selection is None or self.selection_changed() : 333 ut.show_warning("Create/update the table before") 334 return 335 outfile = self.epicure.outname()+"_trackfeatures"+self.get_selection_name()+".xlsx" 336 self.table.to_excel( outfile, sheet_name='EpiCureTrackMeasures' ) 337 if self.epicure.verbose > 0: 338 ut.show_info("Track measures saved in "+outfile)
Save tracks table to file whether it was created or not
341 def save_one_roi(self, lab): 342 """ Save the Rois of cell with label lab """ 343 keep = self.seglayer.data == lab 344 rois = [] 345 if np.sum(keep) > 0: 346 ## add 2D case 347 for iframe, frame in enumerate(keep): 348 if np.sum(frame) > 0: 349 contour = ut.get_contours(frame) 350 roi = self.create_roi(contour[0], iframe, lab) 351 rois.append(roi) 352 353 roifile.roiwrite(self.epicure.outname()+"_rois_cell_"+str(lab)+".zip", rois, mode='w')
Save the Rois of cell with label lab
355 def create_roi(self, contour, frame, label): 356 croi = roifile.ImagejRoi() 357 croi.version = 227 358 croi.roitype = roifile.ROI_TYPE(0) ## polygon 359 croi.n_coordinates = len(contour) 360 croi.position = frame + 1 361 croi.t_position = frame+1 362 coords = [] 363 cent0 = 0 364 cent1 = 0 365 for cont in contour: 366 coords.append([int(cont[1]), int(cont[0])]) 367 cent0 += cont[1] 368 cent1 += cont[0] 369 croi.integer_coordinates = np.array(coords) 370 #croi.top = int(np.min(coords[0])) 371 #croi.left = int(np.min(coords[1])) 372 croi.name = str(frame+1).zfill(4)+'-'+str(int(cent0/len(contour))).zfill(4)+"-"+str(int(cent1/len(contour))).zfill(4) 373 return croi
375 def save_segmentation( self ): 376 """ Save current segmentation in selected format """ 377 if self.output_mode.currentText() == "Only selected cell": 378 ## output only the selected cell 379 lab = self.seglayer.selected_label 380 if self.save_choice.currentText() == "ROI": 381 self.save_one_roi(lab) 382 if self.epicure.verbose > 0: 383 ut.show_info("Cell "+str(lab)+" saved to Fiji ROI") 384 return 385 else: 386 tosave = np.zeros(self.seglayer.data.shape, dtype=self.epicure.dtype) 387 if np.sum(self.seglayer.data==lab) > 0: 388 tosave[self.seglayer.data==lab] = lab 389 endname = "_"+self.save_choice.currentText()+"_"+str(lab)+".tif" 390 else: 391 ## output all cells 392 if self.output_mode.currentText() == "All cells": 393 if self.save_choice.currentText() == "ROI": 394 self.save_all_rois() 395 return 396 tosave = self.seglayer.data 397 endname = "_"+self.save_choice.currentText()+".tif" 398 else: 399 ## or output only selected group 400 group = self.output_mode.currentText() 401 label_group = self.epicure.groups[group] 402 if self.save_choice.currentText() == "ROI": 403 ncells = 0 404 for lab in label_group: 405 self.save_one_roi(lab) 406 ncells += 1 407 if self.epicure.verbose > 0: 408 ut.show_info(str(ncells)+" cells saved to Fiji ROIs") 409 return 410 tosave = np.zeros(self.seglayer.data.shape, dtype=self.epicure.dtype) 411 endname = "_"+self.save_choice.currentText()+"_"+self.output_mode.currentText()+".tif" 412 for lab in label_group: 413 tosave[self.seglayer.data==lab] = lab 414 415 ## save filled image (for label or skeleton) to file 416 outname = os.path.join( self.epicure.outdir, self.epicure.imgname+endname ) 417 if self.save_choice.currentText() == "skeleton": 418 parallel = 0 419 if self.epicure.process_parallel: 420 parallel = self.epicure.nparallel 421 tosave = ut.get_skeleton( tosave, viewer=self.viewer, verbose=self.epicure.verbose, parallel=parallel ) 422 ut.writeTif( tosave, outname, self.epicure.epi_metadata["ScaleXY"], 'uint8', what="Skeleton" ) 423 else: 424 ut.writeTif(tosave, outname, self.epicure.epi_metadata["ScaleXY"], 'float32', what="Segmentation")
Save current segmentation in selected format
426 def save_all_rois( self ): 427 """ Save all cells to ROI format """ 428 ncells = 0 429 for lab in np.unique(self.epicure.seglayer.data): 430 self.save_one_roi(lab) 431 ncells += 1 432 if self.epicure.verbose > 0: 433 ut.show_info(str(ncells)+" cells saved to Fiji ROIs")
Save all cells to ROI format
435 def choose_features( self ): 436 """ Pop-up widget to choose the features to measure """ 437 self.cell_features.choose()
Pop-up widget to choose the features to measure
439 def show_vertices_table(self): 440 """ Show the measurement of vertices table """ 441 self.measure_vertices() 442 self.verticesTable.set_table(self.table)
Show the measurement of vertices table
444 def save_vertices_table(self): 445 """ Save vertices table to file whether it was created or not """ 446 if self.table is None: 447 ut.show_warning("Create/update the table before") 448 return 449 outfile = self.epicure.outname()+"_vertices"+".xlsx" 450 self.table.to_excel( outfile, sheet_name='EpiCureVerticesMeasures' ) 451 if self.epicure.verbose > 0: 452 ut.show_info("Vertices measures saved in "+outfile)
Save vertices table to file whether it was created or not
455 def measure_vertices(self): 456 """ Get all vertices (TCJ) and measure their properties """ 457 def nb_neighbors(regionmask, labimg): 458 """ Measure the nb of neighbors (labels) around each point """ 459 #footprint = disk(radius=8) 460 #dilated = binary_dilation(regionmask, footprint) 461 labels = np.unique(labimg[regionmask]).tolist() 462 nb_nei = len(labels) 463 if 0 in labels: 464 nb_nei = nb_nei - 1 465 return nb_nei 466 467 self.table = None 468 radius = float(self.vertice_radius.text()) 469 display_radius = float(self.vertice_display_radius.text()) 470 ## difference between the measured radius and the displayed radius 471 diff_radius = display_radius - radius 472 if diff_radius < 0: 473 diff_radius = 0 474 parallel = 0 475 if self.epicure.process_parallel: 476 parallel = self.epicure.nparallel 477 ## Get the vertices: junctions of several skeleton lines 478 vertex_img = ut.get_vertices( self.epicure.seg, viewer=None, verbose=self.epicure.verbose, parallel=parallel ) 479 vertices_img = np.zeros(vertex_img.shape, dtype=np.int8) 480 ## Individualise, measure, draw 481 for ind, frame in enumerate(vertex_img): 482 props = ut.binary_properties(frame) 483 nvertex = len(props) 484 vertices = [] 485 for prop in props: 486 if prop.label > 0: 487 pt = prop.centroid 488 vertices.append(pt) 489 vert_img = ut.draw_points(vertices, vertex_img.shape[1:], radius=radius) 490 props = ut.binary_properties(vert_img) 491 if nvertex != len(props): 492 ## one or more vertices had been merged 493 vertices = [] 494 for prop in props: 495 if prop.label > 0: 496 pt = prop.centroid 497 vertices.append(pt) 498 vert_img = ut.draw_points(vertices, vertex_img.shape[1:], radius=radius) 499 #vertices_img[ind] = vert_img 500 lbl_img = label(vert_img) 501 int_measures = pand.DataFrame(ut.regionprops_table(lbl_img, self.movlayer.data[ind], properties=["label", "centroid", "intensity_mean"])) 502 ## expand to measure neighbors 503 exp_lbl = ut.touching_labels(lbl_img, expand=3) 504 measures = pand.DataFrame(ut.regionprops_table(exp_lbl, self.epicure.seg[ind], properties=["label"], extra_properties=[nb_neighbors] )) 505 ## Color the vertices by their number of neighbors 506 for lab in measures["label"]: 507 vertices_img[ind][lbl_img==lab] = int(measures.loc[measures["label"]==lab,"nb_neighbors"].iloc[0]) 508 df = pand.merge(int_measures, measures, on="label", how="inner") 509 df["Frame"] = ind 510 511 if self.table is None: 512 self.table = df 513 else: 514 self.table = pand.concat([self.table, df]) 515 516 ## Expand for display only 517 vertices_img[ind] = ut.touching_labels(vertices_img[ind], expand=diff_radius) 518 519 ## Display the vertices in a new layer 520 ut.remove_layer(self.viewer, "Vertices") # in case already present 521 self.viewer.add_labels(vertices_img, blending="additive", name="Vertices", scale=self.viewer.layers["Movie"].scale, opacity=1)
Get all vertices (TCJ) and measure their properties
523 def measure_features(self): 524 """ Measure features and put them to table """ 525 thick = self.epicure.thickness 526 527 def intensity_junction_cytoplasm(regionmask, intensity): 528 """ Measure the intensity only on the contour of regionmask """ 529 footprint = disk(radius=thick) 530 inside = binary_erosion(regionmask, footprint) 531 inside_intensity = ut.mean_nonzero(intensity*inside) 532 periph_intensity = ut.mean_nonzero(intensity*(regionmask^inside)) 533 return inside_intensity, periph_intensity 534 535 if self.epicure.verbose > 0: 536 print("Measuring features") 537 #self.viewer.window._status_bar._toggle_activity_dock(True) 538 pb = ut.start_progress( self.viewer, total=2, descr="Measuring cells in all movie" ) 539 start_time = time.time() 540 if self.output_mode.currentText() == "Only selected cell": 541 meas = np.zeros(self.epicure.seglayer.data.shape, self.epicure.dtype) 542 lab = self.epicure.seglayer.selected_label 543 meas[self.epicure.seglayer.data==lab] = lab 544 else: 545 if self.output_mode.currentText() == "All cells": 546 meas = self.epicure.seglayer.data 547 else: 548 group = self.output_mode.currentText() 549 meas = np.zeros(self.epicure.seglayer.data.shape, self.epicure.dtype) 550 label_group = self.epicure.groups[group] 551 for lab in label_group: 552 meas[self.epicure.seglayer.data==lab] = lab 553 554 properties, prop_extra, other_features, int_feat, int_extrafeat = self.cell_features.get_features() 555 do_channels = self.cell_features.get_channels() 556 extra_prop = [] 557 if "intensity_junction_cytoplasm" in int_extrafeat: 558 extra_prop = extra_prop + [intensity_junction_cytoplasm] 559 560 extra_properties = [] 561 if (do_channels is not None) and ("Movie" in do_channels): 562 properties = properties + int_feat 563 for extra in int_extrafeat: 564 if extra == "intensity_junction_cytoplasm": 565 extra_properties = extra_properties + [intensity_junction_cytoplasm] 566 567 pb.update() 568 labgroups = self.epicure.group_of_labels() 569 pb.total = self.epicure.nframes 570 chan_dict = dict() 571 if ( do_channels is not None ): 572 for chan in do_channels: 573 if chan == "Movie": 574 continue 575 chan_dict[chan] = self.viewer.layers[chan].data 576 seg = self.epicure.seg 577 mov = self.movlayer.data 578 579 def measure_one_frame_collect( img, frame ): 580 """ Measure on one frame and return a list of dicts for each label """ 581 #pb.update() 582 intimg = mov[frame] 583 frame_table = pand.DataFrame( ut.labels_table(img, intensity_image=intimg, properties=properties, extra_properties=extra_properties) ) 584 if "group" in other_features: 585 frame_table["group"] = frame_table["label"].map(labgroups).fillna("Ungrouped") 586 frame_table["frame"] = frame 587 588 # Boundary 589 if "Boundary" in other_features: 590 boundimg = seg[frame] 591 bds = ut.get_boundary_cells(boundimg) 592 frame_table["Boundary"] = frame_table["label"].isin(bds).astype(int) 593 # Border 594 if "Border" in other_features: 595 bds = ut.get_border_cells(img) 596 frame_table["Border"] = frame_table["label"].isin(bds).astype(int) 597 598 # Intensity features in other channels 599 for chan, intimg_chan in chan_dict.items(): 600 intimg_frame = intimg_chan[frame] 601 frame_tab = ut.labels_table(img, intensity_image=intimg_frame, properties=int_feat, extra_properties=extra_prop) 602 for add_prop in int_feat: 603 frame_table[add_prop+"_"+str(chan)] = frame_tab[add_prop] 604 if "intensity_junction_cytoplasm-0" in frame_tab.keys(): 605 frame_table["intensity_cytoplasm_"+str(chan)] = frame_tab["intensity_junction_cytoplasm-0"] 606 frame_table["intensity_junction_"+str(chan)] = frame_tab["intensity_junction_cytoplasm-1"] 607 608 if prop_extra != []: 609 if "shape_index" in prop_extra: 610 frame_table["shape_index"] = frame_table["perimeter"] / np.sqrt(frame_table["area"]) 611 if "roundness" in prop_extra: 612 frame_table["roundness"] = 4*frame_table["area"] /(np.pi * np.power(frame_table["axis_major_length"],2) ) 613 if "aspect_ratio" in prop_extra: 614 frame_table["aspect_ratio"] = frame_table["axis_major_length"] / frame_table["axis_minor_length"] 615 616 # Neighbor features 617 do_neighbor = "NbNeighbors" in other_features 618 get_neighbor = "Neighbors" in other_features 619 if do_neighbor or get_neighbor: 620 nimg = seg[frame] 621 graph = ut.get_neighbor_graph(nimg, distance=3) 622 all_neighbors = {label: list(graph.adj[label]) for label in graph.nodes} 623 frame_table["neighborlist"] = frame_table["label"].map(lambda l: all_neighbors.get(l, [])) 624 625 if do_neighbor: 626 frame_table["NbNeighbors"] = frame_table["neighborlist"].apply( 627 lambda x: len(x) if x else -1 628 ) 629 if get_neighbor: 630 frame_table["Neighbors"] = frame_table["neighborlist"].apply( 631 lambda x: "&".join(map(str, x)) if x else "" 632 ) 633 if do_neighbor or get_neighbor: 634 frame_table.drop(columns="neighborlist", inplace=True) 635 return pand.DataFrame( frame_table.to_dict(orient="records") ) 636 637 if self.epicure.process_parallel: 638 frame_tables = Parallel( n_jobs=self.epicure.nparallel ) ( 639 delayed( measure_one_frame_collect ) ( frame, iframe ) for iframe, frame in enumerate(meas) ) 640 else: 641 frame_tables = [ 642 measure_one_frame_collect( frame, iframe ) 643 for iframe, frame in (enumerate(meas)) 644 ] 645 self.table = pand.concat(frame_tables, ignore_index=True) 646 647 if "intensity_junction_cytoplasm-0" in self.table.columns: 648 self.table = self.table.rename(columns={"intensity_junction_cytoplasm-0": "intensity_cytoplasm", "intensity_junction_cytoplasm-1":"intensity_junction"}) 649 self.table_selection = self.selection_choices.index(self.output_mode.currentText()) 650 ut.close_progress( self.viewer, pb ) 651 #self.viewer.window._status_bar._toggle_activity_dock(False) 652 if self.epicure.verbose > 0: 653 ut.show_info("Features measured in "+"{:.3f}".format((time.time()-start_time)/60)+" min")
Measure features and put them to table
655 def measure_one_frame(self, img, properties, extra_properties, other_features, channels, int_feat, int_extrafeat, frame, labgroups, prop_extra ): 656 """ Measure on one frame """ 657 if frame is not None: 658 intimg = self.movlayer.data[frame] 659 else: 660 intimg = self.movlayer.data 661 first = "label" not in self.table.keys() 662 nrows = len(self.table["label"]) if "label" in self.table.keys() else 0 663 664 ## add the basic label measures 665 frame_table = ut.labels_table( img, intensity_image=intimg, properties=properties, extra_properties=extra_properties ) 666 ndata = len(frame_table["label"]) 667 for key, value in frame_table.items(): 668 if first: 669 self.table[key] = [] 670 self.table[key].extend(list(value)) 671 672 ## add the frame column 673 if frame is not None: 674 if first: 675 self.table["frame"] = [] 676 self.table["frame"].extend([frame]*ndata) 677 678 ## add info of the cell group 679 if "group" in other_features: 680 frame_group = [ labgroups[label] if label in labgroups.keys() else "Ungrouped" for label in frame_table["label"] ] 681 if first: 682 self.table["group"] = [] 683 self.table["group"].extend( frame_group ) 684 685 ## add the extra shape features 686 if prop_extra != []: 687 if "shape_index" in prop_extra: 688 si = frame_table["perimeter"] /np.sqrt( frame_table["area"] ) 689 if first: 690 self.table["shape_index"] = [] 691 self.table["shape_index"].extend( si ) 692 if "roundness" in prop_extra: 693 rou = 4*frame_table["area"] /(np.pi * np.power(frame_table["axis_major_length"],2) ) 694 if first: 695 self.table["roundness"] = [] 696 self.table["roundness"].extend( rou ) 697 if "aspect_ratio" in prop_extra: 698 ar = list( np.array(frame_table["axis_major_length"])/np.array(frame_table["axis_minor_length"]) ) 699 if first: 700 self.table["aspect_ratio"] = [] 701 self.table["aspect_ratio"].extend( ar ) 702 703 ### Measure intensity features in other chanels if option is on 704 if (channels is not None): 705 for chan in channels: 706 ## if it's movie, already measured in the general measure 707 if chan == "Movie": 708 continue 709 ## otherwise, do a new measure on the selected channels 710 if frame is not None: 711 intimg = self.viewer.layers[chan].data[frame] 712 else: 713 intimg = self.viewer.layers[chan].data 714 frame_tab = ut.labels_table( img, intensity_image=intimg, properties=int_feat, extra_properties=int_extrafeat ) 715 for add_prop in int_feat: 716 if first: 717 self.table[add_prop+"_"+chan] = [] 718 self.table[add_prop+"_"+chan].extend( list(frame_tab[add_prop]) ) 719 if "intensity_junction_cytoplasm-0" in frame_tab.keys(): 720 if first: 721 self.table["intensity_cytoplasm_"+chan] = [] 722 self.table["intensity_junction_"+str(chan)] = [] 723 self.table["intensity_cytoplasm_"+chan].extend( list(frame_tab["intensity_junction_cytoplasm-0"]) ) 724 self.table["intensity_junction_"+str(chan)].extend( list(frame_tab["intensity_junction_cytoplasm-1"]) ) 725 726 727 ## add features of neighbors relationship with graph 728 do_neighbor = "NbNeighbors" in other_features 729 get_neighbor = "Neighbors" in other_features 730 if do_neighbor or get_neighbor: 731 if frame is not None: 732 nimg = self.epicure.seg[frame] 733 else: 734 nimg = self.epicure.seg 735 #start_time = ut.start_time() 736 graph = ut.get_neighbor_graph( nimg, distance=3 ) 737 738 if first: 739 if do_neighbor: 740 self.table["NbNeighbors"] = [] 741 if get_neighbor: 742 self.table["Neighbors"] = [] 743 if do_neighbor: 744 self.table["NbNeighbors"].extend( [-1]*ndata ) 745 if get_neighbor: 746 self.table["Neighbors"].extend( [""]*ndata ) 747 748 for label in np.unique(frame_table["label"]): 749 if label in graph.nodes: 750 rlabel = np.where( (frame_table["label"] == label) )[0] 751 nneighbor = len(graph.adj[label]) 752 for ind in rlabel: 753 if do_neighbor: 754 self.table["NbNeighbors"][ind+nrows] = nneighbor 755 if get_neighbor: 756 self.table["Neighbors"][ind+nrows] = "" 757 sep = "" 758 for key in graph.adj[label].keys(): 759 self.table["Neighbors"][ind+nrows] += sep + str(key) 760 sep = "&" 761 #ut.show_duration( start_time, "Neighborhoods measured" ) 762 763 ## measure cells on boundary 764 if "Boundary" in other_features: 765 if frame is not None: 766 boundimg = self.epicure.seg[frame] 767 else: 768 boundimg = self.epicure.seg 769 bounds = ut.get_boundary_cells( boundimg ) 770 if first: 771 self.table["Boundary"] = [] 772 self.table["Boundary"].extend( [0]*ndata ) 773 for label in np.unique(frame_table["label"]): 774 if label in bounds: 775 rlabel = np.where( (frame_table["label"] == label) )[0] 776 for ind in rlabel: 777 self.table["Boundary"][ind+nrows] = 1 778 779 ## measure cells on border 780 if "Border" in other_features: 781 bounds = ut.get_border_cells( img ) 782 if first: 783 self.table["Border"] = [] 784 self.table["Border"].extend( [0]*ndata ) 785 for label in bounds: 786 rlabel = np.where( (frame_table["label"] == label) )[0] 787 for ind in rlabel: 788 self.table["Border"][ind+nrows] = 1
Measure on one frame
796 def update_selection_list(self): 797 """ Update the possible selection from group cell list """ 798 self.selection_choices = ["Only selected cell", "All cells"] 799 for group in self.epicure.groups.keys(): 800 self.selection_choices.append(group) 801 self.output_mode.clear() 802 for sel in self.selection_choices: 803 self.output_mode.addItem(sel)
Update the possible selection from group cell list
805 def show_table(self): 806 """ Show the measurement table """ 807 #disable automatic update (slow) 808 #if self.table is None: 809 ## create the table and connect action to update it automatically 810 #self.output_mode.currentIndexChanged.connect(self.show_table) 811 #self.measure_other_chanels_cbox.stateChanged.connect(self.show_table) 812 #self.feature_graph_cbox.stateChanged.connect(self.show_table) 813 #self.feature_intensity_cbox.stateChanged.connect(self.show_table) 814 #self.feature_shape_cbox.stateChanged.connect(self.show_table) 815 816 ut.set_active_layer( self.viewer, "Segmentation" ) 817 self.show_feature_map.clear() 818 self.show_feature_map.addItem("") 819 laynames = [lay.name for lay in self.viewer.layers] 820 for lay in laynames: 821 if lay.startswith("Map_"): 822 ut.remove_layer(self.viewer, lay) 823 self.measure_features() 824 featlist = self.table.keys() 825 ## Scaling the features 826 if self.scaled_unit.isChecked(): 827 for feat in featlist: 828 feat_scale, scaled = self.scale_feature( feat, self.table[feat] ) 829 if feat_scale is not None: 830 if (feat_scale[0:4] != "Time") and (feat_scale[0:9] != "centroid-"): 831 del self.table[feat] 832 self.table[feat_scale] = scaled 833 featlist = self.table.keys() 834 ## Adding the list to the feature maps 835 for feat in featlist: 836 self.show_feature_map.addItem(feat) 837 self.featTable.set_table(self.table) 838 self.temp_graph.setEnabled(True) 839 if self.tplots is not None: 840 self.tplots.update_table(self.table)
Show the measurement table
842 def scale_feature( self, feat, featVals ): 843 """ Scale if necessary the feature values """ 844 dist_feats = ["centroid-0", "centroid-1", "perimeter", "axis_major_length", "axis_minor_length", "feret_diameter_max", "equivalent_diameter_area" ] 845 if feat in dist_feats: 846 return feat+"_"+self.epicure.epi_metadata["UnitXY"], np.array(featVals)*self.epicure.epi_metadata["ScaleXY"] 847 area_feats = ["area", "area_convex"] 848 if feat in area_feats: 849 return feat+"_"+self.epicure.epi_metadata["UnitXY"]+"²", np.array(featVals)*self.epicure.epi_metadata["ScaleXY"] * self.epicure.epi_metadata["ScaleXY"] 850 if feat == "frame": 851 return "Time_"+self.epicure.epi_metadata["UnitT"], np.array(featVals)*self.epicure.epi_metadata["ScaleT"] 852 return None, None
Scale if necessary the feature values
855 def show_feature(self): 856 """ Add the image map of the selected feature """ 857 feat = self.show_feature_map.currentText() 858 if (feat is not None) and (feat != ""): 859 if feat in self.table.keys(): 860 values = list(self.table[feat]) 861 if feat == "group": 862 for i, val in enumerate(values): 863 if (val is None) or (val == 'None'): 864 values[i] = 0 865 else: 866 values[i] = list(self.epicure.groups.keys()).index(val) + 1 867 labels = list(self.table["label"]) 868 frames = None 869 if "frame" in self.table: 870 frames = list(self.table["frame"]) 871 self.draw_map(labels, values, frames, feat)
Add the image map of the selected feature
873 def draw_map(self, labels, values, frames, featname): 874 """ Add image layer of values by label """ 875 ## special feature: orientation, draw the axis instead 876 self.viewer.window._status_bar._toggle_activity_dock(True) 877 labels = np.array(labels) 878 values = np.array(values) 879 frames = np.array(frames) 880 def map_frame( iframe, segframe ): 881 """ Draw one frame of the map """ 882 mask = np.where(frames==iframe)[0] 883 labs = labels[mask] 884 vals = values[mask] 885 mapping = np.zeros(segframe.max()+1) 886 mapping[:] = np.nan 887 mapping[labs] = vals 888 return mapping[segframe] 889 890 if frames is not None: 891 ## Plotting a movie 892 if self.epicure.process_parallel: 893 mapfeat = Parallel( n_jobs=self.epicure.nparallel) ( 894 delayed ( map_frame )(iframe, frame ) for iframe, frame in enumerate(self.seglayer.data) 895 ) 896 mapfeat = np.array(mapfeat) 897 else: 898 mapfeat = np.empty(self.epicure.seg.shape, dtype="float16") 899 mapfeat[:] = np.nan 900 for iframe in np.unique(frames): 901 segdata = self.seglayer.data[iframe] 902 mapfeat[iframe] = map_frame( iframe, segdata ) 903 else: 904 mapfeat = np.empty(self.epicure.seg.shape, dtype="float16") 905 mapfeat[:] = np.nan 906 for lab, val in progress(zip(labels, values)): 907 cell = self.seglayer.data==lab 908 mapfeat[cell] = val 909 ut.remove_layer(self.viewer, "Map_"+featname) 910 self.viewer.add_image(mapfeat, name="Map_"+featname, scale=self.viewer.layers["Segmentation"].scale ) 911 self.viewer.window._status_bar._toggle_activity_dock(False)
Add image layer of values by label
913 def draw_orientation( self ): 914 """ Display the cells orientation axis in a new layer """ 915 ## check that necessary features are measured 916 ut.remove_layer( self.viewer, "CellOrientation" ) 917 feats = ["centroid-0", "centroid-1", "orientation"] 918 if self.table is None: 919 print("Features centroid and orientation necessary to draw orientation, but are not measured yet") 920 return 921 for feat in feats: 922 if feat not in self.table.keys(): 923 print("Feature "+feat+" necessary to draw orientation, but was not measured") 924 return 925 ## ok, can work now 926 self.viewer.window._status_bar._toggle_activity_dock(True) 927 928 ## get the coordinates of the axis lines by getting the cell centroid, main orientation 929 xs = np.array( self.table["centroid-0"] ) 930 ys = np.array( self.table["centroid-1"] ) 931 angles = np.array( self.table["orientation"] ) 932 lens = np.array( [10]*len(angles) ) 933 oriens = np.zeros( (self.epicure.seg.shape), dtype="uint8" ) 934 935 ## draw axis length depending on the eccentricity 936 if "eccentricity" in self.table.keys(): 937 lens = np.array(self.table["eccentricity"]*16) 938 939 if "frame" in self.table: 940 frames = np.array( self.table["frame"] ).astype(int) 941 else: 942 frames = np.array( [0]*len(angles) ) 943 944 ## draw the lines in between the two extreme points (using Shape layer is too slow on display for big movies) 945 npts = 30 946 xmax = oriens.shape[1]-1 947 ymax = oriens.shape[2]-1 948 for i in range(npts): 949 xas = np.clip(xs - lens/2 * np.cos( angles ) * i/float(npts), 0, xmax).astype(int) 950 xbs = np.clip(xs + lens/2 * np.cos( angles ) * i/float(npts), 0, xmax).astype(int) 951 yas = np.clip(ys - lens/2 * np.sin( angles ) * i/float(npts), 0, ymax).astype(int) 952 ybs = np.clip(ys + lens/2 * np.sin( angles ) * i/float(npts), 0, ymax).astype(int) 953 oriens[ (frames, xas, yas) ] = 255 954 oriens[ (frames, xbs, ybs) ] = 255 955 956 self.viewer.add_image( oriens, name="CellOrientation", blending="additive", opacity=1, scale=self.viewer.layers["Segmentation"].scale ) 957 self.viewer.window._status_bar._toggle_activity_dock(False)
Display the cells orientation axis in a new layer
961 def to_griot(self): 962 """ Export current frame to new viewer and makes it ready for Griotte plugin """ 963 try: 964 from napari_griottes import make_graph 965 except: 966 ut.show_error("Plugin napari-griottes is not installed") 967 return 968 gview = napari.Viewer() 969 tframe = ut.current_frame(self.viewer) 970 segt = self.epicure.seglayer.data[tframe] 971 touching_frame = self.touching_labels(segt) 972 gview.add_labels(touching_frame, name="TouchingCells", opacity=1) 973 gview.window.add_dock_widget(make_graph(), name="Griottes")
Export current frame to new viewer and makes it ready for Griotte plugin
975 def touching_labels(self, labs): 976 """ Dilate labels so that they all touch """ 977 from skimage.segmentation import find_boundaries 978 from skimage.morphology import skeletonize 979 from skimage.morphology import binary_closing, binary_opening 980 if self.epicure.verbose > 0: 981 print("********** Generate touching labels image ***********") 982 983 ## skeletonize it 984 skel = skeletonize( binary_closing( find_boundaries(labs), footprint=np.ones((10,10)) ) ) 985 ext = np.zeros(labs.shape, dtype="uint8") 986 ext[labs==0] = 1 987 ext = binary_opening(ext, footprint=np.ones((2,2))) 988 newimg = ut.touching_labels(labs, expand=4) 989 newimg[ext>0] = 0 990 return newimg
Dilate labels so that they all touch
992 def to_ncp(self): 993 """ Export current frame to new viewer and makes it ready for napari-cluster-plots plugin """ 994 try: 995 import napari_skimage_regionprops as nsr 996 except: 997 ut.show_error("Plugin napari-skimage-regionprops is not installed") 998 return 999 gview = napari.Viewer() 1000 tframe = ut.current_frame(self.viewer) 1001 segt = self.epicure.seglayer.data[tframe] 1002 moviet = self.epicure.viewer.layers["Movie"].data[tframe] 1003 lab = gview.add_labels(segt, name="Segmentation[t="+str(tframe)+"]", blending="additive") 1004 im = gview.add_image(moviet, name="Movie[t="+str(tframe)+"]", blending="additive") 1005 if self.epicure.verbose > 0: 1006 print("Measure features with napari-skimage-regionprops plugin...") 1007 nsr.regionprops_table(im.data, lab.data, size=True, intensity=True, perimeter=True, shape=True, position=True, moments=True, napari_viewer=gview) 1008 try: 1009 import napari_clusters_plotter as ncp 1010 except: 1011 ut.show_error("Plugin napari-clusters-plotter is not installed") 1012 return 1013 gview.window.add_dock_widget( ncp.ClusteringWidget(gview) ) 1014 gview.window.add_dock_widget( ncp.PlotterWidget(gview) )
Export current frame to new viewer and makes it ready for napari-cluster-plots plugin
1017 def temporal_graphs_events( self ): 1018 """ New window with temporal graph of event counts """ 1019 if self.tplots is not None: 1020 self.tplots.close() 1021 self.tplots = TemporalPlots( self.viewer, self.epicure ) 1022 evt_table = self.count_events() 1023 self.tplots.setTable( evt_table ) 1024 self.tplots.show() 1025 self.viewer.dims.events.current_step.connect(self.position_verticalline)
New window with temporal graph of event counts
1028 def temporal_graphs(self): 1029 """ New window with temporal graph of the current table selection """ 1030 #self.temporal_viewer = napari.Viewer() 1031 self.tplots = TemporalPlots( self.viewer, self.epicure ) 1032 self.tplots.setTable(self.table) 1033 self.tplots.show() 1034 #self.plot_wid = self.viewer.window.add_dock_widget( self.tplots, name="Plots" ) 1035 self.viewer.dims.events.current_step.connect(self.position_verticalline)
New window with temporal graph of the current table selection
1037 def on_close_viewer(self): 1038 """ Temporal plots window is closed """ 1039 if self.epicure.verbose > 1: 1040 print("Closed viewer") 1041 self.viewer.dims.events.current_step.disconnect(self.position_verticalline) 1042 self.temporal_viewer = None 1043 self.tplots = None
Temporal plots window is closed
1045 def position_verticalline(self): 1046 """ Place the vertical line in the temporal graph to the current frame """ 1047 #try: 1048 # wid = self.tplots 1049 #except: 1050 # self.on_close_viewer() 1051 if self.tplots is not None: 1052 self.tplots.move_framepos(self.viewer.dims.current_step[0])
Place the vertical line in the temporal graph to the current frame
1056 def show_trackfeature_table(self): 1057 """ Show the measurement of tracks table """ 1058 self.measure_track_features() 1059 self.trackTable.set_table( self.table )
Show the measurement of tracks table
1061 def measure_track_features(self): 1062 """ Measure track features and put them to table """ 1063 if self.epicure.verbose > 0: 1064 print("Measuring track features") 1065 self.viewer.window._status_bar._toggle_activity_dock(True) 1066 start_time = time.time() 1067 1068 if self.output_mode.currentText() == "Only selected cell": 1069 track_ids = self.epicure.seglayer.selected_label 1070 else: 1071 if self.output_mode.currentText() == "All cells": 1072 track_ids = self.epicure.tracking.get_track_list() 1073 else: 1074 group = self.output_mode.currentText() 1075 track_ids = [] 1076 label_group = self.epicure.groups[group] 1077 for lab in label_group: 1078 track_ids.append(lab) 1079 1080 properties = ["label", "area", "centroid"] 1081 self.table = None 1082 1083 if type(track_ids) == np.ndarray or type(track_ids)==np.array: 1084 track_ids = track_ids.tolist() 1085 if not type(track_ids) == list: 1086 track_ids = [track_ids] 1087 1088 labgroups = self.epicure.group_of_labels() 1089 frame_group = [ labgroups[label] if label in labgroups.keys() else "Ungrouped" for label in track_ids ] 1090 for itrack, track_id in progress(enumerate(track_ids)): 1091 track_frame = self.measure_one_track( track_id ) 1092 track_frame["Group"] = frame_group[itrack] 1093 if self.table is None: 1094 self.table = pand.DataFrame([track_frame]) 1095 else: 1096 self.table = pand.concat([self.table, pand.DataFrame([track_frame])]) 1097 1098 self.table_selection = self.selection_choices.index(self.output_mode.currentText()) 1099 self.viewer.window._status_bar._toggle_activity_dock(False) 1100 if self.epicure.verbose > 0: 1101 ut.show_info("Features measured in "+"{:.3f}".format((time.time()-start_time)/60)+" min")
Measure track features and put them to table
1103 def measure_one_track( self, track_id ): 1104 """ Measure features of one track """ 1105 track_features = self.epicure.tracking.measure_track_features( track_id, self.scaled_unit.isChecked() ) 1106 return track_features
Measure features of one track
1110 def choose_events( self ): 1111 """ Pop-up widget to choose the event types to measure/export """ 1112 self.event_classes.choose()
Pop-up widget to choose the event types to measure/export
1114 def count_events( self ): 1115 """ Count events of selected types """ 1116 evt_types = self.event_classes.get_evt_classes() 1117 if self.epicure.verbose > 2: 1118 print("Counting events of type "+str(evt_types)+" " ) 1119 1120 ## keep only events related to selected cells 1121 labels = self.get_current_labels() 1122 ## count each type of event 1123 table = np.zeros( (self.epicure.nframes,len(evt_types)), dtype="uint8" ) 1124 for itype, evt_type in enumerate( evt_types ): 1125 evts = self.epicure.inspecting.get_events_from_type( evt_type ) 1126 if len( evts ) > 0: 1127 for evt_sid in evts: 1128 pos, label = self.epicure.inspecting.get_event_infos( evt_sid ) 1129 if label in labels: 1130 table[ pos[0], itype ] += 1 1131 df = pand.DataFrame( data=table, columns=evt_types ) 1132 df["frame"] = range(len(df)) 1133 df["label"] = [0]*len(df) 1134 return df
Count events of selected types
1136 def export_events( self ): 1137 """ Export events of selected types """ 1138 evt_types = self.event_classes.get_evt_classes() 1139 export_type = self.save_evt_choice.currentText() 1140 if self.epicure.verbose > 2: 1141 print("Exporting events of type "+str(evt_types)+" to "+export_type ) 1142 self.export_events_type_format( evt_types, export_type )
Export events of selected types
1144 def export_events_type_format( self, evt_types, export_type ): 1145 """ Export events of selected types in selected format """ 1146 ## keep only events related to selected cells 1147 labels = self.get_current_labels() 1148 groups = self.epicure.get_groups( labels ) 1149 if export_type == "CSV File": 1150 res = pand.DataFrame( columns=["label", "frame", "posY", "posX", "EventClass", "Group"] ) 1151 ## export each type of event in separate files 1152 for itype, evt_type in enumerate( evt_types ): 1153 evts = self.epicure.inspecting.get_events_from_type( evt_type ) 1154 if len( evts ) > 0: 1155 rois = [] 1156 for evt_sid in evts: 1157 pos, label = self.epicure.inspecting.get_event_infos( evt_sid ) 1158 ind_lab = np.where( labels==label ) 1159 if len( ind_lab[0] ) > 0: 1160 grp = groups[ int(ind_lab[0][0]) ] 1161 if export_type == "Fiji ROI": 1162 roi = self.create_point_roi( pos, itype ) 1163 rois.append( roi ) 1164 if export_type == "CSV File": 1165 new_event = pand.DataFrame( [[label, pos[0], pos[1], pos[2], evt_type, grp ]], columns=res.columns ) 1166 res = pand.concat( [res, new_event], ignore_index=True ) 1167 if export_type == "Fiji ROI": 1168 outfile = self.epicure.outname()+"_rois_"+evt_type +""+self.get_selection_name()+".zip" 1169 roifile.roiwrite(outfile, rois, mode='w') 1170 if self.epicure.verbose > 0: 1171 print( "Events "+str( evt_type )+" saved in ROI file: "+outfile ) 1172 ## dont save anything if empty, just print info to user 1173 else: 1174 if self.epicure.verbose > 0: 1175 print( "No events of type "+str(evt_type)+"" ) 1176 1177 if export_type == "CSV File": 1178 outfile = self.epicure.outname()+"_events"+self.get_selection_name()+".csv" 1179 res.to_csv( outfile, sep='\t', header=True, index=False ) 1180 if self.epicure.verbose > 0: 1181 print( "Events data "+" saved in CSV file: "+outfile )
Export events of selected types in selected format
1184 def create_point_roi( self, pos, cat=0 ): 1185 """ Create a point Fiji ROI """ 1186 croi = roifile.ImagejRoi() 1187 croi.version = 227 1188 croi.roitype = roifile.ROI_TYPE(10) 1189 croi.name = str(pos[0]+1).zfill(4)+'-'+str(pos[1]).zfill(4)+"-"+str(pos[2]).zfill(4) 1190 croi.n_coordinates = 1 1191 croi.left = int(pos[2]) 1192 croi.top = int(pos[1]) 1193 croi.z_position = 1 1194 croi.t_position = pos[0]+1 1195 croi.c_position = 1 1196 croi.integer_coordinates = np.array( [[0,0]] ) 1197 croi.stroke_width=3 1198 ncolors = 3 1199 if cat%ncolors == 0: ## color type 0 1200 croi.stroke_color = b'\xff\x00\x00\xff' 1201 if cat%ncolors == 1: ## color type 1 1202 croi.stroke_color = b'\xff\x00\xff\x00' 1203 if cat%ncolors == 2: ## color type 2 1204 croi.stroke_color = b'\xff\xff\x00\x00' 1205 return croi
Create a point Fiji ROI
1207 def save_tm_xml( self ): 1208 """ Save current segmentation and tracking in TrackMate XML format """ 1209 outname = os.path.join( self.epicure.outdir, self.epicure.imgname+".xml" ) 1210 save_trackmate_xml( self.epicure, outname ) 1211 if self.epicure.verbose > 0: 1212 ut.show_info("TrackMate XML saved in "+outname)
Save current segmentation and tracking in TrackMate XML format
1214 def save_geff( self ): 1215 """ Save current segmentation and tracking in GEFF format """ 1216 ## save the label segmentation if it's not saved 1217 labelname = os.path.join( self.epicure.outdir, self.epicure.imgname + "_labels.tif" ) 1218 ut.writeTif( self.epicure.seg, labelname, self.epicure.epi_metadata["ScaleXY"], "float32", what="Segmentation" ) 1219 ## then export the GEFF file 1220 if self.epicure.tracking.graph is None: 1221 self.epicure.tracking.graph = {} 1222 outname = os.path.join( self.epicure.outdir, self.epicure.imgname+".geff" ) 1223 save_geff( self.epicure, outname ) 1224 if self.epicure.verbose > 0: 1225 ut.show_info("GEFF file saved in "+outname)
Save current segmentation and tracking in GEFF format
1228class CellFeatures(QWidget): 1229 """ Choice of features to measure """ 1230 def __init__(self, chanlist): 1231 super().__init__() 1232 layout = QVBoxLayout() 1233 1234 self.required = ["label"] 1235 self.features = {} 1236 self.chan_list = None 1237 1238 other_list = ["group", "NbNeighbors", "Neighbors", "Boundary", "Border"] 1239 feat_layout = self.add_feature_group( other_list, "other" ) 1240 layout.addLayout( feat_layout ) 1241 sel_all_b = wid.add_button( "Select spatial features", lambda: self.select_all("other"), "Select all spatial features" ) 1242 desel_all_b = wid.add_button( "Deselect spatial features", lambda: self.deselect_all("other"), "Deselect all spatial features" ) 1243 sel_line_b = wid.hlayout() 1244 sel_line_b.addWidget( sel_all_b ) 1245 sel_line_b.addWidget( desel_all_b ) 1246 layout.addLayout( sel_line_b ) 1247 1248 1249 ## Add shape features 1250 shape_list = ["centroid", "area", "area_convex", "axis_major_length", "axis_minor_length", "feret_diameter_max", "equivalent_diameter_area", "eccentricity", "orientation", "perimeter", "solidity"] 1251 other_shape_list = ["shape_index", "roundness", "aspect_ratio"] 1252 feat_layout = self.add_feature_group( shape_list, "prop" ) 1253 feat_extra_layout = self.add_feature_group( other_shape_list, "prop_extra" ) 1254 layout.addLayout( feat_layout ) 1255 layout.addLayout( feat_extra_layout ) 1256 sel_all = wid.add_button( "Select morphology features", lambda: self.select_all("props"), "Select all morphology features" ) 1257 desel_all = wid.add_button( "Deselect morphology features", lambda: self.deselect_all("props"), "Deselect all morphology features" ) 1258 sel_line = wid.hlayout() 1259 sel_line.addWidget( sel_all ) 1260 sel_line.addWidget( desel_all ) 1261 layout.addLayout( sel_line ) 1262 1263 int_lab = wid.label_line( "Intensity features:") 1264 layout.addWidget( int_lab ) 1265 intensity_list = ["intensity_mean", "intensity_min", "intensity_max"] 1266 extra_list = ["intensity_junction_cytoplasm"] 1267 feat_layout = self.add_feature_group( intensity_list, "intensity_prop" ) 1268 layout.addLayout( feat_layout ) 1269 feat_layout = self.add_feature_group( extra_list, "intensity_extra" ) 1270 layout.addLayout( feat_layout ) 1271 if len(chanlist) > 1: 1272 chan_lab = wid.label_line( "Measure intensity in channels:" ) 1273 layout.addWidget( chan_lab ) 1274 self.chan_list = QListWidget() 1275 self.chan_list.addItems( chanlist ) 1276 self.chan_list.setSelectionMode(aiv.MultiSelection) 1277 self.chan_list.item(0).setSelected(True) 1278 layout.addWidget( self.chan_list ) 1279 1280 sel_all_int = wid.add_button( "Select intensity features", lambda: self.select_all("intensity"), "Select all spatial features" ) 1281 desel_all_int = wid.add_button( "Deselect intensity features", lambda: self.deselect_all("intensity"), "Deselect all spatial features" ) 1282 sel_line_int = wid.hlayout() 1283 sel_line_int.addWidget( sel_all_int ) 1284 sel_line_int.addWidget( desel_all_int ) 1285 layout.addLayout( sel_line_int ) 1286 1287 bye = wid.add_button( "Ok", self.close, "Close the window" ) 1288 layout.addWidget( bye ) 1289 self.setLayout( layout ) 1290 1291 def select_all( self, feat ): 1292 """ Select all features of type feat """ 1293 if feat == "intensity": 1294 self.select_all( "intensity_prop" ) 1295 self.select_all( "intensity_extra" ) 1296 return 1297 if feat == "props": 1298 self.select_all( "prop" ) 1299 self.select_all( "prop_extra" ) 1300 return 1301 for featy, feat_val in self.features.items(): 1302 if feat_val[1] == feat: 1303 feat_val[0].setChecked( True ) 1304 1305 def deselect_all( self, feat ): 1306 """ Deselect all features of type feat """ 1307 if feat == "intensity": 1308 self.deselect_all( "intensity_prop" ) 1309 self.deselect_all( "intensity_extra" ) 1310 return 1311 if feat == "props": 1312 self.deselect_all( "prop" ) 1313 self.deselect_all( "prop_extra" ) 1314 return 1315 for featy, feat_val in self.features.items(): 1316 if feat_val[1] == feat: 1317 feat_val[0].setChecked( False ) 1318 1319 1320 def add_feature_group( self, feat_list, feat_type ): 1321 """ Add features to the GUI """ 1322 layout = QVBoxLayout() 1323 ncols = 3 1324 for i, feat in enumerate(feat_list): 1325 if i%ncols == 0: 1326 line = QHBoxLayout() 1327 feature_check = wid.add_check( ""+feat, True, None, descr="" ) 1328 line.addWidget(feature_check) 1329 self.features[ feat ] = [feature_check, feat_type] 1330 if i%ncols == (ncols-1): 1331 layout.addLayout( line ) 1332 line = None 1333 if line is not None: 1334 layout.addLayout( line ) 1335 return layout 1336 1337 1338 def close( self ): 1339 """ Close the pop-up window """ 1340 self.hide() 1341 1342 def choose( self ): 1343 """ Show the interface to select the choices """ 1344 self.show() 1345 1346 def get_current_settings( self, setting ): 1347 """ Get current settings of check or not of features """ 1348 for feat, feat_cbox in self.features.items(): 1349 setting[feat] = feat_cbox[0].isChecked() 1350 return setting 1351 1352 def apply_settings( self, settings ): 1353 """ Set the checkboxes from preferenced settings """ 1354 for feat, checked in settings.items(): 1355 if feat in self.features.keys(): 1356 self.features[feat][0].setChecked( checked ) 1357 1358 def get_features( self ): 1359 """ Returns the list of features to measure """ 1360 feats = self.required 1361 feats_extra = [] 1362 int_extra_feats = [] 1363 int_feats = [] 1364 other_feats = [] 1365 self.do_intensity = False 1366 for feat, feat_cbox in self.features.items(): 1367 if feat_cbox[0].isChecked(): 1368 if feat_cbox[1] == "prop": 1369 feats.append( feat ) 1370 if feat_cbox[1] == "prop_extra": 1371 feats_extra.append( feat ) 1372 if feat == "shape_index": 1373 if "perimeter" not in feats: 1374 feats.append("perimeter") 1375 if "area" not in feats: 1376 feats.append("area") 1377 if feat == "roundness": 1378 if "area" not in feats: 1379 feats.append("area") 1380 if "axis_major_length" not in feats: 1381 feats.append("axis_major_length") 1382 if feat == "aspect_ratio": 1383 if "axis_major_length" not in feats: 1384 feats.append("axis_major_length") 1385 if "axis_minor_length" not in feats: 1386 feats.append("axis_minor_length") 1387 if feat_cbox[1] == "other": 1388 other_feats.append( feat ) 1389 if feat_cbox[1] == "intensity_prop": 1390 int_feats.append( feat ) 1391 self.do_intensity = True 1392 if feat_cbox[1] == "intensity_extra": 1393 int_extra_feats.append( feat ) 1394 self.do_intensity = True 1395 return feats, feats_extra, other_feats, int_feats, int_extra_feats 1396 1397 def get_channels( self ): 1398 """ Returns the list of channels to measure """ 1399 if self.do_intensity: 1400 if self.chan_list is not None: 1401 wid_channels = self.chan_list.selectedItems() 1402 channels = [] 1403 for chan in wid_channels: 1404 channels.append( chan.text() ) 1405 else: 1406 channels = ["Movie"] 1407 return channels 1408 return None
Choice of features to measure
1230 def __init__(self, chanlist): 1231 super().__init__() 1232 layout = QVBoxLayout() 1233 1234 self.required = ["label"] 1235 self.features = {} 1236 self.chan_list = None 1237 1238 other_list = ["group", "NbNeighbors", "Neighbors", "Boundary", "Border"] 1239 feat_layout = self.add_feature_group( other_list, "other" ) 1240 layout.addLayout( feat_layout ) 1241 sel_all_b = wid.add_button( "Select spatial features", lambda: self.select_all("other"), "Select all spatial features" ) 1242 desel_all_b = wid.add_button( "Deselect spatial features", lambda: self.deselect_all("other"), "Deselect all spatial features" ) 1243 sel_line_b = wid.hlayout() 1244 sel_line_b.addWidget( sel_all_b ) 1245 sel_line_b.addWidget( desel_all_b ) 1246 layout.addLayout( sel_line_b ) 1247 1248 1249 ## Add shape features 1250 shape_list = ["centroid", "area", "area_convex", "axis_major_length", "axis_minor_length", "feret_diameter_max", "equivalent_diameter_area", "eccentricity", "orientation", "perimeter", "solidity"] 1251 other_shape_list = ["shape_index", "roundness", "aspect_ratio"] 1252 feat_layout = self.add_feature_group( shape_list, "prop" ) 1253 feat_extra_layout = self.add_feature_group( other_shape_list, "prop_extra" ) 1254 layout.addLayout( feat_layout ) 1255 layout.addLayout( feat_extra_layout ) 1256 sel_all = wid.add_button( "Select morphology features", lambda: self.select_all("props"), "Select all morphology features" ) 1257 desel_all = wid.add_button( "Deselect morphology features", lambda: self.deselect_all("props"), "Deselect all morphology features" ) 1258 sel_line = wid.hlayout() 1259 sel_line.addWidget( sel_all ) 1260 sel_line.addWidget( desel_all ) 1261 layout.addLayout( sel_line ) 1262 1263 int_lab = wid.label_line( "Intensity features:") 1264 layout.addWidget( int_lab ) 1265 intensity_list = ["intensity_mean", "intensity_min", "intensity_max"] 1266 extra_list = ["intensity_junction_cytoplasm"] 1267 feat_layout = self.add_feature_group( intensity_list, "intensity_prop" ) 1268 layout.addLayout( feat_layout ) 1269 feat_layout = self.add_feature_group( extra_list, "intensity_extra" ) 1270 layout.addLayout( feat_layout ) 1271 if len(chanlist) > 1: 1272 chan_lab = wid.label_line( "Measure intensity in channels:" ) 1273 layout.addWidget( chan_lab ) 1274 self.chan_list = QListWidget() 1275 self.chan_list.addItems( chanlist ) 1276 self.chan_list.setSelectionMode(aiv.MultiSelection) 1277 self.chan_list.item(0).setSelected(True) 1278 layout.addWidget( self.chan_list ) 1279 1280 sel_all_int = wid.add_button( "Select intensity features", lambda: self.select_all("intensity"), "Select all spatial features" ) 1281 desel_all_int = wid.add_button( "Deselect intensity features", lambda: self.deselect_all("intensity"), "Deselect all spatial features" ) 1282 sel_line_int = wid.hlayout() 1283 sel_line_int.addWidget( sel_all_int ) 1284 sel_line_int.addWidget( desel_all_int ) 1285 layout.addLayout( sel_line_int ) 1286 1287 bye = wid.add_button( "Ok", self.close, "Close the window" ) 1288 layout.addWidget( bye ) 1289 self.setLayout( layout )
1291 def select_all( self, feat ): 1292 """ Select all features of type feat """ 1293 if feat == "intensity": 1294 self.select_all( "intensity_prop" ) 1295 self.select_all( "intensity_extra" ) 1296 return 1297 if feat == "props": 1298 self.select_all( "prop" ) 1299 self.select_all( "prop_extra" ) 1300 return 1301 for featy, feat_val in self.features.items(): 1302 if feat_val[1] == feat: 1303 feat_val[0].setChecked( True )
Select all features of type feat
1305 def deselect_all( self, feat ): 1306 """ Deselect all features of type feat """ 1307 if feat == "intensity": 1308 self.deselect_all( "intensity_prop" ) 1309 self.deselect_all( "intensity_extra" ) 1310 return 1311 if feat == "props": 1312 self.deselect_all( "prop" ) 1313 self.deselect_all( "prop_extra" ) 1314 return 1315 for featy, feat_val in self.features.items(): 1316 if feat_val[1] == feat: 1317 feat_val[0].setChecked( False )
Deselect all features of type feat
1320 def add_feature_group( self, feat_list, feat_type ): 1321 """ Add features to the GUI """ 1322 layout = QVBoxLayout() 1323 ncols = 3 1324 for i, feat in enumerate(feat_list): 1325 if i%ncols == 0: 1326 line = QHBoxLayout() 1327 feature_check = wid.add_check( ""+feat, True, None, descr="" ) 1328 line.addWidget(feature_check) 1329 self.features[ feat ] = [feature_check, feat_type] 1330 if i%ncols == (ncols-1): 1331 layout.addLayout( line ) 1332 line = None 1333 if line is not None: 1334 layout.addLayout( line ) 1335 return layout
Add features to the GUI
1346 def get_current_settings( self, setting ): 1347 """ Get current settings of check or not of features """ 1348 for feat, feat_cbox in self.features.items(): 1349 setting[feat] = feat_cbox[0].isChecked() 1350 return setting
Get current settings of check or not of features
1352 def apply_settings( self, settings ): 1353 """ Set the checkboxes from preferenced settings """ 1354 for feat, checked in settings.items(): 1355 if feat in self.features.keys(): 1356 self.features[feat][0].setChecked( checked )
Set the checkboxes from preferenced settings
1358 def get_features( self ): 1359 """ Returns the list of features to measure """ 1360 feats = self.required 1361 feats_extra = [] 1362 int_extra_feats = [] 1363 int_feats = [] 1364 other_feats = [] 1365 self.do_intensity = False 1366 for feat, feat_cbox in self.features.items(): 1367 if feat_cbox[0].isChecked(): 1368 if feat_cbox[1] == "prop": 1369 feats.append( feat ) 1370 if feat_cbox[1] == "prop_extra": 1371 feats_extra.append( feat ) 1372 if feat == "shape_index": 1373 if "perimeter" not in feats: 1374 feats.append("perimeter") 1375 if "area" not in feats: 1376 feats.append("area") 1377 if feat == "roundness": 1378 if "area" not in feats: 1379 feats.append("area") 1380 if "axis_major_length" not in feats: 1381 feats.append("axis_major_length") 1382 if feat == "aspect_ratio": 1383 if "axis_major_length" not in feats: 1384 feats.append("axis_major_length") 1385 if "axis_minor_length" not in feats: 1386 feats.append("axis_minor_length") 1387 if feat_cbox[1] == "other": 1388 other_feats.append( feat ) 1389 if feat_cbox[1] == "intensity_prop": 1390 int_feats.append( feat ) 1391 self.do_intensity = True 1392 if feat_cbox[1] == "intensity_extra": 1393 int_extra_feats.append( feat ) 1394 self.do_intensity = True 1395 return feats, feats_extra, other_feats, int_feats, int_extra_feats
Returns the list of features to measure
1397 def get_channels( self ): 1398 """ Returns the list of channels to measure """ 1399 if self.do_intensity: 1400 if self.chan_list is not None: 1401 wid_channels = self.chan_list.selectedItems() 1402 channels = [] 1403 for chan in wid_channels: 1404 channels.append( chan.text() ) 1405 else: 1406 channels = ["Movie"] 1407 return channels 1408 return None
Returns the list of channels to measure
1410class EventClass( QWidget ): 1411 """ Choice of event types to export/measure """ 1412 def __init__( self, epicure ): 1413 super().__init__() 1414 layout = QVBoxLayout() 1415 1416 self.evt_classes = {} 1417 possible_classes = epicure.event_class 1418 event_layout = self.add_events( possible_classes ) 1419 layout.addLayout( event_layout ) 1420 1421 bye = wid.add_button( "Ok", self.close, "Close the window" ) 1422 layout.addWidget( bye ) 1423 self.setLayout( layout ) 1424 1425 def add_events( self, event_list ): 1426 """ Add events to the GUI """ 1427 layout = QVBoxLayout() 1428 ncols = 3 1429 for i, event in enumerate( event_list ): 1430 if i%ncols == 0: 1431 line = QHBoxLayout() 1432 event_check = wid.add_check_tolayout( line, ""+event, checked=True, descr="") 1433 self.evt_classes[ event ] = [ event_check ] 1434 if i%ncols == (ncols-1): 1435 layout.addLayout( line ) 1436 line = None 1437 if line is not None: 1438 layout.addLayout( line ) 1439 return layout 1440 1441 1442 def close( self ): 1443 """ Close the pop-up window """ 1444 self.hide() 1445 1446 def choose( self ): 1447 """ Show the interface to select the choices """ 1448 self.show() 1449 1450 def get_current_settings( self, setting ): 1451 """ Get current settings of check or not of features """ 1452 for event, event_cbox in self.evt_classes.items(): 1453 setting[event] = event_cbox[0].isChecked() 1454 return setting 1455 1456 def apply_settings( self, settings ): 1457 """ Set the checkboxes from preferenced settings """ 1458 for evt, checked in settings.items(): 1459 if evt in self.evt_classes.keys(): 1460 self.evt_classes[evt][0].setChecked( checked ) 1461 1462 def get_evt_classes( self ): 1463 """ Returns the list of events to measure """ 1464 events = [] 1465 for evt, evt_cbox in self.evt_classes.items(): 1466 if evt_cbox[0].isChecked(): 1467 events.append( evt ) 1468 return events
Choice of event types to export/measure
1412 def __init__( self, epicure ): 1413 super().__init__() 1414 layout = QVBoxLayout() 1415 1416 self.evt_classes = {} 1417 possible_classes = epicure.event_class 1418 event_layout = self.add_events( possible_classes ) 1419 layout.addLayout( event_layout ) 1420 1421 bye = wid.add_button( "Ok", self.close, "Close the window" ) 1422 layout.addWidget( bye ) 1423 self.setLayout( layout )
1425 def add_events( self, event_list ): 1426 """ Add events to the GUI """ 1427 layout = QVBoxLayout() 1428 ncols = 3 1429 for i, event in enumerate( event_list ): 1430 if i%ncols == 0: 1431 line = QHBoxLayout() 1432 event_check = wid.add_check_tolayout( line, ""+event, checked=True, descr="") 1433 self.evt_classes[ event ] = [ event_check ] 1434 if i%ncols == (ncols-1): 1435 layout.addLayout( line ) 1436 line = None 1437 if line is not None: 1438 layout.addLayout( line ) 1439 return layout
Add events to the GUI
1450 def get_current_settings( self, setting ): 1451 """ Get current settings of check or not of features """ 1452 for event, event_cbox in self.evt_classes.items(): 1453 setting[event] = event_cbox[0].isChecked() 1454 return setting
Get current settings of check or not of features
1456 def apply_settings( self, settings ): 1457 """ Set the checkboxes from preferenced settings """ 1458 for evt, checked in settings.items(): 1459 if evt in self.evt_classes.keys(): 1460 self.evt_classes[evt][0].setChecked( checked )
Set the checkboxes from preferenced settings
1470class FeaturesTable(QWidget): 1471 """ Widget to visualize and interact with the measurement table """ 1472 1473 def __init__(self, napari_viewer, epicure): 1474 super().__init__() 1475 self.viewer = napari_viewer 1476 self.epicure = epicure 1477 self.wid_table = QTableWidget() 1478 self.wid_table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers) 1479 self.setLayout(QGridLayout()) 1480 self.layout().addWidget(self.wid_table) 1481 self.wid_table.clicked.connect(self.show_label) 1482 self.wid_table.setSortingEnabled(True) 1483 1484 def show_label(self): 1485 """ When click on the table, show selected cell """ 1486 if self.wid_table is not None: 1487 row = self.wid_table.currentRow() 1488 self.epicure.seglayer.show_selected_label = False 1489 headers = [self.wid_table.horizontalHeaderItem(ind).text() for ind in range(self.wid_table.columnCount()) ] 1490 labelind = None 1491 if "label" in headers: 1492 labelind = headers.index("label") 1493 if "Label" in headers: 1494 labelind = headers.index("Label") 1495 frameind = None 1496 if "frame" in headers: 1497 frameind = headers.index("frame") 1498 if labelind is not None and labelind >= 0: 1499 lab = int(self.wid_table.item(row, labelind).text()) 1500 if frameind is not None: 1501 ## set current frame to the selected row 1502 frame = int(self.wid_table.item(row, frameind).text()) 1503 ut.set_frame(self.viewer, frame) 1504 else: 1505 ## set current frame to the first frame where label or track is present 1506 frame = self.epicure.tracking.get_first_frame( lab ) 1507 if frame is not None: 1508 ut.set_frame(self.viewer, frame) 1509 self.epicure.seglayer.selected_label = lab 1510 self.epicure.seglayer.show_selected_label = True 1511 1512 1513 def get_features_list(self): 1514 """ Return list of measured features """ 1515 return [ self.wid_table.horizontalHeaderItem(ind).text() for ind in range(self.wid_table.columnCount()) ] 1516 1517 def set_table(self, table): 1518 self.wid_table.clear() 1519 self.wid_table.setRowCount(table.shape[0]) 1520 self.wid_table.setColumnCount(table.shape[1]) 1521 1522 for c, column in enumerate(table.keys()): 1523 column_name = column 1524 self.wid_table.setHorizontalHeaderItem(c, QTableWidgetItem(column_name)) 1525 for r, value in enumerate(table.get(column)): 1526 item = QTableWidgetItem() 1527 item.setData( Qt.EditRole, value) 1528 self.wid_table.setItem(r, c, item)
Widget to visualize and interact with the measurement table
1473 def __init__(self, napari_viewer, epicure): 1474 super().__init__() 1475 self.viewer = napari_viewer 1476 self.epicure = epicure 1477 self.wid_table = QTableWidget() 1478 self.wid_table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers) 1479 self.setLayout(QGridLayout()) 1480 self.layout().addWidget(self.wid_table) 1481 self.wid_table.clicked.connect(self.show_label) 1482 self.wid_table.setSortingEnabled(True)
1484 def show_label(self): 1485 """ When click on the table, show selected cell """ 1486 if self.wid_table is not None: 1487 row = self.wid_table.currentRow() 1488 self.epicure.seglayer.show_selected_label = False 1489 headers = [self.wid_table.horizontalHeaderItem(ind).text() for ind in range(self.wid_table.columnCount()) ] 1490 labelind = None 1491 if "label" in headers: 1492 labelind = headers.index("label") 1493 if "Label" in headers: 1494 labelind = headers.index("Label") 1495 frameind = None 1496 if "frame" in headers: 1497 frameind = headers.index("frame") 1498 if labelind is not None and labelind >= 0: 1499 lab = int(self.wid_table.item(row, labelind).text()) 1500 if frameind is not None: 1501 ## set current frame to the selected row 1502 frame = int(self.wid_table.item(row, frameind).text()) 1503 ut.set_frame(self.viewer, frame) 1504 else: 1505 ## set current frame to the first frame where label or track is present 1506 frame = self.epicure.tracking.get_first_frame( lab ) 1507 if frame is not None: 1508 ut.set_frame(self.viewer, frame) 1509 self.epicure.seglayer.selected_label = lab 1510 self.epicure.seglayer.show_selected_label = True
When click on the table, show selected cell
1513 def get_features_list(self): 1514 """ Return list of measured features """ 1515 return [ self.wid_table.horizontalHeaderItem(ind).text() for ind in range(self.wid_table.columnCount()) ]
Return list of measured features
1517 def set_table(self, table): 1518 self.wid_table.clear() 1519 self.wid_table.setRowCount(table.shape[0]) 1520 self.wid_table.setColumnCount(table.shape[1]) 1521 1522 for c, column in enumerate(table.keys()): 1523 column_name = column 1524 self.wid_table.setHorizontalHeaderItem(c, QTableWidgetItem(column_name)) 1525 for r, value in enumerate(table.get(column)): 1526 item = QTableWidgetItem() 1527 item.setData( Qt.EditRole, value) 1528 self.wid_table.setItem(r, c, item)
1530class TemporalPlots(QWidget): 1531 """ Widget to visualize and interact with temporal plots """ 1532 1533 def __init__(self, napari_viewer, epicure): 1534 super().__init__() 1535 self.viewer = napari_viewer 1536 self.epicure = epicure 1537 self.features_list = ["frame"] 1538 self.parameter_gui() 1539 self.vline = None 1540 self.ymin = None 1541 #self.viewer.window.add_dock_widget( self.plot_wid, name="Temporal plot" ) 1542 1543 def parameter_gui(self): 1544 """ add widget to choose plotting parameters """ 1545 1546 layout = QVBoxLayout() 1547 1548 ## choice of feature to plot 1549 feat_choice, self.feature_choice = wid.list_line( label="Plot feature", descr="Choose the feature to plot", func=self.plot_feature ) 1550 layout.addLayout(feat_choice) 1551 ## option to average by group 1552 ck_line, self.avg_group, self.smooth = wid.double_check( "Average by groups", False, self.plot_feature, "Show a line by cell or a line by group", "Smooth lines", False, self.plot_feature, "Smooth temporally (moving average) the plotted lines" ) 1553 layout.addLayout(ck_line) 1554 ## show the plot 1555 self.plot_wid = self.create_plotwidget() 1556 layout.addWidget(self.plot_wid) 1557 ## save plot or save data of the plot 1558 line = wid.double_button( "Save plot image", self.save_plot_image, "Save the grapic in a PNG file", "Save plot data", self.save_plot_data, "Save the value used for the plot in .csv file" ) 1559 self.by_label = wid.add_check( "Arranged data by label", False, None, "Save the data with one column by label" ) 1560 line.addWidget( self.by_label ) 1561 layout.addLayout( line ) 1562 self.setLayout(layout) 1563 self.resize(1000,800) 1564 1565 def setTable(self, table): 1566 """ Data table to plot """ 1567 self.table = table 1568 self.features_list = self.table.keys() 1569 self.update_feature_list() 1570 1571 def update_table(self, table): 1572 """ Update the current plot with the updated table """ 1573 self.table = table 1574 curchoice = self.feature_choice.currentText() 1575 self.features_list = self.table.keys() 1576 self.update_feature_list() 1577 if curchoice in self.features_list: 1578 ind = list(self.features_list).index(curchoice) 1579 self.feature_choice.setCurrentIndex(ind) 1580 self.plot_feature() 1581 1582 def update_feature_list(self): 1583 """ Update the list of feature in the GUI """ 1584 self.feature_choice.clear() 1585 for feat in self.features_list: 1586 self.feature_choice.addItem(feat) 1587 if "division" in self.features_list and "extrusion" in self.features_list: 1588 self.feature_choice.addItem( "division&extrusion" ) 1589 1590 def plot_feature(self): 1591 """ Plot the selected feature in the temporal graph """ 1592 feat = self.feature_choice.currentText() 1593 if feat == "label": 1594 return 1595 if feat == "": 1596 return 1597 if feat == "division&extrusion": 1598 feat = ["division", "extrusion"] 1599 else: 1600 feat = [feat] 1601 1602 tab = list( zip(self.table["frame"]) ) 1603 labname = [] 1604 for ft in feat: 1605 tab = [ (*t, v) for t, v in zip( tab, self.table[ft]) ] 1606 tab = [ (*t, v) for t, v in zip( tab, self.table["label"]) ] 1607 labname.append("label") 1608 if "group" in self.table: 1609 tab = [ (*t, v) for t, v in zip( tab, self.table["group"]) ] 1610 labname.append("group") 1611 1612 self.df = pand.DataFrame( tab, columns=["frame"] + feat + labname ) 1613 shape = "linear" 1614 if self.smooth.isChecked(): 1615 shape = "spline" 1616 if "group" in self.table and self.avg_group.isChecked(): 1617 self.dfmean = self.df.groupby(['group', 'frame'])[feat].mean().reset_index() 1618 self.df.columns.name = 'group' 1619 self.fig = px.line( self.dfmean, x='frame', y=feat, color='group', labels={'frame': 'Time (frame)'}, line_shape=shape, render_mode="svg" ) 1620 else: 1621 if len( np.unique(self.df["label"]) ) > 1000: 1622 ut.show_warning( "Too many lines to plot; Using a random subset instead" ) 1623 subset = sample( np.unique(self.df["label"]).tolist(), 1000) 1624 subdf = self.df[self.df["label"].isin(subset)] 1625 self.fig = px.line( subdf, x="frame", y=feat[0], color="label", labels={'frame': 'Time (frame)'}, line_shape = shape, render_mode="svg") 1626 if len(feat) > 1: 1627 addfig = px.line(subdf, x="frame", y=feat[1], color="label", line_shape = shape ) 1628 addfig.update_traces( patch={"line": {"dash":"dot"}} ) 1629 self.fig.add_trace( addfig.data[0] ) 1630 else: 1631 self.fig = px.line( self.df, x="frame", y=feat[0], color="label", labels={'frame': 'Time (frame)'}, line_shape = shape, render_mode="svg") 1632 if len(feat) > 1: 1633 addfig = px.line(self.df, x="frame", y=feat[1], color="label", line_shape = shape ) 1634 addfig.update_traces( patch={"line": {"dash":"dot"}} ) 1635 self.fig.add_trace( addfig.data[0] ) 1636 1637 if self.webengine: 1638 self.browser.setHtml( self.fig.to_html(include_plotlyjs='cdn')) 1639 else: 1640 self.show_plot_in_browser( self.fig.to_html(include_plotlyjs='cdn')) 1641 1642 def show_plot_in_browser(self,html): 1643 with tempfile.NamedTemporaryFile(mode='w', suffix='.html', delete=False) as f: 1644 f.write(html) 1645 url = 'file://' + f.name 1646 webbrowser.open(url) 1647 1648 def smooth_df( self, df ): 1649 """ Smooth temporally the dataframe by label or by group """ 1650 rollsize = 20 1651 ## average on a smaller scale if only few frames 1652 if np.max( self.table["frame"] ) <= 20: 1653 rollsize = 5 1654 if feat+"_smooth" in self.df.columns: 1655 feat = feat+"_smooth" 1656 else: 1657 self.df[feat+"_smooth"] = self.df[feat].rolling(rollsize, center=True).mean() 1658 #print(self.df) 1659 feat = feat+"_smooth" 1660 1661 def save_plot_image( self ): 1662 """ Save current plot graphic to PNG image """ 1663 feat = self.feature_choice.currentText() 1664 outfile = self.epicure.outname()+"_plot_"+feat+".png" 1665 if self.fig is not None: 1666 self.fig.write_image( outfile ) 1667 if self.epicure.verbose > 0: 1668 ut.show_info("Measures saved in "+outfile) 1669 1670 def save_plot_data( self ): 1671 """ Save the raw data to redraw the current plot to csv file """ 1672 feat = self.feature_choice.currentText() 1673 outfile = self.epicure.outname()+"_time_"+feat+".csv" 1674 if self.avg_group.isChecked(): 1675 data = self.dfmean.reset_index()[["frame", "group", feat]] 1676 if self.by_label.isChecked(): 1677 df = pand.pivot_table( data, columns="label", index="frame", values=feat ) 1678 df.to_csv( outfile, sep='\t', header=True, index=True ) 1679 else: 1680 data[["frame", "group", feat]].to_csv( outfile, sep='\t', header=True, index=False ) 1681 else: 1682 data = self.df.reset_index()[["frame", "label", feat]] 1683 if self.by_label.isChecked(): 1684 df = pand.pivot_table( data, columns="label", index="frame", values=feat ) 1685 df.to_csv( outfile, sep='\t', header=True, index=True ) 1686 else: 1687 data[["frame", "label", feat]].to_csv( outfile, sep='\t', header=True, index=False ) 1688 1689 def move_framepos(self, frame): 1690 """ Move the vertical line showing the current frame position in the main window """ 1691 return 1692 #if self.fig is not None: 1693 # self.fig.add_vline( x=frame, line_dash="dash", line_color="gray" ) 1694 # self.browser.setHtml( self.fig.to_html(include_plotlyjs='cdn')) 1695 1696 1697 1698 def import_webengineview(self): 1699 """Return QWebEngineView from whichever Qt is available.""" 1700 import importlib 1701 try: 1702 # Fall back to Qt5 1703 mod = importlib.import_module("PyQt5.QtWebEngineWidgets") 1704 self.browser = mod.QWebEngineView(self) 1705 return 1706 except Exception: 1707 pass 1708 1709 try: 1710 # Try Qt6 first 1711 view = importlib.import_module("PyQt6.QtWebEngineWidgets") 1712 self.browser = view.QWebEngineView( parent=self ) 1713 return 1714 except Exception as e: 1715 print(e) 1716 pass 1717 1718 raise ImportError( 1719 "No QtWebEngine found. Install PyQt6-WebEngine or PyQtWebEngine." 1720 ) 1721 1722 1723 def create_plotwidget(self): 1724 """ Create plot window """ 1725 try: 1726 self.import_webengineview() 1727 self.webengine = True 1728 except: 1729 self.webengine = False 1730 self.browser = NoEngineViewer() 1731 return self.browser 1732 print(self.webengine) 1733 return self.browser
Widget to visualize and interact with temporal plots
1533 def __init__(self, napari_viewer, epicure): 1534 super().__init__() 1535 self.viewer = napari_viewer 1536 self.epicure = epicure 1537 self.features_list = ["frame"] 1538 self.parameter_gui() 1539 self.vline = None 1540 self.ymin = None 1541 #self.viewer.window.add_dock_widget( self.plot_wid, name="Temporal plot" )
1543 def parameter_gui(self): 1544 """ add widget to choose plotting parameters """ 1545 1546 layout = QVBoxLayout() 1547 1548 ## choice of feature to plot 1549 feat_choice, self.feature_choice = wid.list_line( label="Plot feature", descr="Choose the feature to plot", func=self.plot_feature ) 1550 layout.addLayout(feat_choice) 1551 ## option to average by group 1552 ck_line, self.avg_group, self.smooth = wid.double_check( "Average by groups", False, self.plot_feature, "Show a line by cell or a line by group", "Smooth lines", False, self.plot_feature, "Smooth temporally (moving average) the plotted lines" ) 1553 layout.addLayout(ck_line) 1554 ## show the plot 1555 self.plot_wid = self.create_plotwidget() 1556 layout.addWidget(self.plot_wid) 1557 ## save plot or save data of the plot 1558 line = wid.double_button( "Save plot image", self.save_plot_image, "Save the grapic in a PNG file", "Save plot data", self.save_plot_data, "Save the value used for the plot in .csv file" ) 1559 self.by_label = wid.add_check( "Arranged data by label", False, None, "Save the data with one column by label" ) 1560 line.addWidget( self.by_label ) 1561 layout.addLayout( line ) 1562 self.setLayout(layout) 1563 self.resize(1000,800)
add widget to choose plotting parameters
1565 def setTable(self, table): 1566 """ Data table to plot """ 1567 self.table = table 1568 self.features_list = self.table.keys() 1569 self.update_feature_list()
Data table to plot
1571 def update_table(self, table): 1572 """ Update the current plot with the updated table """ 1573 self.table = table 1574 curchoice = self.feature_choice.currentText() 1575 self.features_list = self.table.keys() 1576 self.update_feature_list() 1577 if curchoice in self.features_list: 1578 ind = list(self.features_list).index(curchoice) 1579 self.feature_choice.setCurrentIndex(ind) 1580 self.plot_feature()
Update the current plot with the updated table
1582 def update_feature_list(self): 1583 """ Update the list of feature in the GUI """ 1584 self.feature_choice.clear() 1585 for feat in self.features_list: 1586 self.feature_choice.addItem(feat) 1587 if "division" in self.features_list and "extrusion" in self.features_list: 1588 self.feature_choice.addItem( "division&extrusion" )
Update the list of feature in the GUI
1590 def plot_feature(self): 1591 """ Plot the selected feature in the temporal graph """ 1592 feat = self.feature_choice.currentText() 1593 if feat == "label": 1594 return 1595 if feat == "": 1596 return 1597 if feat == "division&extrusion": 1598 feat = ["division", "extrusion"] 1599 else: 1600 feat = [feat] 1601 1602 tab = list( zip(self.table["frame"]) ) 1603 labname = [] 1604 for ft in feat: 1605 tab = [ (*t, v) for t, v in zip( tab, self.table[ft]) ] 1606 tab = [ (*t, v) for t, v in zip( tab, self.table["label"]) ] 1607 labname.append("label") 1608 if "group" in self.table: 1609 tab = [ (*t, v) for t, v in zip( tab, self.table["group"]) ] 1610 labname.append("group") 1611 1612 self.df = pand.DataFrame( tab, columns=["frame"] + feat + labname ) 1613 shape = "linear" 1614 if self.smooth.isChecked(): 1615 shape = "spline" 1616 if "group" in self.table and self.avg_group.isChecked(): 1617 self.dfmean = self.df.groupby(['group', 'frame'])[feat].mean().reset_index() 1618 self.df.columns.name = 'group' 1619 self.fig = px.line( self.dfmean, x='frame', y=feat, color='group', labels={'frame': 'Time (frame)'}, line_shape=shape, render_mode="svg" ) 1620 else: 1621 if len( np.unique(self.df["label"]) ) > 1000: 1622 ut.show_warning( "Too many lines to plot; Using a random subset instead" ) 1623 subset = sample( np.unique(self.df["label"]).tolist(), 1000) 1624 subdf = self.df[self.df["label"].isin(subset)] 1625 self.fig = px.line( subdf, x="frame", y=feat[0], color="label", labels={'frame': 'Time (frame)'}, line_shape = shape, render_mode="svg") 1626 if len(feat) > 1: 1627 addfig = px.line(subdf, x="frame", y=feat[1], color="label", line_shape = shape ) 1628 addfig.update_traces( patch={"line": {"dash":"dot"}} ) 1629 self.fig.add_trace( addfig.data[0] ) 1630 else: 1631 self.fig = px.line( self.df, x="frame", y=feat[0], color="label", labels={'frame': 'Time (frame)'}, line_shape = shape, render_mode="svg") 1632 if len(feat) > 1: 1633 addfig = px.line(self.df, x="frame", y=feat[1], color="label", line_shape = shape ) 1634 addfig.update_traces( patch={"line": {"dash":"dot"}} ) 1635 self.fig.add_trace( addfig.data[0] ) 1636 1637 if self.webengine: 1638 self.browser.setHtml( self.fig.to_html(include_plotlyjs='cdn')) 1639 else: 1640 self.show_plot_in_browser( self.fig.to_html(include_plotlyjs='cdn'))
Plot the selected feature in the temporal graph
1648 def smooth_df( self, df ): 1649 """ Smooth temporally the dataframe by label or by group """ 1650 rollsize = 20 1651 ## average on a smaller scale if only few frames 1652 if np.max( self.table["frame"] ) <= 20: 1653 rollsize = 5 1654 if feat+"_smooth" in self.df.columns: 1655 feat = feat+"_smooth" 1656 else: 1657 self.df[feat+"_smooth"] = self.df[feat].rolling(rollsize, center=True).mean() 1658 #print(self.df) 1659 feat = feat+"_smooth"
Smooth temporally the dataframe by label or by group
1661 def save_plot_image( self ): 1662 """ Save current plot graphic to PNG image """ 1663 feat = self.feature_choice.currentText() 1664 outfile = self.epicure.outname()+"_plot_"+feat+".png" 1665 if self.fig is not None: 1666 self.fig.write_image( outfile ) 1667 if self.epicure.verbose > 0: 1668 ut.show_info("Measures saved in "+outfile)
Save current plot graphic to PNG image
1670 def save_plot_data( self ): 1671 """ Save the raw data to redraw the current plot to csv file """ 1672 feat = self.feature_choice.currentText() 1673 outfile = self.epicure.outname()+"_time_"+feat+".csv" 1674 if self.avg_group.isChecked(): 1675 data = self.dfmean.reset_index()[["frame", "group", feat]] 1676 if self.by_label.isChecked(): 1677 df = pand.pivot_table( data, columns="label", index="frame", values=feat ) 1678 df.to_csv( outfile, sep='\t', header=True, index=True ) 1679 else: 1680 data[["frame", "group", feat]].to_csv( outfile, sep='\t', header=True, index=False ) 1681 else: 1682 data = self.df.reset_index()[["frame", "label", feat]] 1683 if self.by_label.isChecked(): 1684 df = pand.pivot_table( data, columns="label", index="frame", values=feat ) 1685 df.to_csv( outfile, sep='\t', header=True, index=True ) 1686 else: 1687 data[["frame", "label", feat]].to_csv( outfile, sep='\t', header=True, index=False )
Save the raw data to redraw the current plot to csv file
1689 def move_framepos(self, frame): 1690 """ Move the vertical line showing the current frame position in the main window """ 1691 return 1692 #if self.fig is not None: 1693 # self.fig.add_vline( x=frame, line_dash="dash", line_color="gray" ) 1694 # self.browser.setHtml( self.fig.to_html(include_plotlyjs='cdn'))
Move the vertical line showing the current frame position in the main window
1698 def import_webengineview(self): 1699 """Return QWebEngineView from whichever Qt is available.""" 1700 import importlib 1701 try: 1702 # Fall back to Qt5 1703 mod = importlib.import_module("PyQt5.QtWebEngineWidgets") 1704 self.browser = mod.QWebEngineView(self) 1705 return 1706 except Exception: 1707 pass 1708 1709 try: 1710 # Try Qt6 first 1711 view = importlib.import_module("PyQt6.QtWebEngineWidgets") 1712 self.browser = view.QWebEngineView( parent=self ) 1713 return 1714 except Exception as e: 1715 print(e) 1716 pass 1717 1718 raise ImportError( 1719 "No QtWebEngine found. Install PyQt6-WebEngine or PyQtWebEngine." 1720 )
Return QWebEngineView from whichever Qt is available.
1723 def create_plotwidget(self): 1724 """ Create plot window """ 1725 try: 1726 self.import_webengineview() 1727 self.webengine = True 1728 except: 1729 self.webengine = False 1730 self.browser = NoEngineViewer() 1731 return self.browser 1732 print(self.webengine) 1733 return self.browser
Create plot window
1735class NoEngineViewer(QWidget): 1736 def __init__(self): 1737 super().__init__() 1738 layout = QVBoxLayout() 1739 self.text_browser = QTextBrowser() 1740 self.text_browser.setHtml("<h2>Plots will be redirected to web browser</h2>" \ 1741 "" \ 1742 "Your napari installation is using pyside6 that doesn't have the necessary dependency to show the plot in this window interactively. To have this option, reinstall napari with pyqt5 or pyqt6." \ 1743 "" \ 1744 "Otherwise, you can still see the plot, it will open in your web browser, but will be slower to display, reload the web page if nothing appears." \ 1745 "") 1746 layout.addWidget(self.text_browser) 1747 self.setLayout(layout)
QWidget(parent: Optional[QWidget] = None, flags: Qt.WindowType = Qt.WindowFlags())