def _resample(self, image, p_resample, type_img, image_ref=None): """ Resample at a fixed resolution to make sure the cord always appears with similar scale, regardless of the native resolution of the image. Assumes SAL orientation. :param image: Image() to resample :param p_resample: float: Resampling resolution in mm :param type_img: {'im', 'seg'}: If im, interpolate using spline. If seg, interpolate using linear then binarize. :param image_ref: Destination Image() to resample image to. :return: """ dict_interp = {'im': 'spline', 'seg': 'linear'} # Create nibabel object nii = Nifti1Image(image.data, image.hdr.get_best_affine()) img = nifti2nipy(nii) # If no reference image is provided, resample to specified resolution if image_ref is None: # Resample to px x p_resample x p_resample mm (orientation is SAL by convention in QC module) img_r = resample_nipy(img, new_size=str(image.dim[4]) + 'x' + str(p_resample) + 'x' + str(p_resample), new_size_type='mm', interpolation=dict_interp[type_img]) # Otherwise, resampling to the space of the reference image else: # Create nibabel object for reference image nii_ref = Nifti1Image(image_ref.data, image_ref.hdr.get_best_affine()) img_ref = nifti2nipy(nii_ref) img_r = resample_nipy(img, img_dest=img_ref, interpolation=dict_interp[type_img]) # If resampled image is a segmentation, binarize using threshold at 0.5 if type_img == 'seg': img_r_data = (img_r.get_data() > 0.5) * 1 else: img_r_data = img_r.get_data() nii_r = nipy2nifti(img_r) # Create Image objects image_r = Image(img_r_data, hdr=nii_r.header, dim=nii_r.header.get_data_shape()). \ change_orientation(image.orientation) return image_r
def _resample(self, image, p_resample, type, image_ref=None): """ Resample at a fixed resolution to make sure the cord always appears with similar scale, regardless of the native resolution of the image. Assumes SAL orientation. :param image: Image() to resample :param p_resample: float: Resampling resolution in mm :param type: {'im', 'seg'}: If im, interpolate using spline. If seg, interpolate using linear then binarize. :param image_ref: Destination Image() to resample image to. :return: """ # If no reference image is provided, create nipy object and resample using resample_nipy() if image_ref is None: dict_interp = {'im': 'spline', 'seg': 'linear'} # Create nibabel object nii = Nifti1Image(image.data, image.hdr.get_best_affine()) img = nifti2nipy(nii) # Resample to px x p_resample x p_resample mm (orientation is SAL by convention in QC module) img_r = resample_nipy(img, new_size=str(image.dim[4]) + 'x' + str(p_resample) + 'x' + str(p_resample), new_size_type='mm', interpolation=dict_interp[type]) # If segmentation, binarize using threshold at 0.5 if type == 'seg': img_r_data = (img_r.get_data() > 0.5) * 1 else: img_r_data = img_r.get_data() nii_r = nipy2nifti(img_r) # Create Image objects image_r = Image(img_r_data, hdr=nii_r.header, dim=nii_r.header.get_data_shape()). \ change_orientation(image.orientation) # If resampling to reference image, use Image() built-in resampling function to ref image else: dict_interp = {'im': 3, 'seg': 0} image_r = image.interpolate_from_image(image_ref, interpolation_mode=dict_interp[type], border='nearest') return image_r
def test_nipy_resample_image_4d(fake_4dimage_nipy): """Test resampling with 4D nipy image""" img_r = resampling.resample_nipy(fake_4dimage_nipy, new_size='2x2x1x1', new_size_type='factor', interpolation='nn') assert img_r.get_data().shape == (18, 18, 9, 3) assert img_r.get_data()[8, 8, 4, 0] == 1.0 # make sure there is no displacement in world coordinate system assert img_r.get_data()[8, 8, 4, 1] == 0.0 assert nipy2nifti(img_r).header.get_zooms() == (0.5, 0.5, 1.0, 1.0)
def test_nipy_resample_image_3d_to_dest(fake_3dimage_nipy, fake_3dimage_nipy_big): """Test resampling with 3D nipy image""" img_r = resampling.resample_nipy(fake_3dimage_nipy, img_dest=fake_3dimage_nipy_big, interpolation='linear') assert img_r.get_data().shape == (29, 39, 19) assert img_r.get_data()[4, 4, 4] == 1.0
def segment_file(input_filename, output_filename, model_name, threshold, verbosity, use_tta): """Segment a volume file. :param input_filename: the input filename. :param output_filename: the output filename. :param model_name: the name of model to use. :param threshold: threshold to apply in predictions (if None, no threshold will be applied) :param verbosity: the verbosity level. :param use_tta: whether it should use TTA (test-time augmentation) or not. :return: the output filename. """ nii_original = nipy.load_image(input_filename) pixdim = nii_original.header["pixdim"][3] target_resample = "0.25x0.25x{:.5f}".format(pixdim) nii_resampled = resampling.resample_nipy(nii_original, new_size=target_resample, new_size_type='mm', interpolation='linear', verbose=verbosity) nii_resampled = nipy2nifti(nii_resampled) pred_slices = segment_volume(nii_resampled, model_name, threshold, use_tta) original_res = "{:.5f}x{:.5f}x{:.5f}".format( nii_original.header["pixdim"][1], nii_original.header["pixdim"][2], nii_original.header["pixdim"][3]) volume_affine = nii_resampled.affine volume_header = nii_resampled.header nii_segmentation = nib.Nifti1Image(pred_slices, volume_affine, volume_header) nii_segmentation = nifti2nipy(nii_segmentation) nii_resampled_original = resampling.resample_nipy(nii_segmentation, new_size=original_res, new_size_type='mm', interpolation='linear', verbose=verbosity) res_data = nii_resampled_original.get_data() # Threshold after resampling, only if specified if threshold is not None: res_data = threshold_predictions(res_data, 0.5) nipy.save_image(nii_resampled_original, output_filename) return output_filename
def dummy_segmentation(size_arr=(256, 256, 256), pixdim=(1, 1, 1), dtype=np.float64, orientation='LPI', shape='rectangle', angle_RL=0, angle_AP=0, angle_IS=0, radius_RL=5.0, radius_AP=3.0, zeroslice=[], debug=False): """Create a dummy Image with a ellipse or ones running from top to bottom in the 3rd dimension, and rotate the image to make sure that compute_csa and compute_shape properly estimate the centerline angle. :param size_arr: tuple: (nx, ny, nz) :param pixdim: tuple: (px, py, pz) :param dtype: Numpy dtype. :param orientation: Orientation of the image. Default: LPI :param shape: {'rectangle', 'ellipse'} :param angle_RL: int: angle around RL axis (in deg) :param angle_AP: int: angle around AP axis (in deg) :param angle_IS: int: angle around IS axis (in deg) :param radius_RL: float: 1st radius. With a, b = 50.0, 30.0 (in mm), theoretical CSA of ellipse is 4712.4 :param radius_AP: float: 2nd radius :param zeroslice: list int: zero all slices listed in this param :param debug: Write temp files for debug :return: img: Image object """ # Initialization padding = 15 # Padding size (isotropic) to avoid edge effect during rotation # Create a 3d array, with dimensions corresponding to x: RL, y: AP, z: IS nx, ny, nz = [int(size_arr[i] * pixdim[i]) for i in range(3)] data = np.random.random((nx, ny, nz)) * 0. xx, yy = np.mgrid[:nx, :ny] # loop across slices and add object for iz in range(nz): if shape == 'rectangle': # theoretical CSA: (a*2+1)(b*2+1) data[:, :, iz] = ((abs(xx - nx / 2) <= radius_RL) & (abs(yy - ny / 2) <= radius_AP)) * 1 if shape == 'ellipse': data[:, :, iz] = (((xx - nx / 2) / radius_RL)**2 + ((yy - ny / 2) / radius_AP)**2 <= 1) * 1 # Pad to avoid edge effect during rotation data = np.pad(data, padding, 'reflect') # ROTATION ABOUT IS AXIS # rotate (in deg), and re-grid using linear interpolation data_rotIS = rotate(data, angle_IS, resize=False, center=None, order=1, mode='constant', cval=0, clip=False, preserve_range=False) # ROTATION ABOUT RL AXIS # Swap x-z axes (to make a rotation within y-z plane, because rotate will apply rotation on the first 2 dims) data_rotIS_swap = data_rotIS.swapaxes(0, 2) # rotate (in deg), and re-grid using linear interpolation data_rotIS_swap_rotRL = rotate(data_rotIS_swap, angle_RL, resize=False, center=None, order=1, mode='constant', cval=0, clip=False, preserve_range=False) # swap back data_rotIS_rotRL = data_rotIS_swap_rotRL.swapaxes(0, 2) # ROTATION ABOUT AP AXIS # Swap y-z axes (to make a rotation within x-z plane) data_rotIS_rotRL_swap = data_rotIS_rotRL.swapaxes(1, 2) # rotate (in deg), and re-grid using linear interpolation data_rotIS_rotRL_swap_rotAP = rotate(data_rotIS_rotRL_swap, angle_AP, resize=False, center=None, order=1, mode='constant', cval=0, clip=False, preserve_range=False) # swap back data_rot = data_rotIS_rotRL_swap_rotAP.swapaxes(1, 2) # Crop image (to remove padding) data_rot_crop = data_rot[padding:nx + padding, padding:ny + padding, padding:nz + padding] # Zero specified slices if zeroslice is not []: data_rot_crop[:, :, zeroslice] = 0 # Create nibabel object xform = np.eye(4) for i in range(3): xform[i][i] = 1 # in [mm] nii = nib.nifti1.Nifti1Image(data_rot_crop.astype('float32'), xform) # Create nipy object and resample to desired resolution nii_nipy = nifti2nipy(nii) nii_nipy_r = resample_nipy(nii_nipy, new_size='x'.join([str(i) for i in pixdim]), new_size_type='mm', interpolation='linear', dtype=dtype) nii_r = nipy2nifti(nii_nipy_r) # Create Image object. Default orientation is LPI. # For debugging add .save() at the end of the command below img = Image(nii_r.get_data(), hdr=nii_r.header, dim=nii_r.header.get_data_shape()) # Update orientation img.change_orientation(orientation) if debug: img.save('tmp_dummy_seg_' + datetime.now().strftime("%Y%m%d%H%M%S%f") + '.nii.gz') return img
def dummy_segmentation(size_arr=(256, 256, 256), pixdim=(1, 1, 1), dtype=np.float64, orientation='LPI', shape='rectangle', angle_RL=0, angle_AP=0, angle_IS=0, radius_RL=5.0, radius_AP=3.0, zeroslice=[], debug=False): """Create a dummy Image with a ellipse or ones running from top to bottom in the 3rd dimension, and rotate the image to make sure that compute_csa and compute_shape properly estimate the centerline angle. :param size_arr: tuple: (nx, ny, nz) :param pixdim: tuple: (px, py, pz) :param dtype: Numpy dtype. :param orientation: Orientation of the image. Default: LPI :param shape: {'rectangle', 'ellipse'} :param angle_RL: int: angle around RL axis (in deg) :param angle_AP: int: angle around AP axis (in deg) :param angle_IS: int: angle around IS axis (in deg) :param radius_RL: float: 1st radius. With a, b = 50.0, 30.0 (in mm), theoretical CSA of ellipse is 4712.4 :param radius_AP: float: 2nd radius :param zeroslice: list int: zero all slices listed in this param :param debug: Write temp files for debug :return: img: Image object """ # Initialization padding = 15 # Padding size (isotropic) to avoid edge effect during rotation # Create a 3d array, with dimensions corresponding to x: RL, y: AP, z: IS nx, ny, nz = [int(size_arr[i] * pixdim[i]) for i in range(3)] data = np.random.random((nx, ny, nz)) * 0. xx, yy = np.mgrid[:nx, :ny] # loop across slices and add object for iz in range(nz): if shape == 'rectangle': # theoretical CSA: (a*2+1)(b*2+1) data[:, :, iz] = ((abs(xx - nx / 2) <= radius_RL) & (abs(yy - ny / 2) <= radius_AP)) * 1 if shape == 'ellipse': data[:, :, iz] = (((xx - nx / 2) / radius_RL) ** 2 + ((yy - ny / 2) / radius_AP) ** 2 <= 1) * 1 # Pad to avoid edge effect during rotation data = np.pad(data, padding, 'reflect') # ROTATION ABOUT IS AXIS # rotate (in deg), and re-grid using linear interpolation data_rotIS = rotate(data, angle_IS, resize=False, center=None, order=1, mode='constant', cval=0, clip=False, preserve_range=False) # ROTATION ABOUT RL AXIS # Swap x-z axes (to make a rotation within y-z plane, because rotate will apply rotation on the first 2 dims) data_rotIS_swap = data_rotIS.swapaxes(0, 2) # rotate (in deg), and re-grid using linear interpolation data_rotIS_swap_rotRL = rotate(data_rotIS_swap, angle_RL, resize=False, center=None, order=1, mode='constant', cval=0, clip=False, preserve_range=False) # swap back data_rotIS_rotRL = data_rotIS_swap_rotRL.swapaxes(0, 2) # ROTATION ABOUT AP AXIS # Swap y-z axes (to make a rotation within x-z plane) data_rotIS_rotRL_swap = data_rotIS_rotRL.swapaxes(1, 2) # rotate (in deg), and re-grid using linear interpolation data_rotIS_rotRL_swap_rotAP = rotate(data_rotIS_rotRL_swap, angle_AP, resize=False, center=None, order=1, mode='constant', cval=0, clip=False, preserve_range=False) # swap back data_rot = data_rotIS_rotRL_swap_rotAP.swapaxes(1, 2) # Crop image (to remove padding) data_rot_crop = data_rot[padding:nx+padding, padding:ny+padding, padding:nz+padding] # Zero specified slices if zeroslice is not []: data_rot_crop[:, :, zeroslice] = 0 # Create nibabel object xform = np.eye(4) for i in range(3): xform[i][i] = 1 # in [mm] nii = nib.nifti1.Nifti1Image(data_rot_crop.astype('float32'), xform) # Create nipy object and resample to desired resolution nii_nipy = nifti2nipy(nii) nii_nipy_r = resample_nipy(nii_nipy, new_size='x'.join([str(i) for i in pixdim]), new_size_type='mm', interpolation='linear', dtype=dtype) nii_r = nipy2nifti(nii_nipy_r) # Create Image object. Default orientation is LPI. # For debugging add .save() at the end of the command below img = Image(nii_r.get_data(), hdr=nii_r.header, dim=nii_r.header.get_data_shape()) # Update orientation img.change_orientation(orientation) if debug: img.save('tmp_dummy_seg_'+datetime.now().strftime("%Y%m%d%H%M%S%f")+'.nii.gz') return img