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