def plot_3d_surface(self, roi, channel, segment=False, flipz=False, offset=None): """Plots areas with greater intensity as 3D surfaces. The scene will be cleared before display. Args: roi (:class:`numpy.ndarray`): Region of interest either as a 3D ``z,y,x`` or 4D ``z,y,x,c`` array. channel (int): Channel to select, which can be None to indicate all channels. segment (bool): True to denoise and segment ``roi`` before displaying, which may remove artifacts that might otherwise lead to spurious surfaces. Defaults to False. flipz: True to invert ``roi`` along z-axis to match handedness of Matplotlib with z progressing upward; defaults to False. offset (Sequence[int]): Origin coordinates in ``z,y,x``; defaults to None. Returns: list: List of Mayavi surfaces for each displayed channel, which are also stored in :attr:`surfaces`. """ # Plot in Mayavi print("viewing 3D surface") pipeline = self.scene.mlab.pipeline settings = config.roi_profile if flipz: # invert along z-axis to match handedness of Matplotlib with z up roi = roi[::-1] if offset is not None: # invert z-offset and translate by ROI z-size so ROI is # mirrored across the xy-plane offset = np.copy(offset) offset[0] = -offset[0] - roi.shape[0] isotropic = plot_3d.get_isotropic_vis(settings) # saturate to remove noise and normalize values roi = plot_3d.saturate_roi(roi, channel=channel) # turn off segmentation if ROI too big (arbitrarily set here as # > 10 million pixels) to avoid performance hit and since likely showing # large region of downsampled image anyway, where don't need hi res num_pixels = np.prod(roi.shape) to_segment = num_pixels < 10000000 time_start = time() multichannel, channels = plot_3d.setup_channels(roi, channel, 3) surfaces = [] for chl in channels: roi_show = roi[..., chl] if multichannel else roi # clip to minimize sub-nuclear variation roi_show = np.clip(roi_show, 0.2, 0.8) if segment: # denoising makes for much cleaner images but also seems to # allow structures to blend together # TODO: consider segmenting individual structures and rendering # as separate surfaces to avoid blending roi_show = restoration.denoise_tv_chambolle(roi_show, weight=0.1) # build surface from segmented ROI if to_segment: vmin, vmax = np.percentile(roi_show, (40, 70)) walker = segmenter.segment_rw(roi_show, chl, vmin=vmin, vmax=vmax) roi_show *= np.subtract(walker[0], 1) else: print("deferring segmentation as {} px is above threshold". format(num_pixels)) # ROI is in (z, y, x) order, so need to transpose or swap x,z axes roi_show = np.transpose(roi_show) surface = pipeline.scalar_field(roi_show) # Contour -> Surface pipeline # create the surface surface = pipeline.contour(surface) # remove many more extraneous points surface = pipeline.user_defined(surface, filter="SmoothPolyDataFilter") surface.filter.number_of_iterations = 400 surface.filter.relaxation_factor = 0.015 # distinguishing pos vs neg curvatures? surface = pipeline.user_defined(surface, filter="Curvatures") surface = self.scene.mlab.pipeline.surface(surface) module_manager = surface.module_manager module_manager.scalar_lut_manager.data_range = np.array([-2, 0]) module_manager.scalar_lut_manager.lut_mode = "gray" ''' # Surface pipleline with contours enabled (similar to above?) surface = pipeline.contour_surface( surface, color=(0.7, 1, 0.7), line_width=6.0) surface.actor.property.representation = 'wireframe' #surface.actor.property.line_width = 6.0 surface.actor.mapper.scalar_visibility = False ''' ''' # IsoSurface pipeline # uses unique IsoSurface module but appears to have # similar output to contour_surface surface = pipeline.iso_surface(surface) # limit contours for simpler surfaces including smaller file sizes; # TODO: consider making settable as arg or through profile surface.contour.number_of_contours = 1 try: # increase min to further reduce complexity surface.contour.minimum_contour = 0.5 surface.contour.maximum_contour = 0.8 except Exception as e: print(e) print("ignoring min/max contour for now") ''' if offset is not None: # translate to offset scaled by isotropic factor surface.actor.actor.position = np.multiply(offset, isotropic)[::-1] # scale surfaces, which expands/contracts but does not appear # to translate the surface position surface.actor.actor.scale = isotropic[::-1] surfaces.append(surface) # keep visual ordering of surfaces when opacity is reduced self.scene.renderer.use_depth_peeling = True print("time to render 3D surface: {}".format(time() - time_start)) self.surfaces = surfaces return surfaces
def threshold(roi): """Thresholds the ROI, with options for various techniques as well as post-thresholding morphological filtering. Args: roi: Region of interest, given as [z, y, x]. Returns: The thresholded region. """ settings = config.roi_profile thresh_type = settings["thresholding"] size = settings["thresholding_size"] thresholded = roi roi_thresh = 0 # various thresholding model if thresh_type == "otsu": try: roi_thresh = filters.threshold_otsu(roi, size) thresholded = roi > roi_thresh except ValueError as e: # np.histogram may give an error apparently if any NaN, so # workaround is set all elements in ROI to False print(e) thresholded = roi > np.max(roi) elif thresh_type == "local": roi_thresh = np.copy(roi) for i in range(roi_thresh.shape[0]): roi_thresh[i] = filters.threshold_local(roi_thresh[i], size, mode="wrap") thresholded = roi > roi_thresh elif thresh_type == "local-otsu": # TODO: not working yet selem = morphology.disk(15) print(np.min(roi), np.max(roi)) roi_thresh = np.copy(roi) roi_thresh = libmag.normalize(roi_thresh, -1.0, 1.0) print(roi_thresh) print(np.min(roi_thresh), np.max(roi_thresh)) for i in range(roi.shape[0]): roi_thresh[i] = filters.rank.otsu(roi_thresh[i], selem) thresholded = roi > roi_thresh elif thresh_type == "random_walker": thresholded = segmenter.segment_rw(roi, size) # dilation/erosion, adjusted based on overall intensity thresh_mean = np.mean(thresholded) print("thresh_mean: {}".format(thresh_mean)) selem_dil = None selem_eros = None if thresh_mean > 0.45: thresholded = morphology.erosion(thresholded, morphology.cube(1)) selem_dil = morphology.ball(1) selem_eros = morphology.octahedron(1) elif thresh_mean > 0.35: thresholded = morphology.erosion(thresholded, morphology.cube(2)) selem_dil = morphology.ball(2) selem_eros = morphology.octahedron(1) elif thresh_mean > 0.3: selem_dil = morphology.ball(1) selem_eros = morphology.cube(5) elif thresh_mean > 0.1: selem_dil = morphology.ball(1) selem_eros = morphology.cube(4) elif thresh_mean > 0.05: selem_dil = morphology.octahedron(2) selem_eros = morphology.octahedron(2) else: selem_dil = morphology.octahedron(1) selem_eros = morphology.octahedron(2) if selem_dil is not None: thresholded = morphology.dilation(thresholded, selem_dil) if selem_eros is not None: thresholded = morphology.erosion(thresholded, selem_eros) return thresholded
def plot_3d_surface(roi, scene_mlab, channel, segment=False, flipz=False): """Plots areas with greater intensity as 3D surfaces. Args: roi (:obj:`np.ndarray`): Region of interest either as a 3D (z, y, x) or 4D (z, y, x, channel) ndarray. scene_mlab (:mod:``mayavi.mlab``): Mayavi mlab module. Any current image will be cleared first. channel (int): Channel to select, which can be None to indicate all channels. segment (bool): True to denoise and segment ``roi`` before displaying, which may remove artifacts that might otherwise lead to spurious surfaces. Defaults to False. flipz: True to invert ``roi`` along z-axis to match handedness of Matplotlib with z progressing upward; defaults to False. """ # Plot in Mayavi #mlab.figure() print("viewing 3D surface") pipeline = scene_mlab.pipeline scene_mlab.clf() settings = config.roi_profile if flipz: # invert along z-axis to match handedness of Matplotlib with z up roi = roi[::-1] # saturate to remove noise and normalize values roi = saturate_roi(roi, channel=channel) # turn off segmentation if ROI too big (arbitrarily set here as # > 10 million pixels) to avoid performance hit and since likely showing # large region of downsampled image anyway, where don't need hi res num_pixels = np.prod(roi.shape) to_segment = num_pixels < 10000000 time_start = time() multichannel, channels = setup_channels(roi, channel, 3) for chl in channels: roi_show = roi[..., chl] if multichannel else roi # clip to minimize sub-nuclear variation roi_show = np.clip(roi_show, 0.2, 0.8) if segment: # denoising makes for much cleaner images but also seems to allow # structures to blend together. TODO: consider segmented individual # structures and rendering as separate surfaces to avoid blending roi_show = restoration.denoise_tv_chambolle(roi_show, weight=0.1) # build surface from segmented ROI if to_segment: vmin, vmax = np.percentile(roi_show, (40, 70)) walker = segmenter.segment_rw(roi_show, chl, vmin=vmin, vmax=vmax) roi_show *= np.subtract(walker[0], 1) else: print("deferring segmentation as {} px is above threshold". format(num_pixels)) # ROI is in (z, y, x) order, so need to transpose or swap x,z axes roi_show = np.transpose(roi_show) surface = pipeline.scalar_field(roi_show) # Contour -> Surface pipeline # create the surface surface = pipeline.contour(surface) # remove many more extraneous points surface = pipeline.user_defined(surface, filter="SmoothPolyDataFilter") surface.filter.number_of_iterations = 400 surface.filter.relaxation_factor = 0.015 # distinguishing pos vs neg curvatures? surface = pipeline.user_defined(surface, filter="Curvatures") surface = scene_mlab.pipeline.surface(surface) module_manager = surface.module_manager module_manager.scalar_lut_manager.data_range = np.array([-2, 0]) module_manager.scalar_lut_manager.lut_mode = "gray" ''' # Surface pipleline with contours enabled (similar to above?) surface = pipeline.contour_surface( surface, color=(0.7, 1, 0.7), line_width=6.0) surface.actor.property.representation = 'wireframe' #surface.actor.property.line_width = 6.0 surface.actor.mapper.scalar_visibility = False ''' ''' # IsoSurface pipeline # uses unique IsoSurface module but appears to have # similar output to contour_surface surface = pipeline.iso_surface(surface) # limit contours for simpler surfaces including smaller file sizes; # TODO: consider making settable as arg or through profile surface.contour.number_of_contours = 1 try: # increase min to further reduce complexity surface.contour.minimum_contour = 0.5 surface.contour.maximum_contour = 0.8 except Exception as e: print(e) print("ignoring min/max contour for now") ''' _resize_glyphs_isotropic(settings, surface) print("time to render 3D surface: {}".format(time() - time_start))