def get_im_from_list(self, data): im = Image(data) # set pix dimension im.hdr.structarr['pixdim'][1] = self.param_data.axial_res im.hdr.structarr['pixdim'][2] = self.param_data.axial_res # set the correct orientation im.save('im_to_orient.nii.gz') # TODO explain this quirk im = msct_image.change_orientation(im, 'IRP') im = msct_image.change_orientation(im, 'PIL', inverse=True) return im
def _call_viewer_centerline(fname_in, interslice_gap=20.0): from spinalcordtoolbox.gui.base import AnatomicalParams from spinalcordtoolbox.gui.centerline import launch_centerline_dialog im_data = Image(fname_in) # Get the number of slice along the (IS) axis im_tmp = msct_image.change_orientation(im_data, 'RPI') _, _, nz, _, _, _, pz, _ = im_tmp.dim del im_tmp params = AnatomicalParams() # setting maximum number of points to a reasonable value params.num_points = np.ceil(nz * pz / interslice_gap) + 2 params.interval_in_mm = interslice_gap params.starting_slice = 'top' im_mask_viewer = msct_image.zeros_like(im_data) controller = launch_centerline_dialog(im_data, im_mask_viewer, params) fname_labels_viewer = sct.add_suffix(fname_in, '_viewer') if not controller.saved: sct.log.error( 'The viewer has been closed before entering all manual points. Please try again.' ) sys.exit(1) # save labels controller.as_niftii(fname_labels_viewer) return fname_labels_viewer
def test_change_nd_orientation(fake_4dimage_sct): import sct_image im_src = fake_4dimage_sct.copy() path_tmp = sct.tmp_create(basename="test_reorient") 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=2, R=2, A=3, P=3, I=4, S=4, ) return tuple([letter2dim[x] for x in orient] + [5]) orientation = im_src.orientation assert orientation == "LPI" assert im_src.header.get_best_affine()[:3,3].tolist() == [0,0,0] im_dst = msct_image.change_orientation(im_src, "RPI") assert im_dst.orientation == "RPI" assert im_dst.data.shape == orient2shape("RPI") assert im_dst.header.get_best_affine()[:3,3].tolist() == [2-1,0,0]
def test_change_nd_orientation(fake_4dimage_sct): im_src = fake_4dimage_sct.copy() path_tmp = tmp_create(basename="test_reorient") 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=2, R=2, A=3, P=3, I=4, S=4, ) return tuple([letter2dim[x] for x in orient] + [5]) orientation = im_src.orientation assert orientation == "LPI" assert im_src.header.get_best_affine()[:3, 3].tolist() == [0, 0, 0] im_dst = msct_image.change_orientation(im_src, "RPI") assert im_dst.orientation == "RPI" assert im_dst.data.shape == orient2shape("RPI") assert im_dst.header.get_best_affine()[:3, 3].tolist() == [2 - 1, 0, 0]
def resample_image(fname, suffix='_resampled.nii.gz', binary=False, npx=0.3, npy=0.3, thr=0.0, interpolation='spline'): """ Resampling function: add a padding, resample, crop the padding :param fname: name of the image file to be resampled :param suffix: suffix added to the original fname after resampling :param binary: boolean, image is binary or not :param npx: new pixel size in the x direction :param npy: new pixel size in the y direction :param thr: if the image is binary, it will be thresholded at thr (default=0) after the resampling :param interpolation: type of interpolation used for the resampling :return: file name after resampling (or original fname if it was already in the correct resolution) """ im_in = Image(fname) orientation = im_in.orientation if orientation != 'RPI': im_in.change_orientation('RPI') fname = add_suffix(im_in.absolutepath, "_rpi") im_in.save(path=fname, mutable=True) nx, ny, nz, nt, px, py, pz, pt = im_in.dim if np.round(px, 2) != np.round(npx, 2) or np.round(py, 2) != np.round(npy, 2): name_resample = extract_fname(fname)[1] + suffix if binary: interpolation = 'nn' if nz == 1: # when data is 2d: we convert it to a 3d image in order to avoid conversion problem with 2d data # TODO: check if this above problem is still present (now that we are using nibabel instead of nipy) run_proc(['sct_image', '-i', ','.join([fname, fname]), '-concat', 'z', '-o', fname]) run_proc(['sct_resample', '-i', fname, '-mm', str(npx) + 'x' + str(npy) + 'x' + str(pz), '-o', name_resample, '-x', interpolation]) if nz == 1: # when input data was 2d: re-convert data 3d-->2d run_proc(['sct_image', '-i', name_resample, '-split', 'z']) im_split = Image(name_resample.split('.nii.gz')[0] + '_Z0000.nii.gz') im_split.save(name_resample) if binary: img = Image(name_resample) img.data = binarize(img.data, thr) img.save() if orientation != 'RPI': img = Image(name_resample) img.change_orientation(orientation) name_resample = add_suffix(img.absolutepath, "_{}".format(orientation.lower())) img.save(path=name_resample, mutable=True) return name_resample else: if orientation != 'RPI': fname = add_suffix(fname, "_RPI") im_in = change_orientation(im_in, orientation).save(fname) printv('Image resolution already ' + str(npx) + 'x' + str(npy) + 'xpz') return fname
def resample_image(fname, suffix='_resampled.nii.gz', binary=False, npx=0.3, npy=0.3, thr=0.0, interpolation='spline'): """ Resampling function: add a padding, resample, crop the padding :param fname: name of the image file to be resampled :param suffix: suffix added to the original fname after resampling :param binary: boolean, image is binary or not :param npx: new pixel size in the x direction :param npy: new pixel size in the y direction :param thr: if the image is binary, it will be thresholded at thr (default=0) after the resampling :param interpolation: type of interpolation used for the resampling :return: file name after resampling (or original fname if it was already in the correct resolution) """ im_in = Image(fname) orientation = im_in.orientation if orientation != 'RPI': fname = im_in.change_orientation(im_in, 'RPI', generate_path=True).save().absolutepath nx, ny, nz, nt, px, py, pz, pt = im_in.dim if np.round(px, 2) != np.round(npx, 2) or np.round(py, 2) != np.round(npy, 2): name_resample = sct.extract_fname(fname)[1] + suffix if binary: interpolation = 'nn' if nz == 1: # when data is 2d: we convert it to a 3d image in order to avoid nipy problem of conversion nifti-->nipy with 2d data sct.run(['sct_image', '-i', ','.join([fname, fname]), '-concat', 'z', '-o', fname]) sct.run(['sct_resample', '-i', fname, '-mm', str(npx) + 'x' + str(npy) + 'x' + str(pz), '-o', name_resample, '-x', interpolation]) if nz == 1: # when input data was 2d: re-convert data 3d-->2d sct.run(['sct_image', '-i', name_resample, '-split', 'z']) im_split = Image(name_resample.split('.nii.gz')[0] + '_Z0000.nii.gz') im_split.save(name_resample) if binary: sct.run(['sct_maths', '-i', name_resample, '-bin', str(thr), '-o', name_resample]) if orientation != 'RPI': name_resample = Image(name_resample) \ .change_orientation(orientation, generate_path=True) \ .save() \ .absolutepath return name_resample else: if orientation != 'RPI': fname = sct.add_suffix(fname, "_RPI") im_in = msct_image.change_orientation(im_in, orientation).save(fname) sct.printv('Image resolution already ' + str(npx) + 'x' + str(npy) + 'xpz') return fname
def main(args=None): # initializations output_type = None param = Param() dim_list = ['x', 'y', 'z', 't'] # check user arguments if not args: args = sys.argv[1:] # Get parser info parser = get_parser() arguments = parser.parse(args) fname_in = arguments["-i"] n_in = len(fname_in) verbose = int(arguments.get('-v')) sct.init_sct(log_level=verbose, update=True) # Update log level if "-o" in arguments: fname_out = arguments["-o"] else: fname_out = None # Open file(s) # im_in_list = [Image(fn) for fn in fname_in] # run command if "-concat" in arguments: dim = arguments["-concat"] assert dim in dim_list dim = dim_list.index(dim) im_out = [concat_data(fname_in, dim)] # TODO: adapt to fname_in elif "-copy-header" in arguments: im_in = Image(fname_in[0]) im_dest = Image(arguments["-copy-header"]) im_dest_new = im_in.copy() im_dest_new.data = im_dest.data.copy() # im_dest.header = im_in.header im_dest_new.absolutepath = im_dest.absolutepath im_out = [im_dest_new] fname_out = arguments["-copy-header"] elif '-display-warp' in arguments: im_in = fname_in[0] visualize_warp(im_in, fname_grid=None, step=3, rm_tmp=True) im_out = None elif "-getorient" in arguments: im_in = Image(fname_in[0]) orient = im_in.orientation im_out = None elif '-keep-vol' in arguments: index_vol = arguments['-keep-vol'] im_in = Image(fname_in[0]) im_out = [remove_vol(im_in, index_vol, todo='keep')] elif '-mcs' in arguments: im_in = Image(fname_in[0]) if n_in != 1: sct.printv(parser.usage.generate(error='ERROR: -mcs need only one input')) if len(im_in.data.shape) != 5: sct.printv(parser.usage.generate(error='ERROR: -mcs input need to be a multi-component image')) im_out = multicomponent_split(im_in) elif '-omc' in arguments: im_ref = Image(fname_in[0]) for fname in fname_in: im = Image(fname) if im.data.shape != im_ref.data.shape: sct.printv(parser.usage.generate(error='ERROR: -omc inputs need to have all the same shapes')) del im im_out = [multicomponent_merge(fname_in)] # TODO: adapt to fname_in elif "-pad" in arguments: im_in = Image(fname_in[0]) ndims = len(im_in.data.shape) if ndims != 3: sct.printv('ERROR: you need to specify a 3D input file.', 1, 'error') return pad_arguments = arguments["-pad"].split(',') if len(pad_arguments) != 3: sct.printv('ERROR: you need to specify 3 padding values.', 1, 'error') padx, pady, padz = pad_arguments padx, pady, padz = int(padx), int(pady), int(padz) im_out = [pad_image(im_in, pad_x_i=padx, pad_x_f=padx, pad_y_i=pady, pad_y_f=pady, pad_z_i=padz, pad_z_f=padz)] elif "-pad-asym" in arguments: im_in = Image(fname_in[0]) ndims = len(im_in.data.shape) if ndims != 3: sct.printv('ERROR: you need to specify a 3D input file.', 1, 'error') return pad_arguments = arguments["-pad-asym"].split(',') if len(pad_arguments) != 6: sct.printv('ERROR: you need to specify 6 padding values.', 1, 'error') padxi, padxf, padyi, padyf, padzi, padzf = pad_arguments padxi, padxf, padyi, padyf, padzi, padzf = int(padxi), int(padxf), int(padyi), int(padyf), int(padzi), int(padzf) im_out = [pad_image(im_in, pad_x_i=padxi, pad_x_f=padxf, pad_y_i=padyi, pad_y_f=padyf, pad_z_i=padzi, pad_z_f=padzf)] elif '-remove-vol' in arguments: index_vol = arguments['-remove-vol'] im_in = Image(fname_in[0]) im_out = [remove_vol(im_in, index_vol, todo='remove')] elif "-setorient" in arguments: sct.printv(fname_in[0]) im_in = Image(fname_in[0]) im_out = [msct_image.change_orientation(im_in, arguments["-setorient"]).save(fname_out)] elif "-setorient-data" in arguments: im_in = Image(fname_in[0]) im_out = [msct_image.change_orientation(im_in, arguments["-setorient-data"], inverse=True).save(fname_out)] elif "-split" in arguments: dim = arguments["-split"] assert dim in dim_list im_in = Image(fname_in[0]) dim = dim_list.index(dim) im_out = split_data(im_in, dim) elif '-type' in arguments: output_type = arguments['-type'] im_in = Image(fname_in[0]) im_out = [im_in] # TODO: adapt to fname_in else: im_out = None sct.printv(parser.usage.generate(error='ERROR: you need to specify an operation to do on the input image')) # in case fname_out is not defined, use first element of input file name list if fname_out == None: fname_out = fname_in[0] # Write output if im_out is not None: sct.printv('Generate output files...', verbose) # if only one output if len(im_out) == 1 and not '-split' in arguments: im_out[0].save(fname_out, dtype=output_type, verbose=verbose) sct.display_viewer_syntax([fname_out], verbose=verbose) if '-mcs' in arguments: # use input file name and add _X, _Y _Z. Keep the same extension l_fname_out = [] for i_dim in range(3): l_fname_out.append(sct.add_suffix(fname_out or fname_in[0], '_' + dim_list[i_dim].upper())) im_out[i_dim].save(l_fname_out[i_dim], verbose=verbose) sct.display_viewer_syntax(fname_out) if '-split' in arguments: # use input file name and add _"DIM+NUMBER". Keep the same extension l_fname_out = [] for i, im in enumerate(im_out): l_fname_out.append(sct.add_suffix(fname_out or fname_in[0], '_' + dim_list[dim].upper() + str(i).zfill(4))) im.save(l_fname_out[i]) sct.display_viewer_syntax(l_fname_out) elif "-getorient" in arguments: sct.printv(orient) elif '-display-warp' in arguments: sct.printv('Warping grid generated.', verbose, 'info')
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_change_orientation(fake_3dimage_sct, fake_3dimage_sct_vis): path_tmp = tmp_create(basename="test_reorient") path_tmp = "." print("Spot-checking that physical coordinates don't change") for shape_is in (1, 2, 3): shape = (1, 1, shape_is) print("Simple image with shape {}".format(shape)) data = np.ones(shape, order="F") data[:, :, shape_is - 1] += 1 im_src = fake_3dimage_sct_custom(data) im_dst = msct_image.change_orientation(im_src, "ASR") # Basic check assert im_dst.orientation == "ASR" # Basic data check assert im_dst.data.mean() == im_src.data.mean() # Basic header check: check that the same voxel # remains at the same physical position aff_src = im_src.header.get_best_affine() aff_dst = im_dst.header.get_best_affine() # Take the extremities "a" & "z"... # consider the original LPI position pta_src = np.array([[0, 0, 0, 1]]).T ptz_src = np.array([[0, 0, shape_is - 1, 1]]).T # and the position in ASR pta_dst = np.array([[0, shape_is - 1, 0, 1]]).T ptz_dst = np.array([[0, 0, 0, 1]]).T # The physical positions should be: posa_src = np.matmul(aff_src, pta_src) posa_dst = np.matmul(aff_dst, pta_dst) print("A at src {}".format(posa_src.T)) print("A at dst {}".format(posa_dst.T)) posz_src = np.matmul(aff_src, ptz_src) posz_dst = np.matmul(aff_dst, ptz_dst) # and they should be equal assert (posa_src == posa_dst).all() assert (posz_src == posz_dst).all() fn = "".join(str(x) for x in im_src.data.shape) im_src.save("{}-src.nii".format(fn)) im_dst.save("{}-dst.nii".format(fn)) np.random.seed(0) print("More checking that physical coordinates don't change") if 1: shape = (7, 8, 9) print("Simple image with shape {}".format(shape)) data = np.ones(shape, order="F") * 10 data[4, 4, 4] = 4 data[3, 3, 3] = 3 data[0, 0, 0] = 0 values = (0, 3, 4) im_ref = fake_3dimage_sct_custom(data) im_ref.header.set_xyzt_units("mm", "msec") import scipy.linalg def rand_rot(): q, _ = scipy.linalg.qr(np.random.randn(3, 3)) if scipy.linalg.det(q) < 0: q[:, 0] = -q[:, 0] return q affine = im_ref.header.get_best_affine() affine[:3, :3] = rand_rot() affine[3, :3] = 0.0 affine[:3, 3] = np.random.random((3)) affine[3, 3] = 1.0 affine[0, 0] *= 2 im_ref.header.set_sform(affine, code='scanner') orientations = msct_image.all_refspace_strings() for ori_src in orientations: for ori_dst in orientations: print("{} -> {}".format(ori_src, ori_dst)) im_src = msct_image.change_orientation(im_ref, ori_src) im_dst = msct_image.change_orientation(im_src, ori_dst) assert im_src.orientation == ori_src assert im_dst.orientation == ori_dst assert im_dst.data.mean() == im_src.data.mean() # Basic header check: check that the same voxel # remains at the same physical position aff_src = im_src.header.get_best_affine() aff_dst = im_dst.header.get_best_affine() data_src = np.array(im_src.data) data_dst = np.array(im_dst.data) for value in values: pt_src = np.argwhere(data_src == value)[0] pt_dst = np.argwhere(data_dst == value)[0] 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))) if 0: print("P at src {}".format(pos_src.T)) print("P at dst {}".format(pos_dst.T)) assert np.allclose(pos_src, pos_dst, atol=1e-3)
def main(args=None): # initializations output_type = None param = Param() dim_list = ['x', 'y', 'z', 't'] # check user arguments if not args: args = sys.argv[1:] # Get parser info parser = get_parser() arguments = parser.parse(args) fname_in = arguments["-i"] n_in = len(fname_in) verbose = int(arguments.get('-v')) sct.init_sct(log_level=verbose, update=True) # Update log level if "-o" in arguments: fname_out = arguments["-o"] else: fname_out = None # Open file(s) # im_in_list = [Image(fn) for fn in fname_in] # run command if "-concat" in arguments: dim = arguments["-concat"] assert dim in dim_list dim = dim_list.index(dim) im_out = [concat_data(fname_in, dim)] # TODO: adapt to fname_in elif "-copy-header" in arguments: im_in = Image(fname_in[0]) im_dest = Image(arguments["-copy-header"]) im_dest_new = im_in.copy() im_dest_new.data = im_dest.data.copy() # im_dest.header = im_in.header im_dest_new.absolutepath = im_dest.absolutepath im_out = [im_dest_new] fname_out = arguments["-copy-header"] elif '-display-warp' in arguments: im_in = fname_in[0] visualize_warp(im_in, fname_grid=None, step=3, rm_tmp=True) im_out = None elif "-getorient" in arguments: im_in = Image(fname_in[0]) orient = im_in.orientation im_out = None elif '-keep-vol' in arguments: index_vol = arguments['-keep-vol'] im_in = Image(fname_in[0]) im_out = [remove_vol(im_in, index_vol, todo='keep')] elif '-mcs' in arguments: im_in = Image(fname_in[0]) if n_in != 1: sct.printv( parser.usage.generate(error='ERROR: -mcs need only one input')) if len(im_in.data.shape) != 5: sct.printv( parser.usage.generate( error='ERROR: -mcs input need to be a multi-component image' )) im_out = multicomponent_split(im_in) elif '-omc' in arguments: im_ref = Image(fname_in[0]) for fname in fname_in: im = Image(fname) if im.data.shape != im_ref.data.shape: sct.printv( parser.usage.generate( error= 'ERROR: -omc inputs need to have all the same shapes')) del im im_out = [multicomponent_merge(fname_in)] # TODO: adapt to fname_in elif "-pad" in arguments: im_in = Image(fname_in[0]) ndims = len(im_in.data.shape) if ndims != 3: sct.printv('ERROR: you need to specify a 3D input file.', 1, 'error') return pad_arguments = arguments["-pad"].split(',') if len(pad_arguments) != 3: sct.printv('ERROR: you need to specify 3 padding values.', 1, 'error') padx, pady, padz = pad_arguments padx, pady, padz = int(padx), int(pady), int(padz) im_out = [ pad_image(im_in, pad_x_i=padx, pad_x_f=padx, pad_y_i=pady, pad_y_f=pady, pad_z_i=padz, pad_z_f=padz) ] elif "-pad-asym" in arguments: im_in = Image(fname_in[0]) ndims = len(im_in.data.shape) if ndims != 3: sct.printv('ERROR: you need to specify a 3D input file.', 1, 'error') return pad_arguments = arguments["-pad-asym"].split(',') if len(pad_arguments) != 6: sct.printv('ERROR: you need to specify 6 padding values.', 1, 'error') padxi, padxf, padyi, padyf, padzi, padzf = pad_arguments padxi, padxf, padyi, padyf, padzi, padzf = int(padxi), int(padxf), int( padyi), int(padyf), int(padzi), int(padzf) im_out = [ pad_image(im_in, pad_x_i=padxi, pad_x_f=padxf, pad_y_i=padyi, pad_y_f=padyf, pad_z_i=padzi, pad_z_f=padzf) ] elif '-remove-vol' in arguments: index_vol = arguments['-remove-vol'] im_in = Image(fname_in[0]) im_out = [remove_vol(im_in, index_vol, todo='remove')] elif "-setorient" in arguments: sct.printv(fname_in[0]) im_in = Image(fname_in[0]) im_out = [ msct_image.change_orientation( im_in, arguments["-setorient"]).save(fname_out) ] elif "-setorient-data" in arguments: im_in = Image(fname_in[0]) im_out = [ msct_image.change_orientation(im_in, arguments["-setorient-data"], inverse=True).save(fname_out) ] elif "-split" in arguments: dim = arguments["-split"] assert dim in dim_list im_in = Image(fname_in[0]) dim = dim_list.index(dim) im_out = split_data(im_in, dim) elif '-type' in arguments: output_type = arguments['-type'] im_in = Image(fname_in[0]) im_out = [im_in] # TODO: adapt to fname_in else: im_out = None sct.printv( parser.usage.generate( error= 'ERROR: you need to specify an operation to do on the input image' )) # in case fname_out is not defined, use first element of input file name list if fname_out == None: fname_out = fname_in[0] # Write output if im_out is not None: sct.printv('Generate output files...', verbose) # if only one output if len(im_out) == 1 and not '-split' in arguments: im_out[0].save(fname_out, dtype=output_type, verbose=verbose) sct.display_viewer_syntax([fname_out], verbose=verbose) if '-mcs' in arguments: # use input file name and add _X, _Y _Z. Keep the same extension l_fname_out = [] for i_dim in range(3): l_fname_out.append( sct.add_suffix(fname_out or fname_in[0], '_' + dim_list[i_dim].upper())) im_out[i_dim].save(l_fname_out[i_dim], verbose=verbose) sct.display_viewer_syntax(fname_out) if '-split' in arguments: # use input file name and add _"DIM+NUMBER". Keep the same extension l_fname_out = [] for i, im in enumerate(im_out): l_fname_out.append( sct.add_suffix( fname_out or fname_in[0], '_' + dim_list[dim].upper() + str(i).zfill(4))) im.save(l_fname_out[i]) sct.display_viewer_syntax(l_fname_out) elif "-getorient" in arguments: sct.printv(orient) elif '-display-warp' in arguments: sct.printv('Warping grid generated.', verbose, 'info')
def deep_segmentation_MSlesion(fname_image, contrast_type, output_folder, ctr_algo='svm', ctr_file=None, brain_bool=True, remove_temp_files=1, verbose=1): """Pipeline.""" path_script = os.path.dirname(__file__) path_sct = os.path.dirname(path_script) # create temporary folder with intermediate results sct.log.info("\nCreating temporary folder...") file_fname = os.path.basename(fname_image) tmp_folder = sct.TempFolder() tmp_folder_path = tmp_folder.get_path() fname_image_tmp = tmp_folder.copy_from(fname_image) if ctr_algo == 'manual': # if the ctr_file is provided tmp_folder.copy_from(ctr_file) file_ctr = os.path.basename(ctr_file) else: file_ctr = None tmp_folder.chdir() # orientation of the image, should be RPI sct.log.info("\nReorient the image to RPI, if necessary...") fname_orient = sct.add_suffix(file_fname, '_RPI') im_2orient = Image(file_fname) original_orientation = im_2orient.orientation if original_orientation != 'RPI': im_orient = msct_image.change_orientation(im_2orient, 'RPI').save(fname_orient) else: im_orient = im_2orient sct.copy(fname_image_tmp, fname_orient) input_resolution = im_orient.dim[4:7] del im_2orient, im_orient # find the spinal cord centerline - execute OptiC binary sct.log.info("\nFinding the spinal cord centerline...") contrast_type_ctr = contrast_type.split('_')[0] fname_res, centerline_filename = find_centerline(algo=ctr_algo, image_fname=fname_orient, path_sct=path_sct, contrast_type=contrast_type_ctr, brain_bool=brain_bool, folder_output=tmp_folder_path, remove_temp_files=remove_temp_files, centerline_fname=file_ctr) im_nii, ctr_nii = Image(fname_res), Image(centerline_filename) # crop image around the spinal cord centerline sct.log.info("\nCropping the image around the spinal cord...") fname_crop = sct.add_suffix(fname_res, '_crop') crop_size = 48 X_CROP_LST, Y_CROP_LST, im_crop_nii = crop_image_around_centerline(im_in=im_nii, ctr_in=ctr_nii, crop_size=crop_size) del ctr_nii # normalize the intensity of the images sct.log.info("Normalizing the intensity...") im_norm_in = apply_intensity_normalization(img=im_crop_nii, contrast=contrast_type) del im_crop_nii # resample to 0.5mm isotropic fname_norm = sct.add_suffix(fname_orient, '_norm') im_norm_in.save(fname_norm) fname_res3d = sct.add_suffix(fname_norm, '_resampled3d') spinalcordtoolbox.resample.nipy_resample.resample_file(fname_norm, fname_res3d, '0.5x0.5x0.5', 'mm', 'linear', verbose=0) # segment data using 3D convolutions sct.log.info("\nSegmenting the MS lesions using deep learning on 3D patches...") segmentation_model_fname = os.path.join(path_sct, 'data', 'deepseg_lesion_models', '{}_lesion.h5'.format(contrast_type)) fname_seg_crop_res = sct.add_suffix(fname_res3d, '_lesionseg') segment_3d(model_fname=segmentation_model_fname, contrast_type=contrast_type, fname_in=fname_res3d, fname_out=fname_seg_crop_res) # resample to the initial pz resolution fname_seg_res2d = sct.add_suffix(fname_seg_crop_res, '_resampled2d') initial_2d_resolution = 'x'.join(['0.5', '0.5', str(input_resolution[2])]) spinalcordtoolbox.resample.nipy_resample.resample_file(fname_seg_crop_res, fname_seg_res2d, initial_2d_resolution, 'mm', 'linear', verbose=0) seg_crop_data = Image(fname_seg_res2d).data # reconstruct the segmentation from the crop data sct.log.info("\nReassembling the image...") seg_uncrop_nii = uncrop_image(ref_in=im_nii, data_crop=seg_crop_data, x_crop_lst=X_CROP_LST, y_crop_lst=Y_CROP_LST) fname_seg_res_RPI = sct.add_suffix(file_fname, '_res_RPI_seg') seg_uncrop_nii.save(fname_seg_res_RPI) del seg_uncrop_nii, im_nii, seg_crop_data # resample to initial resolution sct.log.info("Resampling the segmentation to the original image resolution...") initial_resolution = 'x'.join([str(input_resolution[0]), str(input_resolution[1]), str(input_resolution[2])]) fname_seg_RPI = sct.add_suffix(file_fname, '_RPI_seg') spinalcordtoolbox.resample.nipy_resample.resample_file(fname_seg_res_RPI, fname_seg_RPI, initial_resolution, 'mm', 'linear', verbose=0) seg_initres_nii = Image(fname_seg_RPI) # binarize the resampled image to remove interpolation effects sct.log.info("\nBinarizing the segmentation to avoid interpolation effects...") thr = 0.1 seg_initres_nii.data[np.where(seg_initres_nii.data >= thr)] = 1 seg_initres_nii.data[np.where(seg_initres_nii.data < thr)] = 0 # reorient to initial orientation sct.log.info("\nReorienting the segmentation to the original image orientation...") fname_seg = sct.add_suffix(file_fname, '_seg') if original_orientation != 'RPI': out_nii = msct_image.change_orientation(seg_initres_nii, original_orientation) seg_initres_nii.save(fname_seg) del seg_initres_nii tmp_folder.chdir_undo() # copy image from temporary folder into output folder sct.copy(os.path.join(tmp_folder_path, fname_seg), output_folder) # remove temporary files if remove_temp_files: sct.log.info("\nRemove temporary files...") tmp_folder.cleanup() return os.path.join(output_folder, fname_seg)
def main(args=None): """ Main function :param args: :return: """ # initializations output_type = None dim_list = ['x', 'y', 'z', 't'] # Get parser args if args is None: args = None if sys.argv[1:] else ['--help'] parser = get_parser() arguments = parser.parse_args(args=args) fname_in = arguments.i n_in = len(fname_in) verbose = arguments.v sct.init_sct(log_level=verbose, update=True) # Update log level if arguments.o is not None: fname_out = arguments.o else: fname_out = None # Run command # Arguments are sorted alphabetically (not according to the usage order) if arguments.concat is not None: dim = arguments.concat assert dim in dim_list dim = dim_list.index(dim) im_out = [concat_data(fname_in, dim)] # TODO: adapt to fname_in elif arguments.copy_header is not None: im_in = Image(fname_in[0]) im_dest = Image(arguments.copy_header) im_dest_new = im_in.copy() im_dest_new.data = im_dest.data.copy() # im_dest.header = im_in.header im_dest_new.absolutepath = im_dest.absolutepath im_out = [im_dest_new] fname_out = arguments.copy_header elif arguments.display_warp: im_in = fname_in[0] visualize_warp(im_in, fname_grid=None, step=3, rm_tmp=True) im_out = None elif arguments.getorient: im_in = Image(fname_in[0]) orient = im_in.orientation im_out = None elif arguments.keep_vol is not None: index_vol = (arguments.keep_vol).split(',') for iindex_vol, vol in enumerate(index_vol): index_vol[iindex_vol] = int(vol) im_in = Image(fname_in[0]) im_out = [remove_vol(im_in, index_vol, todo='keep')] elif arguments.mcs: im_in = Image(fname_in[0]) if n_in != 1: sct.printv(parser.error('ERROR: -mcs need only one input')) if len(im_in.data.shape) != 5: sct.printv( parser.error( 'ERROR: -mcs input need to be a multi-component image')) im_out = multicomponent_split(im_in) elif arguments.omc: im_ref = Image(fname_in[0]) for fname in fname_in: im = Image(fname) if im.data.shape != im_ref.data.shape: sct.printv( parser.error( 'ERROR: -omc inputs need to have all the same shapes')) del im im_out = [multicomponent_merge(fname_in)] # TODO: adapt to fname_in elif arguments.pad is not None: im_in = Image(fname_in[0]) ndims = len(im_in.data.shape) if ndims != 3: sct.printv('ERROR: you need to specify a 3D input file.', 1, 'error') return pad_arguments = arguments.pad.split(',') if len(pad_arguments) != 3: sct.printv('ERROR: you need to specify 3 padding values.', 1, 'error') padx, pady, padz = pad_arguments padx, pady, padz = int(padx), int(pady), int(padz) im_out = [ pad_image(im_in, pad_x_i=padx, pad_x_f=padx, pad_y_i=pady, pad_y_f=pady, pad_z_i=padz, pad_z_f=padz) ] elif arguments.pad_asym is not None: im_in = Image(fname_in[0]) ndims = len(im_in.data.shape) if ndims != 3: sct.printv('ERROR: you need to specify a 3D input file.', 1, 'error') return pad_arguments = arguments.pad_asym.split(',') if len(pad_arguments) != 6: sct.printv('ERROR: you need to specify 6 padding values.', 1, 'error') padxi, padxf, padyi, padyf, padzi, padzf = pad_arguments padxi, padxf, padyi, padyf, padzi, padzf = int(padxi), int(padxf), int( padyi), int(padyf), int(padzi), int(padzf) im_out = [ pad_image(im_in, pad_x_i=padxi, pad_x_f=padxf, pad_y_i=padyi, pad_y_f=padyf, pad_z_i=padzi, pad_z_f=padzf) ] elif arguments.remove_vol is not None: index_vol = (arguments.remove_vol).split(',') for iindex_vol, vol in enumerate(index_vol): index_vol[iindex_vol] = int(vol) im_in = Image(fname_in[0]) im_out = [remove_vol(im_in, index_vol, todo='remove')] elif arguments.setorient is not None: sct.printv(fname_in[0]) im_in = Image(fname_in[0]) im_out = [msct_image.change_orientation(im_in, arguments.setorient)] elif arguments.setorient_data is not None: im_in = Image(fname_in[0]) im_out = [ msct_image.change_orientation(im_in, arguments.setorient_data, data_only=True) ] elif arguments.split is not None: dim = arguments.split assert dim in dim_list im_in = Image(fname_in[0]) dim = dim_list.index(dim) im_out = split_data(im_in, dim) elif arguments.type is not None: output_type = arguments.type im_in = Image(fname_in[0]) im_out = [im_in] # TODO: adapt to fname_in elif arguments.to_fsl is not None: space_files = arguments.to_fsl if len(space_files) > 2 or len(space_files) < 1: sct.printv(parser.error('ERROR: -to-fsl expects 1 or 2 arguments')) return spaces = [Image(s) for s in space_files] if len(spaces) < 2: spaces.append(None) im_out = [ displacement_to_abs_fsl(Image(fname_in[0]), spaces[0], spaces[1]) ] else: im_out = None sct.printv( parser.error( 'ERROR: you need to specify an operation to do on the input image' )) # in case fname_out is not defined, use first element of input file name list if fname_out is None: fname_out = fname_in[0] # Write output if im_out is not None: sct.printv('Generate output files...', verbose) # if only one output if len(im_out) == 1 and not '-split' in arguments: im_out[0].save(fname_out, dtype=output_type, verbose=verbose) sct.display_viewer_syntax([fname_out], verbose=verbose) if arguments.mcs: # use input file name and add _X, _Y _Z. Keep the same extension l_fname_out = [] for i_dim in range(3): l_fname_out.append( sct.add_suffix(fname_out or fname_in[0], '_' + dim_list[i_dim].upper())) im_out[i_dim].save(l_fname_out[i_dim], verbose=verbose) sct.display_viewer_syntax(fname_out) if arguments.split is not None: # use input file name and add _"DIM+NUMBER". Keep the same extension l_fname_out = [] for i, im in enumerate(im_out): l_fname_out.append( sct.add_suffix( fname_out or fname_in[0], '_' + dim_list[dim].upper() + str(i).zfill(4))) im.save(l_fname_out[i]) sct.display_viewer_syntax(l_fname_out) elif arguments.getorient: sct.printv(orient) elif arguments.display_warp: sct.printv('Warping grid generated.', verbose, 'info')
def test_more_change_orientation(fake_3dimage_sct, fake_3dimage_sct_vis): path_tmp = sct.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 main(argv=None): """ Main function :param argv: :return: """ parser = get_parser() arguments = parser.parse_args(argv) verbose = arguments.v set_loglevel(verbose=verbose) # initializations output_type = None dim_list = ['x', 'y', 'z', 't'] fname_in = arguments.i im_in_list = [Image(fname) for fname in fname_in] if len(im_in_list ) > 1 and arguments.concat is None and arguments.omc is None: parser.error( "Multi-image input is only supported for the '-concat' and '-omc' arguments." ) # Apply initialization steps to all input images first if arguments.set_sform_to_qform: [im.set_sform_to_qform() for im in im_in_list] elif arguments.set_qform_to_sform: [im.set_qform_to_sform() for im in im_in_list] # Most sct_image options don't accept multi-image input, so here we simply separate out the first image # TODO: Extend the options so that they iterate through the list of images (to support multi-image input) im_in = im_in_list[0] if arguments.o is not None: fname_out = arguments.o else: fname_out = None # Run command # Arguments are sorted alphabetically (not according to the usage order) if arguments.concat is not None: dim = arguments.concat assert dim in dim_list dim = dim_list.index(dim) im_out = [concat_data(im_in_list, dim)] elif arguments.copy_header is not None: if fname_out is None: raise ValueError("Need to specify output image with -o!") im_dest = Image(arguments.copy_header) im_dest_new = im_in.copy() im_dest_new.data = im_dest.data.copy() # im_dest.header = im_in.header im_dest_new.absolutepath = im_dest.absolutepath im_out = [im_dest_new] elif arguments.display_warp: visualize_warp(im_warp=im_in, im_grid=None, step=3, rm_tmp=True) im_out = None elif arguments.getorient: orient = im_in.orientation im_out = None elif arguments.keep_vol is not None: index_vol = (arguments.keep_vol).split(',') for iindex_vol, vol in enumerate(index_vol): index_vol[iindex_vol] = int(vol) im_out = [remove_vol(im_in, index_vol, todo='keep')] elif arguments.mcs: if len(im_in.data.shape) != 5: printv( parser.error( 'ERROR: -mcs input need to be a multi-component image')) im_out = multicomponent_split(im_in) elif arguments.omc: im_ref = im_in_list[0] for im in im_in_list: if im.data.shape != im_ref.data.shape: printv( parser.error( 'ERROR: -omc inputs need to have all the same shapes')) del im im_out = [multicomponent_merge(im_in_list=im_in_list)] elif arguments.pad is not None: ndims = len(im_in.data.shape) if ndims != 3: printv('ERROR: you need to specify a 3D input file.', 1, 'error') return pad_arguments = arguments.pad.split(',') if len(pad_arguments) != 3: printv('ERROR: you need to specify 3 padding values.', 1, 'error') padx, pady, padz = pad_arguments padx, pady, padz = int(padx), int(pady), int(padz) im_out = [ pad_image(im_in, pad_x_i=padx, pad_x_f=padx, pad_y_i=pady, pad_y_f=pady, pad_z_i=padz, pad_z_f=padz) ] elif arguments.pad_asym is not None: ndims = len(im_in.data.shape) if ndims != 3: printv('ERROR: you need to specify a 3D input file.', 1, 'error') return pad_arguments = arguments.pad_asym.split(',') if len(pad_arguments) != 6: printv('ERROR: you need to specify 6 padding values.', 1, 'error') padxi, padxf, padyi, padyf, padzi, padzf = pad_arguments padxi, padxf, padyi, padyf, padzi, padzf = int(padxi), int(padxf), int( padyi), int(padyf), int(padzi), int(padzf) im_out = [ pad_image(im_in, pad_x_i=padxi, pad_x_f=padxf, pad_y_i=padyi, pad_y_f=padyf, pad_z_i=padzi, pad_z_f=padzf) ] elif arguments.remove_vol is not None: index_vol = (arguments.remove_vol).split(',') for iindex_vol, vol in enumerate(index_vol): index_vol[iindex_vol] = int(vol) im_out = [remove_vol(im_in, index_vol, todo='remove')] elif arguments.setorient is not None: printv(im_in.absolutepath) im_out = [change_orientation(im_in, arguments.setorient)] elif arguments.setorient_data is not None: im_out = [ change_orientation(im_in, arguments.setorient_data, data_only=True) ] elif arguments.header is not None: header = im_in.header # Necessary because of https://github.com/nipy/nibabel/issues/480#issuecomment-239227821 im_file = nib.load(im_in.absolutepath) header.structarr['scl_slope'] = im_file.dataobj.slope header.structarr['scl_inter'] = im_file.dataobj.inter printv(create_formatted_header_string(header=header, output_format=arguments.header), verbose=verbose) im_out = None elif arguments.split is not None: dim = arguments.split assert dim in dim_list dim = dim_list.index(dim) im_out = split_data(im_in, dim) elif arguments.type is not None: output_type = arguments.type im_out = [im_in] elif arguments.to_fsl is not None: space_files = arguments.to_fsl if len(space_files) > 2 or len(space_files) < 1: printv(parser.error('ERROR: -to-fsl expects 1 or 2 arguments')) return spaces = [Image(s) for s in space_files] if len(spaces) < 2: spaces.append(None) im_out = [displacement_to_abs_fsl(im_in, spaces[0], spaces[1])] # If these arguments are used standalone, simply pass the input image to the output (the affines were set earlier) elif arguments.set_sform_to_qform or arguments.set_qform_to_sform: im_out = [im_in] else: im_out = None printv( parser.error( 'ERROR: you need to specify an operation to do on the input image' )) # in case fname_out is not defined, use first element of input file name list if fname_out is None: fname_out = fname_in[0] # Write output if im_out is not None: printv('Generate output files...', verbose) # if only one output if len(im_out) == 1 and arguments.split is None: im_out[0].save(fname_out, dtype=output_type, verbose=verbose) display_viewer_syntax([fname_out], verbose=verbose) if arguments.mcs: # use input file name and add _X, _Y _Z. Keep the same extension l_fname_out = [] for i_dim in range(3): l_fname_out.append( add_suffix(fname_out or fname_in[0], '_' + dim_list[i_dim].upper())) im_out[i_dim].save(l_fname_out[i_dim], verbose=verbose) display_viewer_syntax(fname_out) if arguments.split is not None: # use input file name and add _"DIM+NUMBER". Keep the same extension l_fname_out = [] for i, im in enumerate(im_out): l_fname_out.append( add_suffix(fname_out or fname_in[0], '_' + dim_list[dim].upper() + str(i).zfill(4))) im.save(l_fname_out[i]) display_viewer_syntax(l_fname_out) elif arguments.getorient: printv(orient) elif arguments.display_warp: printv('Warping grid generated.', verbose, 'info')
def deep_segmentation_spinalcord(fname_image, contrast_type, output_folder, ctr_algo='cnn', ctr_file=None, brain_bool=True, kernel_size='2d', remove_temp_files=1, verbose=1): """Pipeline.""" path_script = os.path.dirname(__file__) path_sct = os.path.dirname(path_script) # create temporary folder with intermediate results sct.log.info("Creating temporary folder...") file_fname = os.path.basename(fname_image) tmp_folder = sct.TempFolder() tmp_folder_path = tmp_folder.get_path() fname_image_tmp = tmp_folder.copy_from(fname_image) if ctr_algo == 'manual': # if the ctr_file is provided tmp_folder.copy_from(ctr_file) file_ctr = os.path.basename(ctr_file) else: file_ctr = None tmp_folder.chdir() # orientation of the image, should be RPI sct.log.info("Reorient the image to RPI, if necessary...") fname_orient = sct.add_suffix(file_fname, '_RPI') im_2orient = Image(file_fname) original_orientation = im_2orient.orientation if original_orientation != 'RPI': im_orient = msct_image.change_orientation(im_2orient, 'RPI').save(fname_orient) else: im_orient = im_2orient sct.copy(fname_image_tmp, fname_orient) # resampling RPI image sct.log.info("Resample the image to 0.5 mm isotropic resolution...") fname_res = sct.add_suffix(fname_orient, '_resampled') im_2res = im_orient input_resolution = im_2res.dim[4:7] new_resolution = 'x'.join(['0.5', '0.5', str(input_resolution[2])]) spinalcordtoolbox.resample.nipy_resample.resample_file(fname_orient, fname_res, new_resolution, 'mm', 'linear', verbose=0) # find the spinal cord centerline - execute OptiC binary sct.log.info("Finding the spinal cord centerline...") centerline_filename = find_centerline(algo=ctr_algo, image_fname=fname_res, path_sct=path_sct, contrast_type=contrast_type, brain_bool=brain_bool, folder_output=tmp_folder_path, remove_temp_files=remove_temp_files, centerline_fname=file_ctr) # crop image around the spinal cord centerline sct.log.info("Cropping the image around the spinal cord...") fname_crop = sct.add_suffix(fname_res, '_crop') crop_size = 96 if (kernel_size == '3d' and contrast_type == 't2s') else 64 X_CROP_LST, Y_CROP_LST = crop_image_around_centerline( filename_in=fname_res, filename_ctr=centerline_filename, filename_out=fname_crop, crop_size=crop_size) # normalize the intensity of the images sct.log.info("Normalizing the intensity...") fname_norm = sct.add_suffix(fname_crop, '_norm') apply_intensity_normalization(img_path=fname_crop, fname_out=fname_norm) if kernel_size == '2d': # segment data using 2D convolutions sct.log.info( "Segmenting the spinal cord using deep learning on 2D patches...") segmentation_model_fname = os.path.join( path_sct, 'data', 'deepseg_sc_models', '{}_sc.h5'.format(contrast_type)) fname_seg_crop = sct.add_suffix(fname_norm, '_seg') seg_crop_data = segment_2d(model_fname=segmentation_model_fname, contrast_type=contrast_type, input_size=(crop_size, crop_size), fname_in=fname_norm, fname_out=fname_seg_crop) elif kernel_size == '3d': # resample to 0.5mm isotropic fname_res3d = sct.add_suffix(fname_norm, '_resampled3d') spinalcordtoolbox.resample.nipy_resample.resample_file(fname_norm, fname_res3d, '0.5x0.5x0.5', 'mm', 'linear', verbose=0) # segment data using 3D convolutions sct.log.info( "Segmenting the spinal cord using deep learning on 3D patches...") segmentation_model_fname = os.path.join( path_sct, 'data', 'deepseg_sc_models', '{}_sc_3D.h5'.format(contrast_type)) fname_seg_crop_res = sct.add_suffix(fname_res3d, '_seg') segment_3d(model_fname=segmentation_model_fname, contrast_type=contrast_type, fname_in=fname_res3d, fname_out=fname_seg_crop_res) # resample to the initial pz resolution fname_seg_res2d = sct.add_suffix(fname_seg_crop_res, '_resampled2d') initial_2d_resolution = 'x'.join( ['0.5', '0.5', str(input_resolution[2])]) spinalcordtoolbox.resample.nipy_resample.resample_file( fname_seg_crop_res, fname_seg_res2d, initial_2d_resolution, 'mm', 'linear', verbose=0) seg_crop_data = Image(fname_seg_res2d).data # reconstruct the segmentation from the crop data sct.log.info("Reassembling the image...") fname_seg_res_RPI = sct.add_suffix(file_fname, '_res_RPI_seg') uncrop_image(fname_ref=fname_res, fname_out=fname_seg_res_RPI, data_crop=seg_crop_data, x_crop_lst=X_CROP_LST, y_crop_lst=Y_CROP_LST) # resample to initial resolution sct.log.info( "Resampling the segmentation to the original image resolution...") fname_seg_RPI = sct.add_suffix(file_fname, '_RPI_seg') initial_resolution = 'x'.join([ str(input_resolution[0]), str(input_resolution[1]), str(input_resolution[2]) ]) spinalcordtoolbox.resample.nipy_resample.resample_file(fname_seg_res_RPI, fname_seg_RPI, initial_resolution, 'mm', 'linear', verbose=0) # binarize the resampled image to remove interpolation effects sct.log.info( "Binarizing the segmentation to avoid interpolation effects...") thr = '0.0001' if contrast_type in ['t1', 'dwi'] else '0.5' sct.run( ['sct_maths', '-i', fname_seg_RPI, '-bin', thr, '-o', fname_seg_RPI], verbose=0) # post processing step to z_regularized post_processing_volume_wise(fname_in=fname_seg_RPI) # reorient to initial orientation sct.log.info( "Reorienting the segmentation to the original image orientation...") fname_seg = sct.add_suffix(file_fname, '_seg') if original_orientation != 'RPI': im_seg_orient = msct_image.change_orientation( Image(fname_seg_RPI), original_orientation).save(fname_seg) else: sct.copy(fname_seg_RPI, fname_seg) tmp_folder.chdir_undo() # copy image from temporary folder into output folder sct.copy(os.path.join(tmp_folder_path, fname_seg), output_folder) # remove temporary files if remove_temp_files: sct.log.info("Remove temporary files...") tmp_folder.cleanup() return os.path.join(output_folder, fname_seg)
def test_change_orientation(fake_3dimage_sct, fake_3dimage_sct_vis): path_tmp = sct.tmp_create(basename="test_reorient") path_tmp = "." print("Spot-checking that physical coordinates don't change") for shape_is in (1,2,3): shape = (1,1,shape_is) print("Simple image with shape {}".format(shape)) data = np.ones(shape, order="F") data[:,:,shape_is-1] += 1 im_src = fake_3dimage_sct_custom(data) im_dst = msct_image.change_orientation(im_src, "ASR") # Basic check assert im_dst.orientation == "ASR" # Basic data check assert im_dst.data.mean() == im_src.data.mean() # Basic header check: check that the same voxel # remains at the same physical position aff_src = im_src.header.get_best_affine() aff_dst = im_dst.header.get_best_affine() # Take the extremities "a" & "z"... # consider the original LPI position pta_src = np.array([[0,0,0,1]]).T ptz_src = np.array([[0,0,shape_is-1,1]]).T # and the position in ASR pta_dst = np.array([[0,shape_is-1,0,1]]).T ptz_dst = np.array([[0,0,0,1]]).T # The physical positions should be: posa_src = np.matmul(aff_src, pta_src) posa_dst = np.matmul(aff_dst, pta_dst) print("A at src {}".format(posa_src.T)) print("A at dst {}".format(posa_dst.T)) posz_src = np.matmul(aff_src, ptz_src) posz_dst = np.matmul(aff_dst, ptz_dst) # and they should be equal assert (posa_src == posa_dst).all() assert (posz_src == posz_dst).all() fn = "".join(str(x) for x in im_src.data.shape) im_src.save("{}-src.nii".format(fn)) im_dst.save("{}-dst.nii".format(fn)) np.random.seed(0) print("More checking that physical coordinates don't change") if 1: shape = (7,8,9) print("Simple image with shape {}".format(shape)) data = np.ones(shape, order="F") * 10 data[4,4,4] = 4 data[3,3,3] = 3 data[0,0,0] = 0 values = (0,3,4) im_ref = fake_3dimage_sct_custom(data) im_ref.header.set_xyzt_units("mm", "msec") import scipy.linalg def rand_rot(): q, _ = scipy.linalg.qr(np.random.randn(3, 3)) if scipy.linalg.det(q) < 0: q[:, 0] = -q[:, 0] return q affine = im_ref.header.get_best_affine() affine[:3,:3] = rand_rot() affine[3,:3] = 0.0 affine[:3,3] = np.random.random((3)) affine[3,3] = 1.0 affine[0,0] *= 2 im_ref.header.set_sform(affine, code='scanner') orientations = msct_image.all_refspace_strings() for ori_src in orientations: for ori_dst in orientations: print("{} -> {}".format(ori_src, ori_dst)) im_src = msct_image.change_orientation(im_ref, ori_src) im_dst = msct_image.change_orientation(im_src, ori_dst) assert im_src.orientation == ori_src assert im_dst.orientation == ori_dst assert im_dst.data.mean() == im_src.data.mean() # Basic header check: check that the same voxel # remains at the same physical position aff_src = im_src.header.get_best_affine() aff_dst = im_dst.header.get_best_affine() data_src = np.array(im_src.data) data_dst = np.array(im_dst.data) for value in values: pt_src = np.argwhere(data_src == value)[0] pt_dst = np.argwhere(data_dst == value)[0] 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))) if 0: print("P at src {}".format(pos_src.T)) print("P at dst {}".format(pos_dst.T)) assert np.allclose(pos_src, pos_dst, atol=1e-3)
def main(argv=None): """ Main function :param argv: :return: """ parser = get_parser() arguments = parser.parse_args(argv) verbose = arguments.v set_global_loglevel(verbose=verbose) # initializations output_type = None dim_list = ['x', 'y', 'z', 't'] fname_in = arguments.i n_in = len(fname_in) # TODO: The functions for '-concat', '-omc', and '-display-warp' take in filenames, so they ignore 'im_in'. # Instead, we should harmonize this functionality so that 'im_in' is used everywhere. im_in = Image(fname_in[0]) if arguments.set_sform_to_qform is not None: im_in.set_sform_to_qform() if arguments.o is not None: fname_out = arguments.o else: fname_out = None # Run command # Arguments are sorted alphabetically (not according to the usage order) if arguments.concat is not None: dim = arguments.concat assert dim in dim_list dim = dim_list.index(dim) # TODO: Modify concat_data to take in a list of Image() objects so that 'im_in' can be passed instead im_out = [concat_data(fname_in, dim)] elif arguments.copy_header is not None: if fname_out is None: raise ValueError("Need to specify output image with -o!") im_dest = Image(arguments.copy_header) im_dest_new = im_in.copy() im_dest_new.data = im_dest.data.copy() # im_dest.header = im_in.header im_dest_new.absolutepath = im_dest.absolutepath im_out = [im_dest_new] elif arguments.display_warp: # TODO: Modify visualize_warp to take in an Image() object so that 'im_in' can be passed instead visualize_warp(fname_in[0], fname_grid=None, step=3, rm_tmp=True) im_out = None elif arguments.getorient: orient = im_in.orientation im_out = None elif arguments.keep_vol is not None: index_vol = (arguments.keep_vol).split(',') for iindex_vol, vol in enumerate(index_vol): index_vol[iindex_vol] = int(vol) im_out = [remove_vol(im_in, index_vol, todo='keep')] elif arguments.mcs: if n_in != 1: printv(parser.error('ERROR: -mcs need only one input')) if len(im_in.data.shape) != 5: printv( parser.error( 'ERROR: -mcs input need to be a multi-component image')) im_out = multicomponent_split(im_in) elif arguments.omc: im_ref = im_in for fname in fname_in: im = Image(fname) if im.data.shape != im_ref.data.shape: printv( parser.error( 'ERROR: -omc inputs need to have all the same shapes')) del im # TODO: Modify multicomponent_merge to take in a list of Image() objects so that 'im_in' can be passed instead im_out = [multicomponent_merge(fname_in)] # TODO: adapt to fname_in elif arguments.pad is not None: ndims = len(im_in.data.shape) if ndims != 3: printv('ERROR: you need to specify a 3D input file.', 1, 'error') return pad_arguments = arguments.pad.split(',') if len(pad_arguments) != 3: printv('ERROR: you need to specify 3 padding values.', 1, 'error') padx, pady, padz = pad_arguments padx, pady, padz = int(padx), int(pady), int(padz) im_out = [ pad_image(im_in, pad_x_i=padx, pad_x_f=padx, pad_y_i=pady, pad_y_f=pady, pad_z_i=padz, pad_z_f=padz) ] elif arguments.pad_asym is not None: ndims = len(im_in.data.shape) if ndims != 3: printv('ERROR: you need to specify a 3D input file.', 1, 'error') return pad_arguments = arguments.pad_asym.split(',') if len(pad_arguments) != 6: printv('ERROR: you need to specify 6 padding values.', 1, 'error') padxi, padxf, padyi, padyf, padzi, padzf = pad_arguments padxi, padxf, padyi, padyf, padzi, padzf = int(padxi), int(padxf), int( padyi), int(padyf), int(padzi), int(padzf) im_out = [ pad_image(im_in, pad_x_i=padxi, pad_x_f=padxf, pad_y_i=padyi, pad_y_f=padyf, pad_z_i=padzi, pad_z_f=padzf) ] elif arguments.remove_vol is not None: index_vol = (arguments.remove_vol).split(',') for iindex_vol, vol in enumerate(index_vol): index_vol[iindex_vol] = int(vol) im_out = [remove_vol(im_in, index_vol, todo='remove')] elif arguments.setorient is not None: printv(fname_in[0]) im_out = [change_orientation(im_in, arguments.setorient)] elif arguments.setorient_data is not None: im_out = [ change_orientation(im_in, arguments.setorient_data, data_only=True) ] elif arguments.split is not None: dim = arguments.split assert dim in dim_list dim = dim_list.index(dim) im_out = split_data(im_in, dim) elif arguments.type is not None: output_type = arguments.type im_out = [im_in] # TODO: adapt to fname_in elif arguments.to_fsl is not None: space_files = arguments.to_fsl if len(space_files) > 2 or len(space_files) < 1: printv(parser.error('ERROR: -to-fsl expects 1 or 2 arguments')) return spaces = [Image(s) for s in space_files] if len(spaces) < 2: spaces.append(None) im_out = [displacement_to_abs_fsl(im_in, spaces[0], spaces[1])] # If this argument is used standalone, simply pass the input image to the output (sform was set for im_in earlier) elif arguments.set_sform_to_qform is not None: im_out = [im_in] else: im_out = None printv( parser.error( 'ERROR: you need to specify an operation to do on the input image' )) # in case fname_out is not defined, use first element of input file name list if fname_out is None: fname_out = fname_in[0] # Write output if im_out is not None: printv('Generate output files...', verbose) # if only one output if len(im_out) == 1 and arguments.split is None: im_out[0].save(fname_out, dtype=output_type, verbose=verbose) display_viewer_syntax([fname_out], verbose=verbose) if arguments.mcs: # use input file name and add _X, _Y _Z. Keep the same extension l_fname_out = [] for i_dim in range(3): l_fname_out.append( add_suffix(fname_out or fname_in[0], '_' + dim_list[i_dim].upper())) im_out[i_dim].save(l_fname_out[i_dim], verbose=verbose) display_viewer_syntax(fname_out) if arguments.split is not None: # use input file name and add _"DIM+NUMBER". Keep the same extension l_fname_out = [] for i, im in enumerate(im_out): l_fname_out.append( add_suffix(fname_out or fname_in[0], '_' + dim_list[dim].upper() + str(i).zfill(4))) im.save(l_fname_out[i]) display_viewer_syntax(l_fname_out) elif arguments.getorient: printv(orient) elif arguments.display_warp: printv('Warping grid generated.', verbose, 'info')
def find_centerline(algo, image_fname, path_sct, contrast_type, brain_bool, folder_output, remove_temp_files, centerline_fname): if Image(image_fname).dim[2] == 1: # isct_spine_detect requires nz > 1 from sct_image import concat_data im_concat = concat_data([image_fname, image_fname], dim=2) im_concat.save(sct.add_suffix(image_fname, '_concat')) image_fname = sct.add_suffix(image_fname, '_concat') bool_2d = True else: bool_2d = False if algo == 'svm': # run optic on a heatmap computed by a trained SVM+HoG algorithm optic_models_fname = os.path.join(path_sct, 'data', 'optic_models', '{}_model'.format(contrast_type)) _, centerline_filename = optic.detect_centerline( image_fname=image_fname, contrast_type=contrast_type, optic_models_path=optic_models_fname, folder_output=folder_output, remove_temp_files=remove_temp_files, output_roi=False, verbose=0) elif algo == 'cnn': # CNN parameters dct_patch_ctr = { 't2': { 'size': (80, 80), 'mean': 51.1417, 'std': 57.4408 }, 't2s': { 'size': (80, 80), 'mean': 68.8591, 'std': 71.4659 }, 't1': { 'size': (80, 80), 'mean': 55.7359, 'std': 64.3149 }, 'dwi': { 'size': (80, 80), 'mean': 55.744, 'std': 45.003 } } dct_params_ctr = { 't2': { 'features': 16, 'dilation_layers': 2 }, 't2s': { 'features': 8, 'dilation_layers': 3 }, 't1': { 'features': 24, 'dilation_layers': 3 }, 'dwi': { 'features': 8, 'dilation_layers': 2 } } # load model ctr_model_fname = os.path.join(path_sct, 'data', 'deepseg_sc_models', '{}_ctr.h5'.format(contrast_type)) ctr_model = nn_architecture_ctr( height=dct_patch_ctr[contrast_type]['size'][0], width=dct_patch_ctr[contrast_type]['size'][1], channels=1, classes=1, features=dct_params_ctr[contrast_type]['features'], depth=2, temperature=1.0, padding='same', batchnorm=True, dropout=0.0, dilation_layers=dct_params_ctr[contrast_type]['dilation_layers']) ctr_model.load_weights(ctr_model_fname) sct.log.info("Resample the image to 0.5 mm isotropic resolution...") fname_res = sct.add_suffix(image_fname, '_resampled') input_resolution = Image(image_fname).dim[4:7] new_resolution = 'x'.join(['0.5', '0.5', str(input_resolution[2])]) spinalcordtoolbox.resample.nipy_resample.resample_file(image_fname, fname_res, new_resolution, 'mm', 'linear', verbose=0) # compute the heatmap fname_heatmap = sct.add_suffix(image_fname, "_heatmap") img_filename = ''.join(sct.extract_fname(fname_heatmap)[:2]) fname_heatmap_nii = img_filename + '.nii' z_max = heatmap(filename_in=fname_res, filename_out=fname_heatmap_nii, model=ctr_model, patch_shape=dct_patch_ctr[contrast_type]['size'], mean_train=dct_patch_ctr[contrast_type]['mean'], std_train=dct_patch_ctr[contrast_type]['std'], brain_bool=brain_bool) # run optic on the heatmap centerline_filename = sct.add_suffix(fname_heatmap, "_ctr") heatmap2optic(fname_heatmap=fname_heatmap_nii, lambda_value=7 if contrast_type == 't2s' else 1, fname_out=centerline_filename, z_max=z_max if brain_bool else None) elif algo == 'viewer': centerline_filename = sct.add_suffix(image_fname, "_ctr") fname_labels_viewer = _call_viewer_centerline(fname_in=image_fname) centerline_filename = extract_centerline(fname_labels_viewer, remove_temp_files=True, algo_fitting='nurbs', nurbs_pts_number=8000) elif algo == 'manual': centerline_filename = sct.add_suffix(image_fname, "_ctr") image_manual_centerline = Image(centerline_fname) # Re-orient and Re-sample the manual centerline msct_image.change_orientation(image_manual_centerline, 'RPI').save(centerline_filename) else: sct.log.error( 'The parameter "-centerline" is incorrect. Please try again.') sys.exit(1) if algo != 'cnn': sct.log.info("Resample the image to 0.5 mm isotropic resolution...") fname_res = sct.add_suffix(image_fname, '_resampled') input_resolution = Image(image_fname).dim[4:7] new_resolution = 'x'.join(['0.5', '0.5', str(input_resolution[2])]) spinalcordtoolbox.resample.nipy_resample.resample_file(image_fname, fname_res, new_resolution, 'mm', 'linear', verbose=0) spinalcordtoolbox.resample.nipy_resample.resample_file( centerline_filename, centerline_filename, new_resolution, 'mm', 'linear', verbose=0) if bool_2d: from sct_image import split_data im_split_lst = split_data(Image(centerline_filename), dim=2) im_split_lst[0].save(centerline_filename) return fname_res, centerline_filename