def dmri_mask(tmp_path): """Mask image for testing.""" path_out = str(tmp_path / 'mask.nii') sct_create_mask.main(argv=['-i', 'dmri/dmri_T0000.nii.gz', '-p', 'center', '-size', '5mm', '-f', 'gaussian', '-o', path_out]) return path_out
def test_sct_register_multimodal_with_softmask(tmp_path): """ Verify that softmask is actually applied during registration. NB: For 'gaussian', ANTs binaries can't handle softmasks natively, so SCT should be applying the mask directly to the image. Related links: * https://github.com/ivadomed/pipeline-hemis/issues/3. * https://github.com/spinalcordtoolbox/spinalcordtoolbox/issues/3075 """ fname_mask = str(tmp_path / 'mask_t2.nii.gz') fname_t2 = sct_test_path('t2', 't2.nii.gz') fname_t1 = sct_test_path('t1', 't1w.nii.gz') fname_warp = str(tmp_path / "warp_t1w2t2.nii.gz") sct_create_mask.main([ '-i', fname_t2, '-p', f"centerline,{sct_test_path('t2', 't2_centerline-manual.nii.gz')}", '-o', fname_mask, '-f', 'gaussian' ]) sct_register_multimodal.main([ '-i', fname_t1, '-d', fname_t2, '-dseg', sct_test_path('t2', 't2_seg-manual.nii.gz'), '-param', "step=1,type=im,algo=slicereg,metric=CC", '-m', fname_mask, '-ofolder', str(tmp_path), '-r', '0', '-v', '2' ]) # If registration was successful, the warping field should be non-empty assert np.any(Image(fname_warp).data)
def test_sct_register_multimodal_mask_files_exist(tmp_path): """ Run the script without validating results. - TODO: Write a check that verifies the registration results. - TODO: Parametrize this test to add '-initwarpinv warp_anat2template.nii.gz', after the file is added to sct_testing_data: https://github.com/spinalcordtoolbox/spinalcordtoolbox/pull/3407#discussion_r646895013 """ fname_mask = str(tmp_path / 'mask_mt1.nii.gz') sct_create_mask.main([ '-i', sct_test_path('mt', 'mt1.nii.gz'), '-p', f"centerline,{sct_test_path('mt', 'mt1_seg.nii.gz')}", '-size', '35mm', '-f', 'cylinder', '-o', fname_mask ]) sct_register_multimodal.main([ '-i', sct_dir_local_path('data/PAM50/template/', 'PAM50_t2.nii.gz'), '-iseg', sct_dir_local_path('data/PAM50/template/', 'PAM50_cord.nii.gz'), '-d', sct_test_path('mt', 'mt1.nii.gz'), '-dseg', sct_test_path('mt', 'mt1_seg.nii.gz'), '-param', 'step=1,type=seg,algo=centermass:step=2,type=seg,algo=bsplinesyn,slicewise=1,iter=3', '-m', fname_mask, '-initwarp', sct_test_path('t2', 'warp_template2anat.nii.gz'), '-ofolder', str(tmp_path) ]) for path in ["PAM50_t2_reg.nii.gz", "warp_PAM50_t22mt1.nii.gz"]: assert os.path.exists(tmp_path / path) # Because `-initwarp` was specified (but `-initwarpinv` wasn't) the dest->seg files should NOT exist for path in ["mt1_reg.nii.gz", "warp_mt12PAM50_t2.nii.gz"]: assert not os.path.exists(tmp_path / path)
def test_sct_register_multimodal_mask_no_checks(tmp_path): """Run the script without validating results. TODO: Write a check that verifies the registration results as part of https://github.com/spinalcordtoolbox/spinalcordtoolbox/pull/3246.""" fname_mask = str(tmp_path / 'mask_mt1.nii.gz') sct_create_mask.main([ '-i', sct_test_path('mt', 'mt1.nii.gz'), '-p', f"centerline,{sct_test_path('mt', 'mt1_seg.nii.gz')}", '-size', '35mm', '-f', 'cylinder', '-o', fname_mask ]) sct_register_multimodal.main([ '-i', sct_dir_local_path('data/PAM50/template/', 'PAM50_t2.nii.gz'), '-iseg', sct_dir_local_path('data/PAM50/template/', 'PAM50_cord.nii.gz'), '-d', sct_test_path('mt', 'mt1.nii.gz'), '-dseg', sct_test_path('mt', 'mt1_seg.nii.gz'), '-param', 'step=1,type=seg,algo=centermass:step=2,type=seg,algo=bsplinesyn,slicewise=1,iter=3', '-m', fname_mask, '-initwarp', sct_test_path('t2', 'warp_template2anat.nii.gz') ])
def test_sct_create_mask_no_checks(path_input, process, size): """Run the CLI script without checking results. TODO: Check the results. (This test replaces the 'sct_testing' test, which did not implement any checks.)""" sct_create_mask.main(argv=['-i', path_input, '-p', process, '-size', size, '-r', '0'])
def pre_processing(fname_target, fname_sc_seg, fname_level=None, fname_manual_gmseg=None, new_res=0.3, square_size_size_mm=22.5, denoising=True, verbose=1, rm_tmp=True, for_model=False): printv('\nPre-process data...', verbose, 'normal') tmp_dir = tmp_create() copy(fname_target, tmp_dir) fname_target = ''.join(extract_fname(fname_target)[1:]) copy(fname_sc_seg, tmp_dir) fname_sc_seg = ''.join(extract_fname(fname_sc_seg)[1:]) curdir = os.getcwd() os.chdir(tmp_dir) original_info = { 'orientation': None, 'im_sc_seg_rpi': None, 'interpolated_images': [] } im_target = Image(fname_target).copy() im_sc_seg = Image(fname_sc_seg).copy() # get original orientation printv(' Reorient...', verbose, 'normal') original_info['orientation'] = im_target.orientation # assert images are in the same orientation assert im_target.orientation == im_sc_seg.orientation, "ERROR: the image to segment and it's SC segmentation are not in the same orientation" im_target_rpi = im_target.copy().change_orientation( 'RPI', generate_path=True).save() im_sc_seg_rpi = im_sc_seg.copy().change_orientation( 'RPI', generate_path=True).save() original_info['im_sc_seg_rpi'] = im_sc_seg_rpi.copy( ) # target image in RPI will be used to post-process segmentations # denoise using P. Coupe non local means algorithm (see [Manjon et al. JMRI 2010]) implemented in dipy if denoising: printv(' Denoise...', verbose, 'normal') # crop image before denoising to fasten denoising nx, ny, nz, nt, px, py, pz, pt = im_target_rpi.dim size_x, size_y = (square_size_size_mm + 1) / px, (square_size_size_mm + 1) / py size = int(np.ceil(max(size_x, size_y))) # create mask fname_mask = 'mask_pre_crop.nii.gz' sct_create_mask.main([ '-i', im_target_rpi.absolutepath, '-p', 'centerline,' + im_sc_seg_rpi.absolutepath, '-f', 'box', '-size', str(size), '-o', fname_mask ]) # crop image cropper = ImageCropper(im_target_rpi) cropper.get_bbox_from_mask(Image(fname_mask)) im_target_rpi_crop = cropper.crop() # crop segmentation cropper = ImageCropper(im_sc_seg_rpi) cropper.get_bbox_from_mask(Image(fname_mask)) im_sc_seg_rpi_crop = cropper.crop() # denoising from spinalcordtoolbox.math import denoise_nlmeans block_radius = 3 block_radius = int( im_target_rpi_crop.data.shape[2] / 2) if im_target_rpi_crop.data.shape[2] < (block_radius * 2) else block_radius patch_radius = block_radius - 1 data_denoised = denoise_nlmeans(im_target_rpi_crop.data, block_radius=block_radius, patch_radius=patch_radius) im_target_rpi_crop.data = data_denoised im_target_rpi = im_target_rpi_crop im_sc_seg_rpi = im_sc_seg_rpi_crop else: fname_mask = None # interpolate image to reference square image (resample and square crop centered on SC) printv(' Interpolate data to the model space...', verbose, 'normal') list_im_slices = interpolate_im_to_ref(im_target_rpi, im_sc_seg_rpi, new_res=new_res, sq_size_size_mm=square_size_size_mm) original_info[ 'interpolated_images'] = list_im_slices # list of images (not Slice() objects) printv(' Mask data using the spinal cord segmentation...', verbose, 'normal') list_sc_seg_slices = interpolate_im_to_ref( im_sc_seg_rpi, im_sc_seg_rpi, new_res=new_res, sq_size_size_mm=square_size_size_mm, interpolation_mode=1) for i in range(len(list_im_slices)): # list_im_slices[i].data[list_sc_seg_slices[i].data == 0] = 0 list_sc_seg_slices[i] = binarize(list_sc_seg_slices[i], thr_min=0.5, thr_max=1) list_im_slices[ i].data = list_im_slices[i].data * list_sc_seg_slices[i].data printv(' Split along rostro-caudal direction...', verbose, 'normal') list_slices_target = [ Slice(slice_id=i, im=im_slice.data, gm_seg=[], wm_seg=[]) for i, im_slice in enumerate(list_im_slices) ] # load vertebral levels if fname_level is not None: printv(' Load vertebral levels...', verbose, 'normal') # copy level file to tmp dir os.chdir(curdir) copy(fname_level, tmp_dir) os.chdir(tmp_dir) # change fname level to only file name (path = tmp dir now) fname_level = ''.join(extract_fname(fname_level)[1:]) # load levels list_slices_target = load_level(list_slices_target, fname_level) os.chdir(curdir) # load manual gmseg if there is one (model data) if fname_manual_gmseg is not None: printv('\n\tLoad manual GM segmentation(s) ...', verbose, 'normal') list_slices_target = load_manual_gmseg(list_slices_target, fname_manual_gmseg, tmp_dir, im_sc_seg_rpi, new_res, square_size_size_mm, for_model=for_model, fname_mask=fname_mask) if rm_tmp: # remove tmp folder rmtree(tmp_dir) return list_slices_target, original_info