def launch_sagittal_viewer(img: Image, labels: Sequence[int], msg: str, previous_points: Sequence[Coordinate] = None, output_img: Image = None) -> Image: from spinalcordtoolbox.gui import base from spinalcordtoolbox.gui.sagittal import launch_sagittal_dialog params = base.AnatomicalParams() params.vertebraes = labels params.input_file_name = img.absolutepath if output_img is not None: params.output_file_name = output_img.absolutepath else: params.output_file_name = img.absolutepath params.subtitle = msg if previous_points is not None: params.message_warn = 'Please select the label you want to add \nor correct in the list below before clicking \non the image' out = zeros_like(img, dtype='uint8') out.absolutepath = params.output_file_name launch_sagittal_dialog(img, out, params, previous_points) return out
def launch_sagittal_viewer(self, labels): from spinalcordtoolbox.gui import base from spinalcordtoolbox.gui.sagittal import launch_sagittal_dialog params = base.AnatomicalParams() params.vertebraes = labels params.input_file_name = self.image_input.absolutepath params.output_file_name = self.fname_output params.subtitle = self.msg output = msct_image.zeros_like(self.image_input) output.absolutepath = self.fname_output launch_sagittal_dialog(self.image_input, output, params) return output
def launch_sagittal_viewer(self, labels): from spinalcordtoolbox.gui import base from spinalcordtoolbox.gui.sagittal import launch_sagittal_dialog params = base.AnatomicalParams() params.vertebraes = labels params.input_file_name = self.image_input.file_name params.output_file_name = self.fname_output output = self.image_input.copy() output.data *= 0 output.setFileName(self.fname_output) launch_sagittal_dialog(self.image_input, output, params) return output
def launch_sagittal_viewer(self, labels, previous_points=None): from spinalcordtoolbox.gui import base from spinalcordtoolbox.gui.sagittal import launch_sagittal_dialog params = base.AnatomicalParams() params.vertebraes = labels params.input_file_name = self.image_input.absolutepath params.output_file_name = self.fname_output params.subtitle = self.msg if previous_points is not None: params.message_warn = 'Please select the label you want to add \nor correct in the list below before clicking \non the image' output = msct_image.zeros_like(self.image_input) output.absolutepath = self.fname_output launch_sagittal_dialog(self.image_input, output, params, previous_points) return output
def get_bbox_from_gui(self): """ Launch a GUI. The medial sagittal plane of the image is shown. User selects two points: top-left and bottom- right of the cropping window. Note: There is no cropping along the right-left direction. :return: """ from spinalcordtoolbox.gui import base from spinalcordtoolbox.gui.sagittal import launch_sagittal_dialog # Change orientation to SAL (for displaying sagittal view in the GUI) native_orientation = self.img_in.orientation self.img_in.change_orientation('SAL') # Launch GUI params = base.AnatomicalParams() params.vertebraes = [ 1, 2 ] # TODO: Have user draw a sliding rectangle instead (more intuitive) params.subtitle = "Click on the top-left (Label 1) and bottom-right (Label 2) of the image to select your " \ "cropping window." img_labels = zeros_like(self.img_in) launch_sagittal_dialog(self.img_in, img_labels, params) # Extract coordinates img_labels.change_orientation(native_orientation) cropping_coord = img_labels.getNonZeroCoordinates(sorting='value') # Since there is no cropping along the R-L direction, xmin/xmax are based on image dimension self.bbox.xmin, self.bbox.ymin, self.bbox.zmin = ( 0, min(cropping_coord[0].y, cropping_coord[1].y), min(cropping_coord[0].z, cropping_coord[1].z), ) self.bbox.xmax, self.bbox.ymax, self.bbox.zmax = ( img_labels.dim[0], max(cropping_coord[0].y, cropping_coord[1].y), max(cropping_coord[0].z, cropping_coord[1].z), ) # Put back input image in native orientation self.img_in.change_orientation(native_orientation)
def vertebral_detection(fname, fname_seg, contrast, param, init_disc, verbose=1, path_template='', initc2='auto', path_output='../'): """ Find intervertebral discs in straightened image using template matching :param fname: file name of straigthened spinal cord :param fname_seg: file name of straigthened spinal cord segmentation :param contrast: t1 or t2 :param param: advanced parameters :param init_disc: :param verbose: :param path_template: :param path_output: output path for verbose=2 pictures :return: """ sct.printv('\nLook for template...', verbose) sct.printv('Path template: ' + path_template, verbose) # adjust file names if MNI-Poly-AMU template is used (by default: PAM50) fname_level = get_file_label(os.path.join(path_template, 'template'), 'vertebral labeling', output='filewithpath') fname_template = get_file_label(os.path.join(path_template, 'template'), contrast.upper() + '-weighted template', output='filewithpath') # Open template and vertebral levels sct.printv('\nOpen template and vertebral levels...', verbose) data_template = Image(fname_template).data data_disc_template = Image(fname_level).data # open anatomical volume im_input = Image(fname) data = im_input.data # smooth data data = gaussian_filter(data, param.smooth_factor, output=None, mode="reflect") # get dimension of src nx, ny, nz = data.shape # define xc and yc (centered in the field of view) xc = int(np.round(nx / 2)) # direction RL yc = int(np.round(ny / 2)) # direction AP # get dimension of template nxt, nyt, nzt = data_template.shape # define xc and yc (centered in the field of view) xct = int(np.round(nxt / 2)) # direction RL yct = int(np.round(nyt / 2)) # direction AP # define mean distance (in voxel) between adjacent discs: [C1/C2 -> C2/C3], [C2/C3 -> C4/C5], ..., [L1/L2 -> L2/L3] centerline_level = data_disc_template[xct, yct, :] # attribute value to each disc. Starts from max level, then decrease. # NB: value 2 means disc C2/C3 (and so on and so forth). min_level = centerline_level[centerline_level.nonzero()].min() max_level = centerline_level[centerline_level.nonzero()].max() list_disc_value_template = list(range(min_level, max_level)) # add disc above top one list_disc_value_template.insert(int(0), min_level - 1) sct.printv('\nDisc values from template: ' + str(list_disc_value_template), verbose) # get diff to find transitions (i.e., discs) diff_centerline_level = np.diff(centerline_level) # get disc z-values list_disc_z_template = diff_centerline_level.nonzero()[0].tolist() list_disc_z_template.reverse() sct.printv('Z-values for each disc: ' + str(list_disc_z_template), verbose) list_distance_template = ( np.diff(list_disc_z_template) * (-1)).tolist() # multiplies by -1 to get positive distances sct.printv( 'Distances between discs (in voxel): ' + str(list_distance_template), verbose) # if manual mode, open viewer for user to click on C2/C3 disc if init_disc == [] and initc2 == 'manual': from spinalcordtoolbox.gui.base import AnatomicalParams from spinalcordtoolbox.gui.sagittal import launch_sagittal_dialog params = AnatomicalParams() params.num_points = 1 params.vertebraes = [ 3, ] params.subtitle = 'Click at the posterior tip of C2-C3 disc\n' input_file = Image(fname) output_file = msct_image.zeros_like(input_file) output_file.absolutepath = os.path.join(path_output, 'labels.nii.gz') controller = launch_sagittal_dialog(input_file, output_file, params) mask_points = controller.as_string() # assign new init_disc_z value # Note: there is a discrepancy between the label value (3) and the disc value (2). As of mid-2017, the SCT convention for disc C2-C3 is value=3. Before that it was value=2. init_disc = [int(mask_points.split(',')[2]), 2] # display init disc if verbose == 2: import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt # get percentile for automatic contrast adjustment data_display = np.mean(data[xc - param.size_RL:xc + param.size_RL, :, :], axis=0).transpose() percmin = np.percentile(data_display, 10) percmax = np.percentile(data_display, 90) # display image plt.matshow(data_display, fignum=50, cmap=plt.cm.gray, clim=[percmin, percmax], origin='lower') plt.title('Anatomical image') plt.autoscale( enable=False) # to prevent autoscale of axis when displaying plot plt.figure(50), plt.scatter(yc + param.shift_AP_visu, init_disc[0], c='yellow', s=50) plt.text(yc + param.shift_AP_visu + 4, init_disc[0], str(init_disc[1]) + '/' + str(init_disc[1] + 1), verticalalignment='center', horizontalalignment='left', color='pink', fontsize=15), plt.draw() # plt.ion() # enables interactive mode # FIND DISCS # =========================================================================== sct.printv('\nDetect intervertebral discs...', verbose) # assign initial z and disc current_z = init_disc[0] current_disc = init_disc[1] # create list for z and disc list_disc_z = [] list_disc_value = [] zrange = list(range(-10, 10)) direction = 'superior' search_next_disc = True while search_next_disc: sct.printv( 'Current disc: ' + str(current_disc) + ' (z=' + str(current_z) + '). Direction: ' + direction, verbose) try: # get z corresponding to current disc on template current_z_template = list_disc_z_template[current_disc] except: # in case reached the bottom (see issue #849) sct.printv( 'WARNING: Reached the bottom of the template. Stop searching.', verbose, 'warning') break # find next disc # N.B. Do not search for C1/C2 disc (because poorly visible), use template distance instead if current_disc != 1: current_z = compute_corr_3d(data, data_template, x=xc, xshift=0, xsize=param.size_RL, y=yc, yshift=param.shift_AP, ysize=param.size_AP, z=current_z, zshift=0, zsize=param.size_IS, xtarget=xct, ytarget=yct, ztarget=current_z_template, zrange=zrange, verbose=verbose, save_suffix='_disc' + str(current_disc), gaussian_std=999, path_output=path_output) # display new disc if verbose == 2: plt.figure(50), plt.scatter(yc + param.shift_AP_visu, current_z, c='yellow', s=50) plt.text(yc + param.shift_AP_visu + 4, current_z, str(current_disc) + '/' + str(current_disc + 1), verticalalignment='center', horizontalalignment='left', color='yellow', fontsize=15), plt.draw() # append to main list if direction == 'superior': # append at the beginning list_disc_z.insert(0, current_z) list_disc_value.insert(0, current_disc) elif direction == 'inferior': # append at the end list_disc_z.append(current_z) list_disc_value.append(current_disc) # adjust correcting factor based on already-identified discs if len(list_disc_z) > 1: # compute distance between already-identified discs list_distance_current = (np.diff(list_disc_z) * (-1)).tolist() # retrieve the template distance corresponding to the already-identified discs index_disc_identified = [ i for i, j in enumerate(list_disc_value_template) if j in list_disc_value[:-1] ] list_distance_template_identified = [ list_distance_template[i] for i in index_disc_identified ] # divide subject and template distances for the identified discs list_subject_to_template_distance = [ float(list_distance_current[i]) / list_distance_template_identified[i] for i in range(len(list_distance_current)) ] # average across identified discs to obtain an average correcting factor correcting_factor = np.mean(list_subject_to_template_distance) sct.printv('.. correcting factor: ' + str(correcting_factor), verbose) else: correcting_factor = 1 # update list_distance specific for the subject list_distance = [ int(np.round(list_distance_template[i] * correcting_factor)) for i in range(len(list_distance_template)) ] # assign new current_z and disc value if direction == 'superior': try: approx_distance_to_next_disc = list_distance[ list_disc_value_template.index(current_disc - 1)] except ValueError: sct.printv( 'WARNING: Disc value not included in template. Using previously-calculated distance: ' + str(approx_distance_to_next_disc)) # assign new current_z and disc value current_z = current_z + approx_distance_to_next_disc current_disc = current_disc - 1 elif direction == 'inferior': try: approx_distance_to_next_disc = list_distance[ list_disc_value_template.index(current_disc)] except: sct.printv( 'WARNING: Disc value not included in template. Using previously-calculated distance: ' + str(approx_distance_to_next_disc)) # assign new current_z and disc value current_z = current_z - approx_distance_to_next_disc current_disc = current_disc + 1 # if current_z is larger than searching zone, switch direction (and start from initial z minus approximate # distance from updated template distance) if current_z >= nz or current_disc == 0: sct.printv('.. Switching to inferior direction.', verbose) direction = 'inferior' current_disc = init_disc[1] + 1 current_z = init_disc[0] - list_distance[ list_disc_value_template.index(current_disc)] # if current_z is lower than searching zone, stop searching if current_z <= 0: search_next_disc = False # if upper disc is not 1, add disc above top disc based on mean_distance_adjusted upper_disc = min(list_disc_value) # if not upper_disc == 1: sct.printv( 'Adding top disc based on adjusted template distance: #' + str(upper_disc - 1), verbose) approx_distance_to_next_disc = list_distance[ list_disc_value_template.index(upper_disc - 1)] next_z = max(list_disc_z) + approx_distance_to_next_disc sct.printv('.. approximate distance: ' + str(approx_distance_to_next_disc), verbose) # make sure next disc does not go beyond FOV in superior direction if next_z > nz: list_disc_z.insert(0, nz) else: list_disc_z.insert(0, next_z) # assign disc value list_disc_value.insert(0, upper_disc - 1) # Label segmentation label_segmentation(fname_seg, list_disc_z, list_disc_value, verbose=verbose) # save figure if verbose == 2: plt.figure(50), plt.savefig( os.path.join(path_output, "fig_anat_straight_with_labels.png"))