def test_nrrd_dwi_roundtrip(test_nrrd_path): """DWI NRRD round-trip test - loads and saves a NRRD file via Slicer's I/O, twice - checks the node values against the original file each time """ import tempfile # load and re-save NRRD once storagenode1 = slicer.vtkMRMLNRRDStorageNode() storagenode1.SetFileName(test_nrrd_path) dw_node1 = slicer.vtkMRMLDiffusionWeightedVolumeNode() storagenode1.ReadData(dw_node1) __f_tmp_nrrd1 = tempfile.NamedTemporaryFile(suffix=".nhdr", dir=tmp_dir, delete=False) tmp_nrrd1 = __f_tmp_nrrd1.name storagenode1.SetFileName(tmp_nrrd1) storagenode1.WriteData(dw_node1) parsed_nrrd2, dw_node2 = test_nrrd_dwi_load(test_nrrd_path, tmp_nrrd1) # re-save NRRD again storagenode2 = slicer.vtkMRMLNRRDStorageNode() __f_tmp_nrrd2 = tempfile.NamedTemporaryFile(suffix=".nhdr", dir=tmp_dir, delete=False) tmp_nrrd2 = __f_tmp_nrrd2.name storagenode2.SetFileName(tmp_nrrd2) storagenode2.WriteData(dw_node2) # test twice-saved file against original NRRD parsed_nrrd3, dw_node3 = test_nrrd_dwi_load(test_nrrd_path, tmp_nrrd2)
def run_extract_to_bvals(tempdata, args): """ returns array of bvalues """ print("ExtractDWIShell runtests, running: " + repr(args)) proc = subprocess.Popen(args) proc.wait() if proc.returncode != 0: print("ExtractDWIShells failed!") sys.exit(-1) # load NRRD into Slicer sn = slicer.vtkMRMLNRRDStorageNode() sn.SetFileName(tempdata) dw_node = slicer.vtkMRMLDiffusionWeightedVolumeNode() sn.ReadData(dw_node) bvals = vtk.util.numpy_support.vtk_to_numpy(dw_node.GetBValues()) return bvals
def test_nrrd_dwi_load(first_file, second_file=None): """ - load a DWI NRRD file into Slicer - validate b values and gradient vectors against original header - check the values in the vtkMRMLDiffusionWeightedVolumeNode - check the values in the vtkMRMLDWVNode attribute dictionary """ if second_file is None: second_file = first_file # load NRRD into Slicer storagenode = slicer.vtkMRMLNRRDStorageNode() storagenode.SetFileName(first_file) dw_node = slicer.vtkMRMLDiffusionWeightedVolumeNode() storagenode.ReadData(dw_node) slicer_grads = numpy_support.vtk_to_numpy(dw_node.GetDiffusionGradients()) slicer_numgrads = slicer_grads.shape[0] # load NRRD with pure-python parser parsed_nrrd = parse_nhdr(second_file) ################################## # 1) check the number of gradients assert (len(parsed_nrrd.gradients) == slicer_numgrads) ################################## # 2) check the node b values and gradients are correct # Note: vtkDataArray.GetMaxNorm gives max for scalar array. # max b value from the node nose.tools.assert_equal(parsed_nrrd.bvalue, dw_node.GetBValues().GetMaxNorm()) max_parsed_grad_norm = np.max( np.apply_along_axis(np.linalg.norm, 1, parsed_nrrd.gradients)) for i in range(0, slicer_numgrads): g_parsed_raw = parsed_nrrd.gradients[i] g_parsed_normed = normalize(g_parsed_raw) bval_parsed = parsed_nrrd.bvalue * pow( np.linalg.norm(g_parsed_raw) / max_parsed_grad_norm, 2) np.testing.assert_almost_equal( bval_parsed, dw_node.GetBValue(i), decimal=7, err_msg="MRMLNode b value does not match NRRD header") g_from_node = slicer_grads[i, :] # gradients stored in the vtkMRMLDiffusionWeightedVolumeNode must be *normalized*. np.testing.assert_allclose(np.linalg.norm(g_parsed_normed - g_from_node), 0.0, atol=1e-15) # b value from the node attribute dictionary np.testing.assert_equal(parsed_nrrd.bvalue, float(dw_node.GetAttribute("DWMRI_b-value"))) # 3) check gradients in the node attribute dictionary # gradients must match the value on-disk. for i in range(0, slicer_numgrads): grad_key = f"DWMRI_gradient_{i:04d}" parsed_gradient = np.fromstring(parsed_nrrd.header[grad_key], count=3, sep=' ', dtype=np.float64) attr_gradient = np.fromstring(dw_node.GetAttribute(grad_key), count=3, sep=' ', dtype=np.float64) np.testing.assert_array_almost_equal( parsed_gradient, attr_gradient, decimal=12, err_msg= "NHDR gradient does not match gradient in node attribute dictionary" ) return (parsed_nrrd, dw_node)
def main(): if "--test" in sys.argv: runtests(sys.argv[2]) sys.exit(-1) # default fail, runtests must exit(0) # handle arguments parser = argparse.ArgumentParser('Process args') parser.add_argument('--inputDWI', required=True, type=str) parser.add_argument('--bvalues', required=True, type=str) parser.add_argument('--tolerance', required=True, type=float) parser.add_argument('--outputDWI', required=True, type=str) parser.add_argument('--baseline_clamp', required=False, type=str) parser.add_argument('--test') args = parser.parse_args(sys.argv[1:]) dwifile = args.inputDWI outfile = args.outputDWI target_bvals = [float(bvalue) for bvalue in args.bvalues.split(',')] if args.baseline_clamp: bval_clamp = float(args.baseline_clamp) bval_tolerance = args.tolerance # load data sn = slicer.vtkMRMLNRRDStorageNode() sn.SetFileName(dwifile) node_in = mrml.vtkMRMLDiffusionWeightedVolumeNode() print("loading: ", dwifile) sn.ReadData(node_in) dwi_in = slicer.util.arrayFromVolume(node_in) # sanity check that the last axis is volumes assert (node_in.GetNumberOfGradients() == dwi_in.shape[-1] ), "Number of gradients do not match the size of last image axis!" bvals_in = numpy_support.vtk_to_numpy(node_in.GetBValues()) grads_in = numpy_support.vtk_to_numpy(node_in.GetDiffusionGradients()) print(" raw input gradients: ") print(grads_in) print(" raw input bvals: ") print(bvals_in) for (i, g) in enumerate(grads_in): norm = np.linalg.norm(g) if norm > 1e-6: grads_in[i] = g * 1 / norm # select the indices to keep based on b value indices = [] for (i, bval) in enumerate(bvals_in): for check_bval in target_bvals: if abs(bval - check_bval) < bval_tolerance: indices.append(i) print("selected indices: ", indices) # output shape: (3d_vol_shape..., num_indices) num_indices = len(indices) shape_out = dwi_in.shape[:-1] + (num_indices, ) print("input shape: ", dwi_in.shape) print("output shape: ", shape_out) # construct output subset vol_out = np.zeros(shape_out, dtype=dwi_in.dtype) grads_out = np.zeros((num_indices, 3)) bvals_out = np.zeros(num_indices) for (new_i, index) in enumerate(indices): vol_out[:, :, :, new_i] = dwi_in[:, :, :, index] grads_out[new_i, :] = grads_in[index, :] bvals_out[new_i] = bvals_in[index] if args.baseline_clamp: for (i, bval) in enumerate(bvals_out): if bval < bval_clamp: print(" clamping baseline {} (gradient {}) to zero".format( bval, grads_out[i, :])) bvals_out[i] = 0 grads_out[i, :] = np.array([0., 0., 0.]) print("selected bvals: ", bvals_out) print(" grads_out shape: ", grads_out.shape) print(" vol_out shape: ", vol_out.shape) print(" output gradients: ", grads_out) # write output sn_out = slicer.vtkMRMLNRRDStorageNode() sn_out.SetFileName(outfile) node_out = mrml.vtkMRMLDiffusionWeightedVolumeNode() # copy image information node_out.Copy(node_in) # reset the attribute dictionary, otherwise it will be transferred over attrs = vtk.vtkStringArray() node_out.GetAttributeNames(attrs) for i in range(0, attrs.GetNumberOfValues()): node_out.SetAttribute(attrs.GetValue(i), None) # reset the data array to force resizing, otherwise we will just keep the old data too node_out.SetAndObserveImageData(None) slicer.util.updateVolumeFromArray(node_out, vol_out) node_out.SetNumberOfGradients(num_indices) node_out.SetBValues(numpy_support.numpy_to_vtk(bvals_out)) node_out.SetDiffusionGradients(numpy_support.numpy_to_vtk(grads_out)) node_out.Modified() sn_out.WriteData(node_out)
def test_nrrd_dwi_load(first_file, second_file=None): """ - load a DWI NRRD file into Slicer - validate b values and gradient vectors against original header - check the values in the vtkMRMLDiffusionWeightedVolumeNode - check the values in the vtkMRMLDWVNode attribute dictionary """ if second_file is None: second_file = first_file # load NRRD into Slicer storagenode = slicer.vtkMRMLNRRDStorageNode() storagenode.SetFileName(first_file) dw_node = slicer.vtkMRMLDiffusionWeightedVolumeNode() storagenode.ReadData(dw_node) slicer_grads = numpy_support.vtk_to_numpy(dw_node.GetDiffusionGradients()) slicer_numgrads = slicer_grads.shape[0] # load NRRD with pure-python parser parsed_nrrd = parse_nhdr(second_file) ################################## # 1) check the number of gradients assert( len(parsed_nrrd.gradients) == slicer_numgrads ) ################################## # 2) check the node b values and gradients are correct # Note: vtkDataArray.GetMaxNorm gives max for scalar array. # max b value from the node nose.tools.assert_equal(parsed_nrrd.bvalue, dw_node.GetBValues().GetMaxNorm()) max_parsed_grad_norm = np.max(np.apply_along_axis(np.linalg.norm, 1, parsed_nrrd.gradients)) for i in range(0, slicer_numgrads): g_parsed_raw = parsed_nrrd.gradients[i] g_parsed_normed = normalize(g_parsed_raw) bval_parsed = parsed_nrrd.bvalue * pow(np.linalg.norm(g_parsed_raw) / max_parsed_grad_norm, 2) np.testing.assert_almost_equal(bval_parsed, dw_node.GetBValue(i), decimal=7, err_msg="MRMLNode b value does not match NRRD header") g_from_node = slicer_grads[i, :] # gradients stored in the vtkMRMLDiffusionWeightedVolumeNode must be *normalized*. np.testing.assert_allclose(np.linalg.norm(g_parsed_normed - g_from_node), 0.0, atol=1e-15) # b value from the node attribute dictionary np.testing.assert_equal(parsed_nrrd.bvalue, float(dw_node.GetAttribute("DWMRI_b-value"))) # 3) check gradients in the node attribute dictionary # gradients must match the value on-disk. for i in range(0, slicer_numgrads): grad_key = "DWMRI_gradient_{:04d}".format(i) parsed_gradient = np.fromstring(parsed_nrrd.header[grad_key], count=3, sep=' ', dtype=np.float64) attr_gradient = np.fromstring(dw_node.GetAttribute(grad_key), count=3, sep=' ', dtype=np.float64) np.testing.assert_array_almost_equal(parsed_gradient, attr_gradient, decimal=12, err_msg="NHDR gradient does not match gradient in node attribute dictionary") return (parsed_nrrd, dw_node)