def test_integrity(param_test): """ Test integrity of function """ fname_src = param_test.dict_args_with_path["-i"] fname_ref = param_test.dict_args_with_path["-d"] fname_dst = sct.add_suffix(os.path.basename(fname_src), "_reg") #fname_dst = "output.nii.gz" img_src = msct_image.Image(fname_src) img_ref = msct_image.Image(fname_ref) img_dst = msct_image.Image(fname_dst) if img_dst.orientation != img_ref.orientation: param_test.output += "\nImage has wrong orientation (%s -> %s)" \ % (img_ref.orientation, img_dst.orientation) param_test.status = 1 if len(img_src.data.shape) > 3: # Allowed failure for now return param_test if not (img_dst.data != 0).any(): param_test.output += "\nImage is garbage (all zeros)" param_test.status = 1 return param_test
def test_transfo_skip_pix2phys(): # "Recipe" useful if you want to skip pix2phys, which is *not* a good idea dir_tmp = "." print("Achieving a shifting of +1,+1,+1 (in LPI)") print(" Create data") path_src = "warp-src.nii" img_src = fake_3dimage_sct().save(path_src) print(" Create a warping field" \ """ Now we want to shift things by (+1,+1,+1), meaning that a destination voxel at position (x_1, y_1, z_1) contains the stuff that was in the source voxel at position (x_1-1, y_1-1, z_1-1). """) shape = tuple(list(img_src.data.shape) + [1,3]) data = np.ones(shape, order="F") data[...,2] *= -1 # invert Z so that the result is what we expect # See test_transfo_exhaustive_wrt_orientations() path_warp = "warp-field111.nii" img_warp = fake_image_sct_custom(data) img_warp.header.set_intent('vector', (), '') img_warp.save(path_warp) path_dst = "warp-dst111.nii" xform = sct_apply_transfo.Transform(path_src, path_warp, path_src, path_dst) xform.apply() img_src2 = msct_image.Image(path_src) img_dst = msct_image.Image(path_dst) print(" Check that the source wasn't altered") assert (img_src.data == img_src2.data).all() assert img_src.orientation == img_dst.orientation assert img_src.data.shape == img_dst.data.shape print(" Check the contents") dat_src = img_src.data dat_dst = np.array(img_dst.data) if 0: for idx_slice in range(shape[2]): print("") print(dat_src[...,idx_slice]) print(dat_dst[...,idx_slice]) # The volume should be shifted by 1 and the "lowest faces" of the destination # should be empty assert np.allclose(dat_dst[0,:,:], 0) assert np.allclose(dat_dst[:,0,:], 0) assert np.allclose(dat_dst[:,:,0], 0) assert np.allclose(dat_src[:-1,:-1,:-1], dat_dst[1:,1:,1:])
def main(): import numpy as np import spinalcordtoolbox.image as msct_image parser = get_parser() args = parser.parse_args(args=None if sys.argv[1:] else ['--help']) # Initialization fname_mt0 = '' fname_mt1 = '' fname_mtr = '' # register = param.register # remove_temp_files = param.remove_temp_files # verbose = param.verbose # Check input parameters fname_mt0 = args.mt0 fname_mt1 = args.mt1 fname_mtr = args.o verbose = args.v sct.init_sct(log_level=verbose, update=True) # Update log level # compute MTR sct.printv('\nCompute MTR...', verbose) nii_mt1 = msct_image.Image(fname_mt1) data_mt1 = nii_mt1.data data_mt0 = msct_image.Image(fname_mt0).data data_mtr = 100 * (data_mt0 - data_mt1) / data_mt0 # save MTR file nii_mtr = nii_mt1 nii_mtr.data = data_mtr nii_mtr.save(fname_mtr) # sct.run(fsloutput+'fslmaths -dt double mt0.nii -sub mt1.nii -mul 100 -div mt0.nii -thr 0 -uthr 100 fname_mtr', verbose) sct.display_viewer_syntax([fname_mt0, fname_mt1, fname_mtr])
def test_tolerance_of_affine_mismatch_check(): """Verify that affine mismatch error is thrown only for mismatches above a certain tolerance.""" # ERROR NOT EXPECTED (Affine matrices have slight differences, but are close enough to be equivalent) # NB: Specific values taken from anonymized data from https://github.com/neuropoly/spinalcordtoolbox/issues/3251 qform_affine = np.array([[-0.0000000613307, -0.0032542832702, -0.8999945527288, 36.8009071350098], [-0.9322916865349, -0.0000000613307, 0.0000000594134, 214.2190246582031], [0.0000000615451, -0.9322860067718, 0.0031415651366, 122.8873901367188], [0.0000000000000, 0.0000000000000, 0.0000000000000, 1.0000000000000]]) sform_affine = np.array([[-0.0000000000007, -0.0032542543486, -0.8999945521355, 36.8009071350098], [-0.9322916865349, 0.0000000001912, -0.0000000000000, 214.2190246582031], [-0.0000000001912, -0.9322860240936, 0.0031415862031, 122.8873901367188], [0.0000000000000, 0.0000000000000, 0.0000000000000, 1.0000000000000]]) header_e7 = nibabel.Nifti1Header() header_e7.set_sform(affine=sform_affine) header_e7.set_qform(affine=qform_affine) msct_image.Image(param=[1, 1, 1], hdr=header_e7, check_sform=True) # ERROR NOT EXPECTED (A bigger discrepancy is introduced, but it doesn't exceed the tolerance of the check) qform_affine_e3 = qform_affine.copy() qform_affine_e3[0, 0] += 1e-3 header_e3 = nibabel.Nifti1Header() header_e3.set_sform(affine=sform_affine) header_e3.set_qform(affine=qform_affine_e3) msct_image.Image(param=[1, 1, 1], hdr=header_e3, check_sform=True) # ERROR EXPECTED (A bigger discrepancy is introduced, and it does exceed the tolerance of the check) qform_affine_e2 = qform_affine.copy() qform_affine_e2[0, 0] += 1e-2 header_e2 = nibabel.Nifti1Header() header_e2.set_sform(affine=sform_affine) header_e2.set_qform(affine=qform_affine_e2) with pytest.raises(ValueError) as e: msct_image.Image(param=[1, 1, 1], hdr=header_e2, check_sform=True) assert "Image sform does not match qform" in str(e.value)
def label_vert(fname_seg, fname_label, verbose=1): """ Label segmentation using vertebral labeling information. No orientation expected. :param fname_seg: file name of segmentation. :param fname_label: file name for a labelled segmentation that will be used to label the input segmentation :param fname_out: file name of the output labeled segmentation. If empty, will add suffix "_labeled" to fname_seg :param verbose: :return: """ # Open labels im_disc = msct_image.Image(fname_label).change_orientation("RPI") # retrieve all labels coord_label = im_disc.getNonZeroCoordinates() # compute list_disc_z and list_disc_value list_disc_z = [] list_disc_value = [] for i in range(len(coord_label)): list_disc_z.insert(0, coord_label[i].z) # '-1' to use the convention "disc labelvalue=3 ==> disc C2/C3" list_disc_value.insert(0, coord_label[i].value - 1) list_disc_value = [x for (y, x) in sorted(zip(list_disc_z, list_disc_value), reverse=True)] list_disc_z = [y for (y, x) in sorted(zip(list_disc_z, list_disc_value), reverse=True)] # label segmentation from sct_label_vertebrae import label_segmentation label_segmentation(fname_seg, list_disc_z, list_disc_value, verbose=verbose)
def compute_shape(segmentation, algo_fitting='hanning', window_length=50, remove_temp_files=1, verbose=1): """ This function characterizes the shape of the spinal cord, based on the segmentation Shape properties are computed along the spinal cord and averaged per z-slices. Option is to provide intervertebral disks to average shape properties over vertebral levels (fname_discs). WARNING: the segmentation needs to be binary. :param segmentation: input segmentation. Could be either an Image or a file name. :param algo_fitting: :param window_length: :param remove_temp_files: :param verbose: :return metrics: Dict of class Metric() """ im_seg = msct_image.Image(segmentation).change_orientation('RPI') # Extract min and max index in Z direction data_seg = im_seg.data X, Y, Z = (data_seg > 0).nonzero() # min_z_index, max_z_index = min(Z), max(Z) shape_properties = msct_shape.compute_properties_along_centerline(im_seg=im_seg, smooth_factor=0.0, interpolation_mode=0, algo_fitting=algo_fitting, window_length=window_length, remove_temp_files=remove_temp_files, verbose=verbose) metrics = {} for key, value in shape_properties.items(): # Making sure all entries added to metrics have results if not value == []: metrics[key] = Metric(data=np.array(value), label=key) return metrics
def test_transfo_null(): dir_tmp = "." print("Null warping field") print(" Create some recognizable data") data = np.ones((7, 8, 9), order="F") path_src = "warp-src.nii" img_src = fake_image_sct_custom(data).save(path_src) print(" Create a null warping field") data = np.zeros((7, 8, 9, 1, 3), order="F") path_warp = "warp-field.nii" img_warp = fake_image_sct_custom(data) img_warp.header.set_intent('vector', (), '') img_warp.save(path_warp) print(" Apply") path_dst = "warp-dst.nii" xform = sct_apply_transfo.Transform(input_filename=path_src, fname_dest=path_src, list_warp=[path_warp], output_filename=path_dst) xform.apply() img_src2 = msct_image.Image(path_src) img_dst = msct_image.Image(path_dst) print(" Check that the source wasn't altered") assert (img_src.data == img_src2.data).all() assert img_src.orientation == img_dst.orientation assert img_src.data.shape == img_dst.data.shape print( " Check that the destination is identical to the source since the field was null" ) dat_src = img_src.data dat_dst = np.array(img_dst.data) for idx_slice, (slice_src, slice_dst) in enumerate( msct_image.SlicerMany((img_src, img_dst), msct_image.Slicer)): slice_src = np.array(slice_src) slice_dst = np.array(slice_dst) print(slice_src) print(slice_dst) assert np.allclose(slice_src, slice_dst) assert np.allclose(dat_src, dat_dst)
def convert(fname_in, fname_out, squeeze_data=True, dtype=None, verbose=1): """ Convert data :return True/False """ printv('sct_convert -i ' + fname_in + ' -o ' + fname_out, verbose, 'code') img = image.Image(fname_in) img = image.convert(img, squeeze_data=squeeze_data, dtype=dtype) img.save(fname_out, mutable=True, verbose=verbose)
def fake_3dimage_sct_custom(data): """ :return: an Image (3D) in RAS+ (aka SCT LPI) space """ i = fake_3dimage_custom(data) img = msct_image.Image(i.get_data(), hdr=i.header, orientation="LPI", dim=i.header.get_data_shape(), ) return img
def convert(fname_in, fname_out, squeeze_data=True, dtype=None, verbose=1): """ Convert data :return True/False """ import spinalcordtoolbox.image as msct_image printv('sct_convert -i ' + fname_in + ' -o ' + fname_out, verbose, 'code') im = msct_image.Image(fname_in) if squeeze_data: im.data = np.squeeze(im.data) if dtype: im.change_type(dtype) im.save(fname_out, mutable=True, verbose=verbose) return im
def test_change_shape(fake_3dimage_sct): # Add dimension im_src = fake_3dimage_sct shape = tuple(list(im_src.data.shape) + [1]) im_dst = msct_image.change_shape(im_src, shape) path_tmp = tmp_create(basename="test_reshape") src_path = os.path.join(path_tmp, "src.nii") dst_path = os.path.join(path_tmp, "dst.nii") im_src.save(src_path) im_dst.save(dst_path) im_src = msct_image.Image(src_path) im_dst = msct_image.Image(dst_path) assert im_dst.data.shape == shape data_src = im_src.data data_dst = im_dst.data assert (data_dst.reshape(data_src.shape) == data_src).all() # Remove dimension im_dst = im_dst.change_shape(im_src.data.shape) assert im_dst.data.shape == im_src.data.shape
def generate_consensus_reading(fname_raterMasks_lst, threshold=0.5, fname_out='./consensus_reading.nii.gz'): ''' This function generate a consensus reading mask from several segmentation mask of the same raw image. In the generated mask, 1 indicates that this voxel is part of the consensus reading, 0 otherwise. Input: - fname_raterMasks_lst [list]: list of nifti binary mask filenames. Note that they are suspected to all have the same size, since they are all the mask of the same raw image. - threshold [float]: value between 0. and 1. A voxel is labelled 1 in the output consensus mask if [threshold] of the raters segmented this voxel as lesion. - fname_out [string]: filename of the consensus reading mask generated. ''' # compute the number of agreement needed to consider a voxel as part of the consensus reading mask threshold_n = int(threshold * len(fname_raterMasks_lst)) # Sum all the rater segmentations data_sum = np.sum(np.asarray([ msct_image.Image(fname_raterMask).data for fname_raterMask in fname_raterMasks_lst ]), axis=0) # Threshold the resulting array according to the threshold data_thresh = np.zeros(data_sum.shape) data_thresh[np.where(data_sum >= threshold_n)] = 1 # Save the resulting array as Image in the related ofolder im_consensus = msct_image.zeros_like(msct_image.Image( fname_raterMasks_lst[0]), dtype=np.uint8) im_consensus.data = data_thresh im_consensus.save(fname_out)
def find_maxVert_perScan(data_folder): ''' Opens each labelled image and find the max vertebral level available. Return a dictionary with the scan_name as key, the max vert as value. ''' scan_max_vert_dct = {} for scan_foldname in os.listdir(data_folder): path_labeled_cord_img = os.path.join( data_folder, scan_foldname, scan_foldname + '_seg_manual_labeled.nii.gz') if not os.path.isfile(path_labeled_cord_img): path_labeled_cord_img = os.path.join( data_folder, scan_foldname, scan_foldname + '_seg_labeled.nii.gz') if os.path.isfile(path_labeled_cord_img): scan_max_vert_dct[scan_foldname] = np.max( np.unique(msct_image.Image(path_labeled_cord_img).data)) return scan_max_vert_dct
def main(argv=None): """ Main function :param args: :return: """ parser = get_parser() arguments = parser.parse_args(argv) verbose = arguments.v set_global_loglevel(verbose=verbose) # Building the command, do sanity checks fname_in = arguments.i fname_out = arguments.o squeeze_data = bool(arguments.squeeze) # convert file img = image.Image(fname_in) img = image.convert(img, squeeze_data=squeeze_data) img.save(fname_out, mutable=True, verbose=verbose)
def compute_csa(segmentation, algo_fitting='bspline', angle_correction=True, use_phys_coord=True, remove_temp_files=1, verbose=1): """ Compute CSA. Note: segmentation can be binary or weighted for partial volume effect. :param segmentation: input segmentation. Could be either an Image or a file name. :param algo_fitting: :param angle_correction: :param use_phys_coord: :return metrics: Dict of class process_seg.Metric() """ # create temporary folder path_tmp = sct.tmp_create() # open image and save in temp folder im_seg = msct_image.Image(segmentation).save(path_tmp, ) # change orientation to RPI im_seg.change_orientation('RPI') nx, ny, nz, nt, px, py, pz, pt = im_seg.dim fname_seg = os.path.join(path_tmp, 'segmentation_RPI.nii.gz') im_seg.save(fname_seg) # Extract min and max index in Z direction data_seg = im_seg.data X, Y, Z = (data_seg > 0).nonzero() min_z_index, max_z_index = min(Z), max(Z) # if angle correction is required, get segmentation centerline # Note: even if angle_correction=0, we should run the code below so that z_centerline_voxel is defined (later used # with option -vert). See #1791 # TODO: check if we need use_phys_coord case with recent changes in centerline if use_phys_coord: # fit centerline, smooth it and return the first derivative (in physical space) _, arr_ctl, arr_ctl_der = get_centerline(im_seg, algo_fitting=algo_fitting, verbose=verbose) x_centerline_fit, y_centerline_fit, z_centerline = arr_ctl x_centerline_deriv, y_centerline_deriv, z_centerline_deriv = arr_ctl_der centerline = Centerline(x_centerline_fit.tolist(), y_centerline_fit.tolist(), z_centerline.tolist(), x_centerline_deriv.tolist(), y_centerline_deriv.tolist(), z_centerline_deriv.tolist()) # average centerline coordinates over slices of the image x_centerline_fit_rescorr, y_centerline_fit_rescorr, z_centerline_rescorr, x_centerline_deriv_rescorr, \ y_centerline_deriv_rescorr, z_centerline_deriv_rescorr = centerline.average_coordinates_over_slices(im_seg) # compute Z axis of the image, in physical coordinate axis_X, axis_Y, axis_Z = im_seg.get_directions() else: # fit centerline, smooth it and return the first derivative (in voxel space but FITTED coordinates) _, arr_ctl, arr_ctl_der = get_centerline(im_seg, algo_fitting=algo_fitting, verbose=verbose) x_centerline_fit, y_centerline_fit, z_centerline = arr_ctl x_centerline_deriv, y_centerline_deriv, z_centerline_deriv = arr_ctl_der # # correct centerline fitted coordinates according to the data resolution # x_centerline_fit_rescorr, y_centerline_fit_rescorr, z_centerline_rescorr, \ # x_centerline_deriv_rescorr, y_centerline_deriv_rescorr, z_centerline_deriv_rescorr = \ # x_centerline_fit * px, y_centerline_fit * py, z_centerline * pz, \ # x_centerline_deriv * px, y_centerline_deriv * py, z_centerline_deriv * pz # # axis_Z = [0.0, 0.0, 1.0] # Compute CSA sct.printv('\nCompute CSA...', verbose) # Initialize 1d array with nan. Each element corresponds to a slice. csa = np.full_like(np.empty(nz), np.nan, dtype=np.double) angles = np.full_like(np.empty(nz), np.nan, dtype=np.double) for iz in range(min_z_index, max_z_index + 1): if angle_correction: # in the case of problematic segmentation (e.g., non continuous segmentation often at the extremities), # display a warning but do not crash try: # normalize the tangent vector to the centerline (i.e. its derivative) tangent_vect = normalize( np.array([ x_centerline_deriv[iz - min_z_index] * px, y_centerline_deriv[iz - min_z_index] * py, pz ])) except IndexError: sct.printv( 'WARNING: Your segmentation does not seem continuous, which could cause wrong estimations at the ' 'problematic slices. Please check it, especially at the extremities.', type='warning') # compute the angle between the normal vector of the plane and the vector z angle = np.arccos(np.vdot(tangent_vect, np.array([0, 0, 1]))) else: angle = 0.0 # compute the number of voxels, assuming the segmentation is coded for partial volume effect between 0 and 1. number_voxels = np.sum(data_seg[:, :, iz]) # compute CSA, by scaling with voxel size (in mm) and adjusting for oblique plane csa[iz] = number_voxels * px * py * np.cos(angle) angles[iz] = math.degrees(angle) # Remove temporary files if remove_temp_files: sct.printv('\nRemove temporary files...') sct.rmtree(path_tmp) # prepare output metrics = { 'csa': Metric(data=csa, label='CSA [mm^2]'), 'angle': Metric(data=angles, label='Angle between cord axis and z [deg]') } return metrics
def merge_images(list_fname_src, fname_dest, list_fname_warp, param): """ Merge multiple source images onto destination space. All images are warped to the destination space and then added. To deal with overlap during merging (e.g. one voxel in destination image is shared with two input images), the resulting voxel is divided by the sum of the partial volume of each image. For example, if src(x,y,z)=1 is mapped to dest(i,j,k) with a partial volume of 0.5 (because destination voxel is bigger), then its value after linear interpolation will be 0.5. To account for partial volume, the resulting voxel will be: dest(i,j,k) = 0.5*0.5/0.5 = 0.5. Now, if two voxels overlap in the destination space, let's say: src(x,y,z)=1 and src2'(x',y',z')=1, then the resulting value will be: dest(i,j,k) = (0.5*0.5 + 0.5*0.5) / (0.5+0.5) = 0.5. So this function acts like a weighted average operator, only in destination voxels that share multiple source voxels. Parameters ---------- list_fname_src fname_dest list_fname_warp param Returns ------- """ # create temporary folder path_tmp = sct.tmp_create() # get dimensions of destination file nii_dest = msct_image.Image(fname_dest) # initialize variables data = np.zeros([ nii_dest.dim[0], nii_dest.dim[1], nii_dest.dim[2], len(list_fname_src) ]) partial_volume = np.zeros([ nii_dest.dim[0], nii_dest.dim[1], nii_dest.dim[2], len(list_fname_src) ]) data_merge = np.zeros([nii_dest.dim[0], nii_dest.dim[1], nii_dest.dim[2]]) # loop across files i_file = 0 for fname_src in list_fname_src: # apply transformation src --> dest sct_apply_transfo.main(args=[ '-i', fname_src, '-d', fname_dest, '-w', list_fname_warp[i_file], '-x', param.interp, '-o', 'src_' + str(i_file) + '_template.nii.gz', '-v', param.verbose ]) # create binary mask from input file by assigning one to all non-null voxels sct_maths.main(args=[ '-i', fname_src, '-bin', str(param.almost_zero), '-o', 'src_' + str(i_file) + 'native_bin.nii.gz' ]) # apply transformation to binary mask to compute partial volume sct_apply_transfo.main(args=[ '-i', 'src_' + str(i_file) + 'native_bin.nii.gz', '-d', fname_dest, '-w', list_fname_warp[i_file], '-x', param.interp, '-o', 'src_' + str(i_file) + '_template_partialVolume.nii.gz' ]) # open data data[:, :, :, i_file] = msct_image.Image('src_' + str(i_file) + '_template.nii.gz').data partial_volume[:, :, :, i_file] = msct_image.Image( 'src_' + str(i_file) + '_template_partialVolume.nii.gz').data i_file += 1 # merge files using partial volume information (and convert nan resulting from division by zero to zeros) data_merge = np.divide(np.sum(data * partial_volume, axis=3), np.sum(partial_volume, axis=3)) data_merge = np.nan_to_num(data_merge) # write result in file nii_dest.data = data_merge nii_dest.save(param.fname_out) # remove temporary folder if param.rm_tmp: sct.rmtree(path_tmp)
def extract_metrics_scans_asPanda(data_folder, scan_max_vert_dct, shape_metrics_lst): ''' Loops across each scan results and extract the values, per vertebral level. Returns a list of panda dataframes, one per scan with the following column: z_idx, lvl, csa and the shape_metrics_lst ''' scan_results_pd_lst = [] for scan_foldname, max_vert in sorted(scan_max_vert_dct.items(), key=operator.itemgetter(1)): path_scan_folder = os.path.join(data_folder, scan_foldname) if os.path.isdir(path_scan_folder): path_labeled_cord_img = os.path.join( path_scan_folder, scan_foldname + '_seg_manual_labeled.nii.gz') if not os.path.isfile(path_labeled_cord_img): path_labeled_cord_img = os.path.join( path_scan_folder, scan_foldname + '_seg_labeled.nii.gz') path_shape_csv = os.path.join( path_scan_folder, 'metrics', scan_foldname + '_seg_manual_shape.csv') if not os.path.isfile(path_shape_csv): path_shape_csv = os.path.join(path_scan_folder, 'metrics', scan_foldname + '_seg_shape.csv') path_csa_pickle = os.path.join(path_scan_folder, 'metrics', 'csa_per_slice.pickle') if os.path.isfile(path_labeled_cord_img) and os.path.isfile( path_shape_csv) and os.path.isfile(path_csa_pickle): labeled_cord_img = msct_image.Image(path_labeled_cord_img) shape_pd = pd.read_csv(path_shape_csv)[[u'Unnamed: 0'] + shape_metrics_lst] shape_pd = shape_pd.rename(columns={u'Unnamed: 0': 'z_idx'}) csa_pd = pd.read_pickle(path_csa_pickle)[[ u'CSA (mm^2)', u'Slice (z)' ]] csa_pd = csa_pd.rename(columns={ u'Slice (z)': 'z_idx', u'CSA (mm^2)': 'csa' }) scan_results_tmp_dct = {} for m in ['z_idx', 'csa', 'lvl'] + shape_metrics_lst: scan_results_tmp_dct[m] = [] scan_results_tmp_pd = pd.DataFrame.from_dict( scan_results_tmp_dct) for zz in range(labeled_cord_img.dim[1]): lvl = np.max(np.unique(labeled_cord_img.data[:, zz, :])) if lvl: idx_tmp = len(scan_results_tmp_pd.index.values) scan_results_tmp_pd.loc[idx_tmp, 'z_idx'] = zz scan_results_tmp_pd.loc[idx_tmp, 'csa'] = csa_pd[ csa_pd.z_idx == zz].csa.values[0] scan_results_tmp_pd.loc[idx_tmp, 'lvl'] = lvl for m_s in shape_metrics_lst: if len(shape_pd[shape_pd.z_idx == zz][m_s].values): scan_results_tmp_pd.loc[ idx_tmp, m_s] = shape_pd[shape_pd.z_idx == zz][m_s].values[0] scan_results_pd_lst.append(scan_results_tmp_pd) if max_vert == max(scan_max_vert_dct.values()): break return scan_results_pd_lst
def test_more_change_orientation(fake_3dimage_sct, fake_3dimage_sct_vis): path_tmp = tmp_create(basename="test_reorient") path_tmp = "." im_src = fake_3dimage_sct.copy() im_src.save(os.path.join(path_tmp, "src.nii"), mutable=True) print(im_src.orientation, im_src.data.shape) def orient2shape(orient): # test-data-specific thing letter2dim = dict( L=7, R=7, A=8, P=8, I=9, S=9, ) return tuple([letter2dim[x] for x in orient]) orientation = im_src.orientation # LPI assert im_src.header.get_best_affine()[:3, 3].tolist() == [0, 0, 0] im_dst = msct_image.change_orientation(im_src, "RPI") print(im_dst.orientation, im_dst.data.shape) assert im_dst.data.shape == orient2shape("RPI") assert im_dst.header.get_best_affine()[:3, 3].tolist() == [7 - 1, 0, 0] # spot check orientation = im_src.orientation # LPI im_dst = msct_image.change_orientation(im_src, "IRP") print(im_dst.orientation, im_dst.data.shape) assert im_dst.data.shape == orient2shape("IRP") # to & fro im_dst2 = msct_image.change_orientation(im_dst, orientation) print(im_dst2.orientation, im_dst2.data.shape) assert im_dst2.orientation == im_src.orientation assert im_dst2.data.shape == orient2shape(orientation) assert (im_dst2.data == im_src.data).all() assert np.allclose(im_src.header.get_best_affine(), im_dst2.header.get_best_affine()) #fn = os.path.join(path_tmp, "pouet.nii") im_ref = fake_3dimage_sct.copy() im_src = fake_3dimage_sct.copy() orientation = im_src.orientation im_src.change_orientation("ASR").change_orientation(orientation) assert im_src.orientation == im_ref.orientation assert (im_dst2.data == im_src.data).all() assert np.allclose(im_src.header.get_best_affine(), im_ref.header.get_best_affine()) im_dst2 = msct_image.change_orientation(im_dst, orientation) print(im_dst2.orientation, im_dst2.data.shape) assert im_dst2.orientation == im_src.orientation assert im_dst2.data.shape == orient2shape(orientation) assert (im_dst2.data == im_src.data).all() assert np.allclose(im_src.header.get_best_affine(), im_dst2.header.get_best_affine()) # copy im_dst = im_src.copy().change_orientation("IRP") assert im_dst.data.shape == orient2shape("IRP") print(im_dst.orientation, im_dst.data.shape) print("Testing orientation persistence") img = im_src.copy() orientation = img.orientation fn = os.path.join(path_tmp, "pouet.nii") img.change_orientation("PIR").save(fn) assert img.data.shape == orient2shape("PIR") img = msct_image.Image(fn) assert img.orientation == "PIR" assert img.data.shape == orient2shape("PIR") print(img.orientation, img.data.shape) # typical pattern img = fake_3dimage_sct_vis.copy() print(img.header.get_best_affine()) orientation = img.orientation path_tmp = "." fn = os.path.join(path_tmp, "vis.nii") fn2 = img.save(fn, mutable=True).change_orientation( "ALS", generate_path=True).save().absolutepath img = msct_image.Image(fn2) assert img.orientation == "ALS" assert img.data.shape == orient2shape("ALS") print(img.header.get_best_affine()) fn2 = img.save(fn, mutable=True).change_orientation( "RAS", generate_path=True).save().absolutepath img = msct_image.Image(fn2) assert img.orientation == "RAS" assert img.data.shape == orient2shape("RAS") print(img.header.get_best_affine()) fn2 = img.save(fn, mutable=True).change_orientation( "RPI", generate_path=True).save().absolutepath img = msct_image.Image(fn2) assert img.orientation == "RPI" assert img.data.shape == orient2shape("RPI") print(img.header.get_best_affine()) fn2 = img.save(fn, mutable=True).change_orientation( "PLI", generate_path=True).save().absolutepath img = msct_image.Image(fn2) assert img.orientation == "PLI" assert img.data.shape == orient2shape("PLI") print(img.header.get_best_affine()) # print(src.header) possibilities = [ "ASR", "SRA", "RAS", ] possibilities = msct_image.all_refspace_strings() for orientation in possibilities: dst = msct_image.change_orientation(im_src, orientation) # dst.save("pouet-{}.nii".format(dst.orientation)) print(orientation, dst.orientation, dst.data.shape, dst.dim) assert orientation == dst.orientation
def test_transfo_figure_out_ants_frame_exhaustive(): dir_tmp = "." all_orientations = msct_image.all_refspace_strings() #all_orientations = ("LPS", "LPI") print("Wondering which orientation is native to ANTs") working_orientations = [] # there can't be only one... for orientation in all_orientations: print(" Shifting +1,+1,+1 (in {})".format(orientation)) path_src = "warp-{}-src.nii".format(orientation) img_src = fake_3dimage_sct().change_orientation(orientation).save(path_src) # Create warping field shape = tuple(list(img_src.data.shape) + [1,3]) data = np.ones(shape, order="F") path_warp = "warp-{}-field.nii".format(orientation) img_warp = fake_image_sct_custom(data) img_warp.header.set_intent('vector', (), '') img_warp.change_orientation(orientation).save(path_warp) print(" Affine:\n{}".format(img_warp.header.get_best_affine())) path_dst = "warp-{}-dst.nii".format(orientation) xform = sct_apply_transfo.Transform(path_src, path_warp, path_src, path_dst) xform.apply() img_src2 = msct_image.Image(path_src) img_dst = msct_image.Image(path_dst) assert img_src.orientation == img_dst.orientation assert img_src.data.shape == img_dst.data.shape dat_src = img_src.data dat_dst = np.array(img_dst.data) value = 222 aff_src = img_dst.header.get_best_affine() aff_dst = img_dst.header.get_best_affine() pt_src = np.array(np.unravel_index(np.argmin(np.abs(dat_src - value)), dat_src.shape))#, order="F")) pt_dst = np.array(np.unravel_index(np.argmin(np.abs(dat_dst - value)), dat_dst.shape))#, order="F")) print("Point %s -> %s" % (pt_src, pt_dst)) pos_src = np.matmul(aff_src, np.hstack((pt_src, [1])).reshape((4,1))) pos_dst = np.matmul(aff_dst, np.hstack((pt_dst, [1])).reshape((4,1))) displacement = (pos_dst - pos_src).T[:3] print("Displacement (physical): %s" % (displacement)) displacement = pt_dst - pt_src print("Displacement (logical): %s" % (displacement)) assert dat_src.shape == dat_dst.shape if 0: for idx_slice in range(9): print(dat_src[...,idx_slice]) print(dat_dst[...,idx_slice]) print("") try: # Check same as before assert np.allclose(dat_dst[0,:,:], 0) assert np.allclose(dat_dst[:,0,:], 0) assert np.allclose(dat_dst[:,:,0], 0) assert np.allclose(dat_src[:-1,:-1,:-1], dat_dst[1:,1:,1:]) working_orientations.append(orientation) except AssertionError as e: continue print("\x1B[31;1m Failed in {}\x1B[0m".format(orientation)) for idx_slice in range(shape[2]): print(dat_src[...,idx_slice]) print(dat_dst[...,idx_slice]) print("") print("-> Working orientation: {}".format(" ".join(working_orientations)))
def main(args=None): # Initialization # fname_anat = '' # fname_centerline = '' sigma = 3 # default value of the standard deviation for the Gaussian smoothing (in terms of number of voxels) param = Param() # remove_temp_files = param.remove_temp_files # verbose = param.verbose start_time = time.time() parser = get_parser() arguments = parser.parse(sys.argv[1:]) fname_anat = arguments['-i'] fname_centerline = arguments['-s'] if '-smooth' in arguments: sigma = arguments['-smooth'] if '-param' in arguments: param.update(arguments['-param']) if '-r' in arguments: remove_temp_files = int(arguments['-r']) if '-v' in arguments: verbose = int(arguments['-v']) # Display arguments sct.printv('\nCheck input arguments...') sct.printv(' Volume to smooth .................. ' + fname_anat) sct.printv(' Centerline ........................ ' + fname_centerline) sct.printv(' Sigma (mm) ........................ ' + str(sigma)) sct.printv(' Verbose ........................... ' + str(verbose)) # Check that input is 3D: from spinalcordtoolbox.image import Image nx, ny, nz, nt, px, py, pz, pt = Image(fname_anat).dim dim = 4 # by default, will be adjusted later if nt == 1: dim = 3 if nz == 1: dim = 2 if dim == 4: sct.printv( 'WARNING: the input image is 4D, please split your image to 3D before smoothing spinalcord using :\n' 'sct_image -i ' + fname_anat + ' -split t -o ' + fname_anat, verbose, 'warning') sct.printv('4D images not supported, aborting ...', verbose, 'error') # Extract path/file/extension path_anat, file_anat, ext_anat = sct.extract_fname(fname_anat) path_centerline, file_centerline, ext_centerline = sct.extract_fname( fname_centerline) path_tmp = sct.tmp_create(basename="smooth_spinalcord", verbose=verbose) # Copying input data to tmp folder sct.printv('\nCopying input data to tmp folder and convert to nii...', verbose) sct.copy(fname_anat, os.path.join(path_tmp, "anat" + ext_anat)) sct.copy(fname_centerline, os.path.join(path_tmp, "centerline" + ext_centerline)) # go to tmp folder curdir = os.getcwd() os.chdir(path_tmp) # convert to nii format convert('anat' + ext_anat, 'anat.nii') convert('centerline' + ext_centerline, 'centerline.nii') # Change orientation of the input image into RPI sct.printv('\nOrient input volume to RPI orientation...') fname_anat_rpi = msct_image.Image("anat.nii") \ .change_orientation("RPI", generate_path=True) \ .save() \ .absolutepath # Change orientation of the input image into RPI sct.printv('\nOrient centerline to RPI orientation...') fname_centerline_rpi = msct_image.Image("centerline.nii") \ .change_orientation("RPI", generate_path=True) \ .save() \ .absolutepath # Straighten the spinal cord # straighten segmentation sct.printv('\nStraighten the spinal cord using centerline/segmentation...', verbose) cache_sig = sct.cache_signature( input_files=[fname_anat_rpi, fname_centerline_rpi], input_params={"x": "spline"}, ) cachefile = os.path.join(curdir, "straightening.cache") if sct.cache_valid(cachefile, cache_sig) and os.path.isfile( os.path.join( curdir, 'warp_curve2straight.nii.gz')) and os.path.isfile( os.path.join( curdir, 'warp_straight2curve.nii.gz')) and os.path.isfile( os.path.join(curdir, 'straight_ref.nii.gz')): # if they exist, copy them into current folder sct.printv('Reusing existing warping field which seems to be valid', verbose, 'warning') sct.copy(os.path.join(curdir, 'warp_curve2straight.nii.gz'), 'warp_curve2straight.nii.gz') sct.copy(os.path.join(curdir, 'warp_straight2curve.nii.gz'), 'warp_straight2curve.nii.gz') sct.copy(os.path.join(curdir, 'straight_ref.nii.gz'), 'straight_ref.nii.gz') # apply straightening sct.run([ 'sct_apply_transfo', '-i', fname_anat_rpi, '-w', 'warp_curve2straight.nii.gz', '-d', 'straight_ref.nii.gz', '-o', 'anat_rpi_straight.nii', '-x', 'spline' ], verbose) else: sct.run([ 'sct_straighten_spinalcord', '-i', fname_anat_rpi, '-o', 'anat_rpi_straight.nii', '-s', fname_centerline_rpi, '-x', 'spline', '-param', 'algo_fitting=' + param.algo_fitting ], verbose) sct.cache_save(cachefile, cache_sig) # Smooth the straightened image along z sct.printv('\nSmooth the straightened image along z...') sct.run([ 'sct_maths', '-i', 'anat_rpi_straight.nii', '-smooth', '0,0,' + str(sigma), '-o', 'anat_rpi_straight_smooth.nii' ], verbose) # Apply the reversed warping field to get back the curved spinal cord sct.printv( '\nApply the reversed warping field to get back the curved spinal cord...' ) sct.run([ 'sct_apply_transfo', '-i', 'anat_rpi_straight_smooth.nii', '-o', 'anat_rpi_straight_smooth_curved.nii', '-d', 'anat.nii', '-w', 'warp_straight2curve.nii.gz', '-x', 'spline' ], verbose) # replace zeroed voxels by original image (issue #937) sct.printv('\nReplace zeroed voxels by original image...', verbose) nii_smooth = Image('anat_rpi_straight_smooth_curved.nii') data_smooth = nii_smooth.data data_input = Image('anat.nii').data indzero = np.where(data_smooth == 0) data_smooth[indzero] = data_input[indzero] nii_smooth.data = data_smooth nii_smooth.save('anat_rpi_straight_smooth_curved_nonzero.nii') # come back os.chdir(curdir) # Generate output file sct.printv('\nGenerate output file...') sct.generate_output_file( os.path.join(path_tmp, "anat_rpi_straight_smooth_curved_nonzero.nii"), file_anat + '_smooth' + ext_anat) # Remove temporary files if remove_temp_files == 1: sct.printv('\nRemove temporary files...') sct.rmtree(path_tmp) # Display elapsed time elapsed_time = time.time() - start_time sct.printv('\nFinished! Elapsed time: ' + str(int(np.round(elapsed_time))) + 's\n') sct.display_viewer_syntax([file_anat, file_anat + '_smooth'], verbose=verbose)
def notest_transfo_more_exhaustive_wrt_orientations(): dir_tmp = "." print("Figuring out which orientations work without workaround") all_orientations = msct_image.all_refspace_strings() orientations_ok = [] orientations_ng = [] orientations_dk = [] for orientation_src in all_orientations: for orientation_ref in all_orientations: shift = np.array([1,2,3]) shift_wanted = shift.copy() shift[2] *= -1 # ANTs / ITK reference frame is LPS, ours is LPI # (see docs or test_transfo_figure_out_ants_frame_exhaustive()) print(" Shifting {} in {} ref {}".format(shift_wanted, orientation_src, orientation_ref)) path_src = "warp2-{}.nii".format(orientation_src) img_src = fake_3dimage_sct().change_orientation(orientation_src).save(path_src) path_ref = "warp2-{}.nii".format(orientation_ref) img_ref = fake_3dimage_sct().change_orientation(orientation_ref).save(path_ref) # Create warping field shape = tuple(list(img_src.data.shape) + [1,3]) data = np.zeros(shape, order="F") data[:,:,:,0] = shift path_warp = "warp-{}-{}-field.nii".format(orientation_src, orientation_ref) img_warp = fake_image_sct_custom(data) img_warp.header.set_intent('vector', (), '') img_warp.change_orientation(orientation_ref).save(path_warp) #print(" Affine:\n{}".format(img_warp.header.get_best_affine())) path_dst = "warp-{}-{}-dst.nii".format(orientation_src, orientation_ref) xform = sct_apply_transfo.Transform(path_src, path_warp, path_ref, path_dst) xform.apply() img_src2 = msct_image.Image(path_src) img_dst = msct_image.Image(path_dst) assert img_ref.orientation == img_dst.orientation assert img_ref.data.shape == img_dst.data.shape dat_src = img_src.data dat_dst = np.array(img_dst.data) value = 50505 aff_src = img_src.header.get_best_affine() aff_dst = img_dst.header.get_best_affine() pt_src = np.argwhere(dat_src == value)[0] try: pt_dst = np.argwhere(dat_dst == value)[0] 1/0 except: # Work around numerical inaccuracy, that is somehow introduced by ANTs min_ = np.round(np.min(np.abs(dat_dst - value)), 1) pt_dst = np.array(np.unravel_index(np.argmin(np.abs(dat_dst - value)), dat_dst.shape))#, order="F")) print(" Point %s -> %s (%s) %s" % (pt_src, pt_dst, dat_dst[tuple(pt_dst)], min_)) if min_ != 0: orientations_dk.append((orientation_src, orientation_ref)) continue pos_src = np.matmul(aff_src, np.hstack((pt_src, [1])).reshape((4,1))) pos_dst = np.matmul(aff_dst, np.hstack((pt_dst, [1])).reshape((4,1))) displacement = (pos_dst - pos_src).reshape((-1))[:3] displacement_log = pt_dst - pt_src #print(" Displacement (logical): %s" % (displacement_log)) if not np.allclose(displacement, shift_wanted): orientations_ng.append((orientation_src, orientation_ref)) print(" \x1B[31;1mDisplacement (physical): %s\x1B[0m" % (displacement)) else: orientations_ok.append((orientation_src, orientation_ref)) print(" Displacement (physical): %s" % (displacement)) print("") def ori_str(x): return " ".join(["{}->{}".format(x,y) for (x,y) in x]) print("Orientations OK: {}".format(ori_str(orientations_ok))) print("Orientations NG: {}".format(ori_str(orientations_ng))) print("Orientations DK: {}".format(ori_str(orientations_dk)))
def setUp(self): self.image = msct_image.Image(sct_test_path('t2', 't2.nii.gz')) self.overlay = msct_image.Image(self.image) self.params = base.AnatomicalParams()
def apply(self): # Initialization fname_src = self.input_filename # source image (moving) list_warp = self.list_warp # list of warping fields fname_out = self.output_filename # output fname_dest = self.fname_dest # destination image (fix) verbose = self.verbose remove_temp_files = self.remove_temp_files crop_reference = self.crop # if = 1, put 0 everywhere around warping field, if = 2, real crop interp = sct.get_interpolation('isct_antsApplyTransforms', self.interp) # Parse list of warping fields sct.printv('\nParse list of warping fields...', verbose) use_inverse = [] fname_warp_list_invert = [] # list_warp = list_warp.replace(' ', '') # remove spaces # list_warp = list_warp.split(",") # parse with comma for idx_warp, path_warp in enumerate(self.list_warp): # Check if this transformation should be inverted if path_warp in self.list_warpinv: use_inverse.append('-i') # list_warp[idx_warp] = path_warp[1:] # remove '-' fname_warp_list_invert += [[ use_inverse[idx_warp], list_warp[idx_warp] ]] else: use_inverse.append('') fname_warp_list_invert += [[path_warp]] path_warp = list_warp[idx_warp] if path_warp.endswith((".nii", ".nii.gz")) \ and msct_image.Image(list_warp[idx_warp]).header.get_intent()[0] != 'vector': raise ValueError("Displacement field in {} is invalid: should be encoded" \ " in a 5D file with vector intent code" \ " (see https://nifti.nimh.nih.gov/pub/dist/src/niftilib/nifti1.h" \ .format(path_warp)) # need to check if last warping field is an affine transfo isLastAffine = False path_fname, file_fname, ext_fname = sct.extract_fname( fname_warp_list_invert[-1][-1]) if ext_fname in ['.txt', '.mat']: isLastAffine = True # check if destination file is 3d if not sct.check_if_3d(fname_dest): sct.printv('ERROR: Destination data must be 3d') # N.B. Here we take the inverse of the warp list, because sct_WarpImageMultiTransform concatenates in the reverse order fname_warp_list_invert.reverse() fname_warp_list_invert = functools.reduce(lambda x, y: x + y, fname_warp_list_invert) # Extract path, file and extension path_src, file_src, ext_src = sct.extract_fname(fname_src) path_dest, file_dest, ext_dest = sct.extract_fname(fname_dest) # Get output folder and file name if fname_out == '': path_out = '' # output in user's current directory file_out = file_src + '_reg' ext_out = ext_src fname_out = os.path.join(path_out, file_out + ext_out) # Get dimensions of data sct.printv('\nGet dimensions of data...', verbose) img_src = msct_image.Image(fname_src) nx, ny, nz, nt, px, py, pz, pt = img_src.dim # nx, ny, nz, nt, px, py, pz, pt = sct.get_dimension(fname_src) sct.printv( ' ' + str(nx) + ' x ' + str(ny) + ' x ' + str(nz) + ' x ' + str(nt), verbose) # if 3d if nt == 1: # Apply transformation sct.printv('\nApply transformation...', verbose) if nz in [0, 1]: dim = '2' else: dim = '3' sct.run([ 'isct_antsApplyTransforms', '-d', dim, '-i', fname_src, '-o', fname_out, '-t' ] + fname_warp_list_invert + ['-r', fname_dest] + interp, verbose=verbose, is_sct_binary=True) # if 4d, loop across the T dimension else: path_tmp = sct.tmp_create(basename="apply_transfo", verbose=verbose) # convert to nifti into temp folder sct.printv( '\nCopying input data to tmp folder and convert to nii...', verbose) img_src.save(os.path.join(path_tmp, "data.nii")) sct.copy(fname_dest, os.path.join(path_tmp, file_dest + ext_dest)) fname_warp_list_tmp = [] for fname_warp in list_warp: path_warp, file_warp, ext_warp = sct.extract_fname(fname_warp) sct.copy(fname_warp, os.path.join(path_tmp, file_warp + ext_warp)) fname_warp_list_tmp.append(file_warp + ext_warp) fname_warp_list_invert_tmp = fname_warp_list_tmp[::-1] curdir = os.getcwd() os.chdir(path_tmp) # split along T dimension sct.printv('\nSplit along T dimension...', verbose) im_dat = msct_image.Image('data.nii') im_header = im_dat.hdr data_split_list = sct_image.split_data(im_dat, 3) for im in data_split_list: im.save() # apply transfo sct.printv('\nApply transformation to each 3D volume...', verbose) for it in range(nt): file_data_split = 'data_T' + str(it).zfill(4) + '.nii' file_data_split_reg = 'data_reg_T' + str(it).zfill(4) + '.nii' status, output = sct.run([ 'isct_antsApplyTransforms', '-d', '3', '-i', file_data_split, '-o', file_data_split_reg, '-t', ] + fname_warp_list_invert_tmp + [ '-r', file_dest + ext_dest, ] + interp, verbose, is_sct_binary=True) # Merge files back sct.printv('\nMerge file back...', verbose) import glob path_out, name_out, ext_out = sct.extract_fname(fname_out) # im_list = [Image(file_name) for file_name in glob.glob('data_reg_T*.nii')] # concat_data use to take a list of image in input, now takes a list of file names to open the files one by one (see issue #715) fname_list = glob.glob('data_reg_T*.nii') fname_list.sort() im_out = sct_image.concat_data(fname_list, 3, im_header['pixdim']) im_out.save(name_out + ext_out) os.chdir(curdir) sct.generate_output_file( os.path.join(path_tmp, name_out + ext_out), fname_out) # Delete temporary folder if specified if int(remove_temp_files): sct.printv('\nRemove temporary files...', verbose) sct.rmtree(path_tmp, verbose=verbose) # 2. crop the resulting image using dimensions from the warping field warping_field = fname_warp_list_invert[-1] # if last warping field is an affine transfo, we need to compute the space of the concatenate warping field: if isLastAffine: sct.printv( 'WARNING: the resulting image could have wrong apparent results. You should use an affine transformation as last transformation...', verbose, 'warning') elif crop_reference == 1: ImageCropper(input_file=fname_out, output_file=fname_out, ref=warping_field, background=0).crop() # sct.run('sct_crop_image -i '+fname_out+' -o '+fname_out+' -ref '+warping_field+' -b 0') elif crop_reference == 2: ImageCropper(input_file=fname_out, output_file=fname_out, ref=warping_field).crop() # sct.run('sct_crop_image -i '+fname_out+' -o '+fname_out+' -ref '+warping_field) sct.display_viewer_syntax([fname_dest, fname_out], verbose=verbose)
def main(args=None): import numpy as np import spinalcordtoolbox.image as msct_image # Initialization fname_mt0 = '' fname_mt1 = '' file_out = param.file_out # register = param.register # remove_temp_files = param.remove_temp_files # verbose = param.verbose # check user arguments if not args: args = sys.argv[1:] # Check input parameters parser = get_parser() arguments = parser.parse(args) fname_mt0 = arguments['-mt0'] fname_mt1 = arguments['-mt1'] remove_temp_files = int(arguments['-r']) verbose = int(arguments['-v']) # Extract path/file/extension path_mt0, file_mt0, ext_mt0 = sct.extract_fname(fname_mt0) path_out, file_out, ext_out = '', file_out, ext_mt0 # create temporary folder path_tmp = sct.tmp_create() # Copying input data to tmp folder and convert to nii sct.printv('\nCopying input data to tmp folder and convert to nii...', verbose) from sct_convert import convert convert(fname_mt0, os.path.join(path_tmp, "mt0.nii"), dtype=np.float32) convert(fname_mt1, os.path.join(path_tmp, "mt1.nii"), dtype=np.float32) # go to tmp folder curdir = os.getcwd() os.chdir(path_tmp) # compute MTR sct.printv('\nCompute MTR...', verbose) nii_mt1 = msct_image.Image('mt1.nii') data_mt1 = nii_mt1.data data_mt0 = msct_image.Image('mt0.nii').data data_mtr = 100 * (data_mt0 - data_mt1) / data_mt0 # save MTR file nii_mtr = nii_mt1 nii_mtr.data = data_mtr nii_mtr.save("mtr.nii") # sct.run(fsloutput+'fslmaths -dt double mt0.nii -sub mt1.nii -mul 100 -div mt0.nii -thr 0 -uthr 100 mtr.nii', verbose) # come back os.chdir(curdir) # Generate output files sct.printv('\nGenerate output files...', verbose) sct.generate_output_file(os.path.join(path_tmp, "mtr.nii"), os.path.join(path_out, file_out + ext_out)) # Remove temporary files if remove_temp_files == 1: sct.printv('\nRemove temporary files...') sct.rmtree(path_tmp) sct.display_viewer_syntax([fname_mt0, fname_mt1, file_out])
def setUp(self): self.image = msct_image.Image( os.path.expanduser('~/sct_example_data/t2/t2.nii.gz')) self.overlay = msct_image.Image(self.image) self.params = base.AnatomicalParams()
def extract_centerline(segmentation, verbose=0, algo_fitting='hanning', type_window='hanning', window_length=5, use_phys_coord=True, file_out='centerline'): """ Extract centerline from a binary or weighted segmentation by computing the center of mass slicewise. :param segmentation: input segmentation. Could be either an Image or a file name. :param verbose: :param algo_fitting: :param type_window: :param window_length: :param use_phys_coord: TODO: Explain the pros/cons of use_phys_coord. :param file_out: :return: None """ # TODO: output continuous centerline (and add in unit test) # TODO: centerline coordinate should have the same orientation as the input image # TODO: no need for unecessary i/o. Everything could be done in RAM # Create temp folder path_tmp = sct.tmp_create() # Open segmentation volume im_seg = msct_image.Image(segmentation) # im_seg.change_orientation('RPI', generate_path=True) native_orientation = im_seg.orientation im_seg.change_orientation("RPI", generate_path=True).save(path_tmp, mutable=True) fname_tmp_seg = im_seg.absolutepath # extract centerline and smooth it if use_phys_coord: # fit centerline, smooth it and return the first derivative (in physical space) x_centerline_fit, y_centerline_fit, z_centerline, \ x_centerline_deriv, y_centerline_deriv, z_centerline_deriv = smooth_centerline( fname_tmp_seg, algo_fitting=algo_fitting, type_window=type_window, window_length=window_length, nurbs_pts_number=3000, phys_coordinates=True, verbose=verbose, all_slices=False) centerline = Centerline(x_centerline_fit, y_centerline_fit, z_centerline, x_centerline_deriv, y_centerline_deriv, z_centerline_deriv) # average centerline coordinates over slices of the image (floating point) x_centerline_fit_rescorr, y_centerline_fit_rescorr, z_centerline_rescorr, \ x_centerline_deriv_rescorr, y_centerline_deriv_rescorr, z_centerline_deriv_rescorr = \ centerline.average_coordinates_over_slices(im_seg) # compute z_centerline in image coordinates (discrete) voxel_coordinates = im_seg.transfo_phys2pix( [[x_centerline_fit_rescorr[i], y_centerline_fit_rescorr[i], z_centerline_rescorr[i]] for i in range(len(z_centerline_rescorr))]) x_centerline_voxel = [coord[0] for coord in voxel_coordinates] y_centerline_voxel = [coord[1] for coord in voxel_coordinates] z_centerline_voxel = [coord[2] for coord in voxel_coordinates] else: # fit centerline, smooth it and return the first derivative (in voxel space but FITTED coordinates) x_centerline_voxel, y_centerline_voxel, z_centerline_voxel, \ x_centerline_deriv, y_centerline_deriv, z_centerline_deriv = smooth_centerline( 'segmentation_RPI.nii.gz', algo_fitting=algo_fitting, type_window=type_window, window_length=window_length, nurbs_pts_number=3000, phys_coordinates=False, verbose=verbose, all_slices=True) if verbose == 2: # TODO: code below does not work import matplotlib.pyplot as plt # Creation of a vector x that takes into account the distance between the labels nz_nonz = len(z_centerline_voxel) x_display = [0 for i in range(x_centerline_voxel.shape[0])] y_display = [0 for i in range(y_centerline_voxel.shape[0])] for i in range(0, nz_nonz, 1): x_display[int(z_centerline_voxel[i] - z_centerline_voxel[0])] = x_centerline[i] y_display[int(z_centerline_voxel[i] - z_centerline_voxel[0])] = y_centerline[i] plt.figure(1) plt.subplot(2, 1, 1) plt.plot(z_centerline_voxel, x_display, 'ro') plt.plot(z_centerline_voxel, x_centerline_voxel) plt.xlabel("Z") plt.ylabel("X") plt.title("x and x_fit coordinates") plt.subplot(2, 1, 2) plt.plot(z_centerline_voxel, y_display, 'ro') plt.plot(z_centerline_voxel, y_centerline_voxel) plt.xlabel("Z") plt.ylabel("Y") plt.title("y and y_fit coordinates") plt.show() # Create an image with the centerline # TODO: write the center of mass, not the discrete image coordinate (issue #1938) im_centerline = im_seg.copy() data_centerline = im_centerline.data * 0 # Find z-boundaries above which and below which there is no non-null slices min_z_index, max_z_index = int(round(min(z_centerline_voxel))), int(round(max(z_centerline_voxel))) # loop across slices and set centerline pixel to value=1 for iz in range(min_z_index, max_z_index + 1): data_centerline[int(round(x_centerline_voxel[iz - min_z_index])), int(round(y_centerline_voxel[iz - min_z_index])), int(iz)] = 1 # assign data to centerline image im_centerline.data = data_centerline # reorient centerline to native orientation im_centerline.change_orientation(native_orientation) # save nifti volume fname_centerline = file_out + '.nii.gz' im_centerline.save(fname_centerline, dtype='uint8') # display stuff # sct.display_viewer_syntax([fname_segmentation, fname_centerline], colormaps=['gray', 'green']) # output csv with centerline coordinates fname_centerline_csv = file_out + '.csv' f_csv = open(fname_centerline_csv, 'w') f_csv.write('x,y,z\n') # csv header for i in range(min_z_index, max_z_index + 1): f_csv.write("%d,%d,%d\n" % (int(i), x_centerline_voxel[i - min_z_index], y_centerline_voxel[i - min_z_index])) f_csv.close() # TODO: display open syntax for csv # create a .roi file fname_roi_centerline = optic.centerline2roi(fname_image=fname_centerline, folder_output='./', verbose=verbose)
def compute_properties_along_centerline(fname_seg_image, property_list, fname_disks_image=None, smooth_factor=5.0, interpolation_mode=0, remove_temp_files=1, verbose=1): # Check list of properties # If diameters is in the list, compute major and minor axis length and check orientation compute_diameters = False property_list_local = list(property_list) if 'diameters' in property_list_local: compute_diameters = True property_list_local.remove('diameters') property_list_local.append('major_axis_length') property_list_local.append('minor_axis_length') property_list_local.append('orientation') # TODO: make sure fname_segmentation and fname_disks are in the same space path_tmp = sct.tmp_create(basename="compute_properties_along_centerline", verbose=verbose) image = msct_image.Image(fname_seg_image) \ .change_orientation("RPI", generate_path=True) \ .save(path_tmp, mutable=True) fname_segmentation_orient = image.absolutepath if fname_disks_image is not None: image_disks = msct_image.Image(fname_disks_image) \ .change_orientation("RPI", generate_path=True) \ .save(path_tmp, mutable=True) fname_disks_orient = image_disks.absolutepath # go to tmp folder curdir = os.getcwd() os.chdir(path_tmp) # Initiating some variables nx, ny, nz, nt, px, py, pz, pt = image.dim resolution = 0.5 properties = {key: [] for key in property_list_local} properties['incremental_length'] = [] properties['distance_from_C1'] = [] properties['vertebral_level'] = [] properties['z_slice'] = [] # compute the spinal cord centerline based on the spinal cord segmentation number_of_points = 5 * nz x_centerline_fit, y_centerline_fit, z_centerline, x_centerline_deriv, y_centerline_deriv, z_centerline_deriv = smooth_centerline( fname_segmentation_orient, algo_fitting='nurbs', verbose=verbose, nurbs_pts_number=number_of_points, all_slices=False, phys_coordinates=True, remove_outliers=True) centerline = Centerline(x_centerline_fit, y_centerline_fit, z_centerline, x_centerline_deriv, y_centerline_deriv, z_centerline_deriv) # Compute vertebral distribution along centerline based on position of intervertebral disks if fname_disks_image is not None: coord = image_disks.getNonZeroCoordinates(sorting='z', reverse_coord=True) coord_physical = [] for c in coord: c_p = image_disks.transfo_pix2phys([[c.x, c.y, c.z]])[0] c_p.append(c.value) coord_physical.append(c_p) centerline.compute_vertebral_distribution(coord_physical) sct.printv('Computing spinal cord shape along the spinal cord...') with tqdm.tqdm(total=centerline.number_of_points) as pbar: # Extracting patches perpendicular to the spinal cord and computing spinal cord shape for index in range(centerline.number_of_points): # value_out = -5.0 value_out = 0.0 current_patch = centerline.extract_perpendicular_square( image, index, resolution=resolution, interpolation_mode=interpolation_mode, border='constant', cval=value_out) # check for pixels close to the spinal cord segmentation that are out of the image from skimage.morphology import dilation patch_zero = np.copy(current_patch) patch_zero[patch_zero == value_out] = 0.0 patch_borders = dilation(patch_zero) - patch_zero """ if np.count_nonzero(patch_borders + current_patch == value_out + 1.0) != 0: c = image.transfo_phys2pix([centerline.points[index]])[0] print('WARNING: no patch for slice', c[2]) continue """ sc_properties = properties2d(patch_zero, [resolution, resolution]) if sc_properties is not None: properties['incremental_length'].append( centerline.incremental_length[index]) if fname_disks_image is not None: properties['distance_from_C1'].append( centerline.dist_points[index]) properties['vertebral_level'].append( centerline.l_points[index]) properties['z_slice'].append( image.transfo_phys2pix([centerline.points[index]])[0][2]) for property_name in property_list_local: properties[property_name].append( sc_properties[property_name]) else: c = image.transfo_phys2pix([centerline.points[index]])[0] print('WARNING: no properties for slice', c[2]) pbar.update(1) # Adding centerline to the properties for later use properties['centerline'] = centerline # We assume that the major axis is in the right-left direction # this script checks the orientation of the spinal cord and invert axis if necessary to make sure the major axis is right-left if compute_diameters: diameter_major = properties['major_axis_length'] diameter_minor = properties['minor_axis_length'] orientation = properties['orientation'] for i, orientation_item in enumerate(orientation): if -45.0 < orientation_item < 45.0: continue else: temp = diameter_minor[i] properties['minor_axis_length'][i] = diameter_major[i] properties['major_axis_length'][i] = temp properties['RL_diameter'] = properties['major_axis_length'] properties['AP_diameter'] = properties['minor_axis_length'] del properties['major_axis_length'] del properties['minor_axis_length'] # smooth the spinal cord shape with a gaussian kernel if required # TODO: not all properties can be smoothed if smooth_factor != 0.0: # smooth_factor is in mm import scipy window = scipy.signal.hann(smooth_factor / np.mean(centerline.progressive_length)) for property_name in property_list_local: properties[property_name] = scipy.signal.convolve( properties[property_name], window, mode='same') / np.sum(window) if compute_diameters: property_list_local.remove('major_axis_length') property_list_local.remove('minor_axis_length') property_list_local.append('RL_diameter') property_list_local.append('AP_diameter') property_list = property_list_local # Display properties on the referential space. Requires intervertebral disks if verbose == 2: x_increment = 'distance_from_C1' if fname_disks_image is None: x_increment = 'incremental_length' # Display the image and plot all contours found fig, axes = plt.subplots(len(property_list_local), sharex=True, sharey=False) for k, property_name in enumerate(property_list_local): axes[k].plot(properties[x_increment], properties[property_name]) axes[k].set_ylabel(property_name) if fname_disks_image is not None: properties[ 'distance_disk_from_C1'] = centerline.distance_from_C1label # distance between each disk and C1 (or first disk) xlabel_disks = [ centerline.convert_vertlabel2disklabel[label] for label in properties['distance_disk_from_C1'] ] xtick_disks = [ properties['distance_disk_from_C1'][label] for label in properties['distance_disk_from_C1'] ] plt.xticks(xtick_disks, xlabel_disks, rotation=30) else: axes[-1].set_xlabel('Position along the spinal cord (in mm)') plt.show() # Removing temporary folder os.chdir(curdir) if remove_temp_files: sct.rmtree(path_tmp) return property_list, properties