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 gen_qc(path_qc): t2_image = sct_test_path('t2', 't2.nii.gz') t2_seg = sct_test_path('t2', 't2_seg-manual.nii.gz') qc.generate_qc(fname_in1=t2_image, fname_seg=t2_seg, path_qc=path_qc, process="sct_deepseg_gm")
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 dmri_single_volumes(tmp_path): """ Create .nii.gz, bvals, and bvecs files for individual dMRI volumes. This prep is necessary because `sct_testing_data` lacks individual bvals and bvecs files for the `dmri_T000#.nii.gz` files, and sct_dmri_separate_b0_and_dwi won't generate new bvals/bvecs file either. """ # Get all bvals/bvecs corresponding to individual dMRI volumes bvals, bvecs = read_bvals_bvecs(sct_test_path('dmri', 'bvals.txt'), sct_test_path('dmri', 'bvecs.txt')) fname_dmri_imgs, fname_bvals, fname_bvecs = [], [], [] for i, (bval, bvec) in enumerate(zip(bvals, bvecs)): # 1. Use existing single-volume dMRI files from sct_testing_data fname_dmri_imgs.append(sct_test_path('dmri', f'dmri_T000{i}.nii.gz')) # 2. Copy bval into single-volume bvals file fname_bval = str(tmp_path / f'bvals_T000{i}.txt') fname_bvals.append(fname_bval) with open(fname_bval, 'w') as f: f.write(str(bval)) # 3. Copy bvec into single-volume bvecs file fname_bvec = str(tmp_path / f'bvecs_T000{i}.txt') fname_bvecs.append(fname_bvec) with open(fname_bvec, 'w') as f: f.write(' '.join(map(str, bvec))) return fname_dmri_imgs, fname_bvals, fname_bvecs
def test_straighten(): """Test straightening with default params""" fname_t2 = sct_test_path('t2', 't2.nii.gz') # sct_download_data -d sct_testing_data fname_t2_seg = sct_test_path('t2', 't2_seg-manual.nii.gz') sc_straight = SpinalCordStraightener(fname_t2, fname_t2_seg) sc_straight.accuracy_results = True sc_straight.straighten() assert sc_straight.mse_straightening < 0.8 assert sc_straight.max_distance_straightening < 1.2
def template_lpi(tmp_path_factory): """Change orientation of test data template to LPI.""" path_out = str( tmp_path_factory.mktemp("tmp_data") / 'template_lpi') # tmp_path_factory is needed for module scope shutil.copytree(sct_test_path('template'), path_out) for file in glob.glob(sct_test_path('template_lpi', 'template', '*.nii.gz')): nii = Image(file) nii.change_orientation('LPI') nii.save(file) return path_out
def test_deep_segmentation_spinalcord(params): """High level segmentation API""" fname_im = sct_test_path('t2', 't2.nii.gz') fname_centerline_manual = sct_test_path('t2', 't2_centerline-manual.nii.gz') # Set at channels_first in test_deepseg_lesion.test_segment() K.set_image_data_format("channels_last") # Call segmentation function im_seg, _, _ = sct.deepseg_sc.core.deep_segmentation_spinalcord( Image(fname_im), params['contrast'], ctr_algo='file', ctr_file=fname_centerline_manual, brain_bool=False, kernel_size=params['kernel'], threshold_seg=0.5) assert im_seg.data.dtype == np.dtype('uint8') # Compare with ground-truth segmentation assert np.all(im_seg.data == Image(params['fname_seg_manual']).data)
def test_sct_deepseg_sc_check_output_exists(tmp_path): fname_out = str(tmp_path / 'test_seg.nii.gz') sct_deepseg_sc.main(argv=[ '-i', sct_test_path('t2', 't2.nii.gz'), '-c', 't2', '-o', fname_out ]) assert os.path.isfile(fname_out)
def test_sct_analyze_lesion_matches_expected_dummy_lesion_measurements(dummy_lesion, rtol, tmp_path): """Run the CLI script and verify that the lesion measurements match expected values.""" # Run the analysis on the dummy lesion file path_lesion, expected_measurements = dummy_lesion sct_analyze_lesion.main(argv=['-m', path_lesion, '-s', sct_test_path("t2", "t2_seg-manual.nii.gz"), '-ofolder', str(tmp_path)]) # Load analysis results from pickled pandas.Dataframe _, fname, _ = extract_fname(path_lesion) with open(tmp_path/f"{fname}_analyzis.pkl", 'rb') as f: measurements = pickle.load(f)['measures'] # Validate analysis results for key, expected_value in expected_measurements.items(): if key == 'volume [mm3]': np.testing.assert_equal(measurements.at[0, key], expected_value) else: # The length/diameter won't match exactly due to angle adjustment # from spinal cord centerline curvature np.testing.assert_allclose(measurements.at[0, key], expected_value, rtol=rtol) # The values will be adjusted according to the cos of the angle # between the spinal cord centerline and the S-I axis, as per: # https://github.com/spinalcordtoolbox/spinalcordtoolbox/pull/3681#discussion_r804822552 if key == 'max_equivalent_diameter [mm]': assert measurements.at[0, key] < expected_value elif key == 'length [mm]': assert measurements.at[0, key] > expected_value
def check_testing_data_integrity(files_checksums: Mapping[os.PathLike, str]): changed = [] new = [] missing = [] after = [] for root, _, files in os.walk(sct_test_path()): for f in files: fname = os.path.join(root, f) chksum = checksum(fname) after.append(fname) if fname not in files_checksums: logger.warning( f"Discovered new file in sct_testing_data that didn't exist before: {(fname, chksum)}" ) new.append((fname, chksum)) elif files_checksums[fname] != chksum: logger.error( f"Checksum mismatch for test data: {fname}. Got {chksum} instead of {files_checksums[fname]}" ) changed.append((fname, chksum)) for fname, chksum in files_checksums.items(): if fname not in after: logger.error(f"Test data missing after test:a: {fname}") missing.append((fname, chksum)) assert not changed # assert not new assert not missing
def download_data(request): # This is a hack because the capsys fixture can't be used with # session scope at the moment. # https://github.com/pytest-dev/pytest/issues/2704#issuecomment-603387680 capmanager = request.config.pluginmanager.getplugin("capturemanager") with capmanager.global_and_fixture_disabled(): print('\nDownloading sct testing data.') downloader.main(['-d', 'sct_testing_data', '-o', sct_test_path()])
def step_axial_data_in_same_space(): """ """ src = sct_test_path('mt', 'mt0_seg.nii.gz') dest = sct_test_path('mt', 'mt1_seg.nii.gz') step = Paramreg( step='1', type='seg', algo='slicereg', metric='MeanSquares', iter='5', ) cli_params = Param() cli_params.debug = 2 return src, dest, step, cli_params
def test_sct_register_to_template_non_rpi_template(tmp_path, template_lpi): """Test registration with option -ref subject when template is not RPI orientation, causing #3300.""" # Run registration to template using the RPI template as input file sct_register_to_template.main(argv=[ '-i', sct_test_path('template', 'template', 'PAM50_small_t2.nii.gz'), '-s', sct_test_path('template', 'template', 'PAM50_small_cord.nii.gz'), '-ldisc', sct_test_path('template', 'template', 'PAM50_small_label_disc.nii.gz'), '-c', 't2', '-t', template_lpi, '-ref', 'subject', '-param', 'step=1,type=seg,algo=centermass', '-ofolder', str(tmp_path), '-r', '0', '-v', '2' ]) img_orig = Image( sct_test_path('template', 'template', 'PAM50_small_t2.nii.gz')) img_reg = Image(str(tmp_path / 'template2anat.nii.gz')) # Check if both images almost overlap. If they are right-left flipped, distance should be above threshold assert np.linalg.norm(img_orig.data - img_reg.data) < 1
def test_data_integrity(request): files_checksums = dict() for root, _, files in os.walk(sct_test_path()): for f in files: fname = os.path.join(root, f) chksum = checksum(fname) files_checksums[fname] = chksum request.addfinalizer(lambda: check_testing_data_integrity(files_checksums))
def labeled_data_test_params(path_in=sct_test_path('t2', 't2.nii.gz'), path_seg=sct_test_path('t2', 'labels.nii.gz')): """Generate image/label pairs for various test cases of test_sagittal_slice_get_center_spit.""" im_in = Image(path_in) # Base anatomical image im_seg_labeled = Image(path_seg) # Base labeled segmentation assert np.count_nonzero(im_seg_labeled.data) >= 2, "Labeled segmentation image has fewer than 2 labels" # Create image with all but one label removed im_seg_one_label = im_seg_labeled.copy() for x, y, z in np.argwhere(im_seg_one_label.data)[1:]: im_seg_one_label.data[x, y, z] = 0 # Create image with no labels im_seg_no_labels = im_seg_labeled.copy() for x, y, z in np.argwhere(im_seg_no_labels.data): im_seg_no_labels.data[x, y, z] = 0 return [pytest.param(im_in, im_seg_labeled, id='multiple_labels'), pytest.param(im_in, im_seg_one_label, id='one_label'), pytest.param(im_in, im_seg_no_labels, id='no_labels')]
def test_sct_maths_symmetrize(dim, tmp_path): """Run the CLI script, then verify that symmetrize properly flips and averages the image data.""" path_in = sct_test_path('t2', 't2.nii.gz') path_out = str(tmp_path / f't2_sym_{dim}.nii.gz') sct_maths.main( argv=['-i', path_in, '-symmetrize', str(dim), '-o', path_out]) im_in = Image(path_out) im_out = Image(path_out) assert np.array_equal( im_out.data, (im_in.data + np.flip(im_in.data, axis=int(dim))) / 2.0)
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_register_step_ants_slice_regularized_registration( step_axial_data_in_same_space): src, dest, step, cli_params = step_axial_data_in_same_space warp_forward_out, warp_inverse_out = register_step_ants_slice_regularized_registration( src=src, dest=dest, step=step, metricSize='4') # Verify integrity of the output Tx Ty file txty_result = np.genfromtxt('step1TxTy_poly.csv', skip_header=1, delimiter=',') txty_groundtruth = np.genfromtxt(sct_test_path( 'mt', 'step1TxTy_poly_groundtruth.csv'), skip_header=1, delimiter=',') assert txty_result == pytest.approx(txty_groundtruth, abs=1e-14)
def test_register_step_ants_slice_regularized_registration( step_axial_data_in_same_space): src, dest, step, cli_params = step_axial_data_in_same_space outfiles = _, _, txty_csv_out = register_step_ants_slice_regularized_registration( src, dest, step, metricSize='4') # Verify integrity of the output Tx Ty file txty_result = np.genfromtxt(txty_csv_out, skip_header=1, delimiter=',') txty_groundtruth = np.genfromtxt(sct_test_path( 'mt', 'step1TxTy_poly_groundtruth.csv'), skip_header=1, delimiter=',') assert txty_result == pytest.approx(txty_groundtruth, abs=1e-14) # tmp_path can't be used here because the output files are generated by isct_antsSliceRegularizedRegistration, # which doesn't easily allow the output filepaths to be modified. Instead, remove manually. for file in outfiles: os.unlink(file)
def test_create_seg_mid(tmp_path): """Test the '-create-seg-mid' option in sct_label_utils.""" input = sct_test_path('t2', 't2_seg-manual.nii.gz') output = str(tmp_path / 't2_seg_labeled.nii.gz') # Create a single label using the new syntax sct_label_utils.main(['-i', input, '-create-seg-mid', '3', '-o', output]) output_img = Image(output) labels = np.argwhere(output_img.data) assert len(labels) == 1 # Ensure slice coordinate of label is centered at midpoint of I-S axis for coord, axis, shape in zip(labels[0], output_img.orientation, Image(output).data.shape): if axis in ['I', 'S']: assert coord == round(shape / 2) # Old syntax for this behavior should not be allowed with pytest.raises(DeprecationWarning): sct_label_utils.main( ['-i', input, '-create-seg', '-1,3', '-o', output])
def dummy_lesion(request, tmp_path): """Define a fake voxel lesion using the specified dimensions.""" starting_coord, dim = request.param # Format the coordinates into a str argument that `-create` can accept coordinates = [] for x in range(starting_coord[0], starting_coord[0] + dim[0]): for y in range(starting_coord[1], starting_coord[1] + dim[1]): for z in range(starting_coord[2], starting_coord[2] + dim[2]): coord = [str(x), str(y), str(z), "1"] coordinates.append(",".join(coord)) create_arg = ":".join(coordinates) # Create the lesion mask file and output to a temporary directory path_ref = sct_test_path("t2", "t2.nii.gz") path_out = str(tmp_path/"lesion.nii.gz") sct_label_utils.main(argv=['-i', path_ref, '-o', path_out, '-create', create_arg]) # Compute the expected (voxel) measurements from the provided dimensions # NB: Actual measurements will differ slightly due to spine curvature measurements = { # NB: 'sct_analyze_lesion' treats lesions as cylinders. So: # - Vertical axis: Length of the cylinder 'length [mm]': dim[1], # - Horizontal plane: Cross-sectional slices of the cylinder. # Specifically, 'max_equivalent_diameter' takes the # cross-sectional area of the lesion (which is computed # using square voxels), then finds the diameter of an # equivalent *circle* with that same area: # a = pi*r^2 # -> a = pi*(d/2)^2 # -> d = 2*sqrt(a/pi) 'max_equivalent_diameter [mm]': 2 * np.sqrt(dim[0] * dim[2] / np.pi), 'volume [mm3]': dim[0] * dim[1] * dim[2], } return path_out, measurements
def test_model_dict(): """ Make sure all fields are present in each model. :return: """ for key, value in sct.deepseg.models.MODELS.items(): assert('url' in value) assert('description' in value) assert('default' in value) # noinspection 801,PyShadowingNames @pytest.mark.parametrize('fname_image, fname_seg_manual, fname_out, task', [ (sct_test_path('t2s', 't2s.nii.gz'), sct_test_path('t2s', 't2s_seg-deepseg.nii.gz'), 't2s_seg_deepseg.nii.gz', 'seg_sc_t2star'), ]) def test_segment_nifti(fname_image, fname_seg_manual, fname_out, task, tmp_path): """ Uses the locally-installed sct_testing_data """ fname_out = str(tmp_path/fname_out) # tmp_path for automatic cleanup sct_deepseg.main(['-i', fname_image, '-task', task, '-o', fname_out]) # TODO: implement integrity test (for now, just checking if output segmentation file exists) # Make sure output file exists assert os.path.isfile(fname_out) # Compare with ground-truth segmentation
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()
from __future__ import print_function, absolute_import import os import pytest from spinalcordtoolbox.utils import sct_test_path import sct_compute_mtsat out_mstat = "out_mtsat.nii.gz" out_t1map = "out_t1map.nii.gz" INPUT_PARAMS = [ [ '-mt', sct_test_path('mt', 'mt1.nii.gz'), '-pd', sct_test_path('mt', 'mt0.nii.gz'), '-t1', sct_test_path('mt', 't1w.nii.gz'), '-omtsat', out_mstat, '-ot1map', out_t1map ], [ '-mt', sct_test_path('mt', 'mt1.nii.gz'), '-pd', sct_test_path('mt', 'mt0.nii.gz'), '-t1', sct_test_path('mt', 't1w.nii.gz'), '-omtsat', out_mstat, '-ot1map', out_t1map, '-trmt', '51', '-trpd', '52', '-trt1', '10', '-famt', '4', '-fapd', '5', '-fat1', '14' ], ]
def test_sct_image_display_warp_check_output_exists(): """Run the CLI script and check that the warp image file was created.""" fname_in = 'warp_template2anat.nii.gz' fname_out = 'grid_3_resample_' + fname_in sct_image.main(argv=['-i', sct_test_path('t2', fname_in), '-display-warp']) assert os.path.exists(sct_test_path('t2', fname_out))
def test_sct_dmri_display_bvecs_png_exists_bvec_bval_inputs(): """Run the CLI script.""" sct_dmri_display_bvecs.main(argv=['-bvec', sct_test_path('dmri', 'bvecs.txt'), '-bval', sct_test_path('dmri', 'bvals.txt')]) assert os.path.exists('bvecs.png') os.unlink('bvecs.png')
'rmse': 0.3, 'laplacian': 0.5, 'norm': 3.6 }, {}), (dummy_centerline(size_arr=(30, 20, 50), subsampling=10), { 'median': 0, 'rmse': 0.1, 'laplacian': 0.5, 'norm': 3.8 }, {}), ] param_optic = [ ({ 'fname_image': sct_test_path('t2', 't2.nii.gz'), 'contrast': 't2', 'fname_centerline-optic': sct_test_path('t2', 't2_centerline-optic.nii.gz') }), ({ 'fname_image': sct_test_path('t2s', 't2s.nii.gz'), 'contrast': 't2s', 'fname_centerline-optic': sct_test_path('t2s/t2s_centerline-optic.nii.gz') }), ({ 'fname_image': sct_test_path('dmri', 'dwi_mean.nii.gz'), 'contrast':
import logging from time import time import numpy as np import pytest import spinalcordtoolbox.labels as sct_labels from spinalcordtoolbox.image import Image, zeros_like from spinalcordtoolbox.utils import sct_test_path from spinalcordtoolbox.types import Coordinate from test_image import fake_3dimage, fake_3dimage2 logger = logging.getLogger(__name__) seg_img = Image(sct_test_path('t2', 't2_seg-manual.nii.gz')) t2_img = Image(sct_test_path('t2', 't2.nii.gz')) labels_img = Image(sct_test_path('t2', 'labels.nii.gz')) # TODO [AJ] investigate how to parametrize fixtures from test_image.py # without redefining the function here def fake_3dimage_sct2(): """ :return: an Image (3D) in RAS+ (aka SCT LPI) space shape = (1,2,3) """ i = fake_3dimage2() img = Image( i.get_data(), hdr=i.header,
# pytest unit tests for spinalcordtoolbox.deepseg_sc import pytest import numpy as np import nibabel as nib from keras import backend as K import spinalcordtoolbox as sct from spinalcordtoolbox.image import Image import spinalcordtoolbox.deepseg_sc.core from spinalcordtoolbox.testing.create_test_data import dummy_centerline from spinalcordtoolbox.utils import sct_test_path param_deepseg = [ ({ 'fname_seg_manual': sct_test_path('t2', 't2_seg-deepseg_sc-2d.nii.gz'), 'contrast': 't2', 'kernel': '2d' }), ({ 'fname_seg_manual': sct_test_path('t2', 't2_seg-deepseg_sc-3d.nii.gz'), 'contrast': 't2', 'kernel': '3d' }), ] # noinspection 801,PyShadowingNames @pytest.mark.parametrize('params', param_deepseg) def test_deep_segmentation_spinalcord(params): """High level segmentation API"""
def test_sct_propseg_o_flag(tmp_path): argv = ['-i', sct_test_path('t2', 't2.nii.gz'), '-c', 't2', '-ofolder', str(tmp_path), '-o', 'test_seg.nii.gz'] sct_propseg.main(argv) output_files = sorted([f for f in os.listdir(tmp_path) if os.path.isfile(os.path.join(tmp_path, f))]) assert output_files == ['t2_centerline.nii.gz', 'test_seg.nii.gz']