def get_intersection_volume(roilist, xvoxel=1., yvoxel=1.): # There is probably a clever way to compute this by constructing # an "intersection contour" for each layer: for each contour, keep only # points that are inside all other contours in the list. But is tough to then # put those points in the right order. # Instead we'll just make a grid of points and get the volume of the combined mask. # With xvoxel and yvoxel the caller can tweak the voxel size of the mask in x and y. # In z the voxel size is given by the incoming ROIs. dz = min([r.dz for r in roilist]) assert (dz > 0) assert (xvoxel > 0) assert (yvoxel > 0) bb = bounding_box(bb=roilist[0].bb) for roi in roilist[1:]: bb.intersect(roi.bb) if bb.empty: # too bad return 0. spacing = np.array([xvoxel, yvoxel, dz], dtype=float) bb.add_margins(2 * spacing) dimsize = np.array(np.round((bb.maxcorner - bb.mincorner) / spacing), dtype=int) #img = sitk.Image(dimsize,sitk.sitkUInt8) img = itk.image_from_array(np.zeros(dimsize[::-2], dtype=np.uint8)) img.SetOrigin(bb.mincorner) img.SetSpacing(spacing) itkmask = itk.array_from_image(roilist[0].get_mask(img)) for roi in roilist[1:]: itkmask *= itk.array_from_image(roi.get_mask(img)) return np.sum(itkmask) * np.prod(spacing)
def test_three_float_3D_images(self): logger.info('Test_Product test_three_float_3D_images') nx, ny, nz = 2, 3, 4 minlog, maxlog = -5., 5. spacing = (421., 214., 142.) origin = (421421., 214214., 142142.) thirteen = 13.333 imglistF = [ itk.image_from_array( np.logspace(minlog, maxlog, nx * ny * nz).reshape(nx, ny, nz).astype(np.float32)), itk.image_from_array( np.logspace(minlog, maxlog, nx * ny * nz)[::-1].reshape( nx, ny, nz).astype(np.float32)), itk.image_from_array(thirteen * np.ones( (nx, ny, nz), dtype=np.float32)) ] for imgF in imglistF: imgF.SetSpacing(spacing) imgF.SetOrigin(origin) imgprodF = image_product(input_list=imglistF) self.assertTrue(np.allclose(itk.array_from_image(imgprodF), thirteen)) self.assertTrue(itk.array_from_image(imgprodF).shape == (nx, ny, nz)) self.assertTrue(np.allclose(imgprodF.GetSpacing(), spacing)) self.assertTrue(np.allclose(imgprodF.GetOrigin(), origin)) self.assertTrue(type(imgprodF) == itk.Image[itk.F, 3])
def faf_ACGM_image(gm, acf, factor=4.168696975): if gm.GetImageDimension() != 2: print("gm image dimension (" + str(gm.GetImageDimension()) + ") is not 2") sys.exit(1) if acf.GetImageDimension() != 2: print("acf image dimension (" + str(acf.GetImageDimension()) + ") is not 2") sys.exit(1) resampleACF = gt.applyTransformation(input=acf, like=gm, force_resample=True, pad=-1) resampleACFArray = itk.array_from_image(resampleACF) gmArray = itk.array_from_image(gm) acgmArray = np.zeros(gmArray.shape) negativeACFIndex = np.where(resampleACFArray == -1) positiveACFIndex = np.where(resampleACFArray != -1) acgmArray[negativeACFIndex] = factor * gmArray[negativeACFIndex] acgmArray[positiveACFIndex] = resampleACFArray[positiveACFIndex] * gmArray[ positiveACFIndex] acgmImage = itk.image_from_array(acgmArray) acgmImage.CopyInformation(gm) return acgmImage
def test_itk_registration(self): import os os.environ["FOOTSTEPS_NAME"] = "test" import footsteps icon_registration.test_utils.download_test_data() model = icon_registration.pretrained_models.OAI_knees_registration_model( pretrained=True) image_A = itk.imread( str(icon_registration.test_utils.TEST_DATA_DIR / "knees_diverse_sizes" / #"9126260_20060921_SAG_3D_DESS_LEFT_11309302_image.nii.gz") "9487462_20081003_SAG_3D_DESS_RIGHT_11495603_image.nii.gz")) image_B = itk.imread( str(icon_registration.test_utils.TEST_DATA_DIR / "knees_diverse_sizes" / "9225063_20090413_SAG_3D_DESS_RIGHT_12784112_image.nii.gz")) print(image_A.GetLargestPossibleRegion().GetSize()) print(image_B.GetLargestPossibleRegion().GetSize()) print(image_A.GetSpacing()) print(image_B.GetSpacing()) phi_AB, phi_BA = icon_registration.itk_wrapper.register_pair( model, image_A, image_B) assert (isinstance(phi_AB, itk.CompositeTransform)) interpolator = itk.LinearInterpolateImageFunction.New(image_A) warped_image_A = itk.resample_image_filter( image_A, transform=phi_AB, interpolator=interpolator, size=itk.size(image_B), output_spacing=itk.spacing(image_B), output_direction=image_B.GetDirection(), output_origin=image_B.GetOrigin()) plt.imshow( np.array(itk.checker_board_image_filter(warped_image_A, image_B))[40]) plt.colorbar() plt.savefig(footsteps.output_dir + "grid.png") plt.clf() plt.imshow(np.array(warped_image_A)[40]) plt.savefig(footsteps.output_dir + "warped.png") plt.clf() reference = np.load(icon_registration.test_utils.TEST_DATA_DIR / "warped.npy") np.save(footsteps.output_dir + "warped.npy", itk.array_from_image(warped_image_A)[40]) self.assertLess( np.mean( np.abs(reference - itk.array_from_image(warped_image_A)[40])), 1e-6)
def test_eight_3D_images(self): logger.info('Test_MinMax test_eight_3D_images') nx,ny,nz = 30,40,50 dmin,dmax = np.float32(-20.5), np.float32(31230.5) spacing = (321.,213.,132.) origin = (321321.,213213.,132132.) alist = [ np.random.uniform(dmin,dmax,nx*ny*nz).astype(np.float32) for i in range(8)] indices = np.arange(nx*ny*nz,dtype=np.uint32) imglist=list() for (j,a) in enumerate(alist): a[indices%8 == j] = dmin a[indices%8 == (j+4)%8] = dmax img=itk.image_from_array(a.reshape(nx,ny,nz).copy()) img.SetSpacing(spacing) img.SetOrigin(origin) imglist.append(img) imgmin = image_min(imglist) imgmax = image_max(imglist) self.assertTrue( type(imgmin) == itk.Image[itk.F,3]) self.assertTrue( type(imgmax) == itk.Image[itk.F,3]) self.assertTrue( np.allclose(itk.array_from_image(imgmin),dmin)) self.assertTrue( np.allclose(itk.array_from_image(imgmax),dmax)) self.assertTrue( np.allclose(imgmin.GetSpacing(),spacing)) self.assertTrue( np.allclose(imgmax.GetSpacing(),spacing)) self.assertTrue( np.allclose(imgmin.GetOrigin(),origin)) self.assertTrue( np.allclose(imgmax.GetOrigin(),origin))
def dilat(input, output): ''' Doc todo ''' print(input) ctvi = itk.imread(input) ImageType = type(ctvi) Dimension = ctvi.GetImageDimension() radiusValue = 1 StructuringElementType = itk.FlatStructuringElement[Dimension] structuringElement = StructuringElementType.Ball(radiusValue) grayscaleFilter = itk.GrayscaleDilateImageFilter[ ImageType, ImageType, StructuringElementType].New() grayscaleFilter.SetInput(ctvi) grayscaleFilter.SetKernel(structuringElement) grayscaleFilter.Update() o = grayscaleFilter.GetOutput() o = itk.array_from_image(o) c = itk.array_from_image(ctvi) o[c > 0.0] = 0 o = c + o o = itk.image_from_array(o) o.CopyInformation(ctvi) itk.imwrite(o, output)
def test_five_int_3D_images(self): logger.info('Test_Product test_five_int_3D_images') nx,ny,nz = 30,40,50 spacing = (321.,213.,132.) origin = (321321.,213213.,132132.) pval=np.ones(5)/5.0 p2=np.random.multinomial(1,pval,(nz,nx,ny)).swapaxes(0,3).copy() p3=np.random.multinomial(2,pval,(nz,nx,ny)).swapaxes(0,3).copy() p7=np.random.multinomial(2,pval,(nz,nx,ny)).swapaxes(0,3).copy() p13=np.random.multinomial(1,pval,(nz,nx,ny)).swapaxes(0,3).copy() p37=np.random.multinomial(1,pval,(nz,nx,ny)).swapaxes(0,3).copy() a0,a1,a2,a3,a4 = 2**p2*3**p3*7**p7*13**p13*37**p37 imglist = list() for a in (a0,a1,a2,a3,a4): img = itk.image_from_array(a.astype(ctypes.c_ulong)) img.SetSpacing( spacing ) img.SetOrigin( origin ) imglist.append(img) imgprodUS = image_product(input_list=imglist) answer = 424242 self.assertTrue( type(imgprodUS) == itk.Image[itk.UL,3]) self.assertTrue( (itk.array_from_image(imgprodUS) == answer).all() ) self.assertTrue( itk.array_from_image(imgprodUS).shape == (nx,ny,nz)) self.assertTrue( np.allclose(imgprodUS.GetSpacing(),spacing) ) self.assertTrue( np.allclose(imgprodUS.GetOrigin(),origin) )
def _mwr_with_loops(dose, mass, newgrid): """ Reference implementation, only for testing. This function computes a dose distribution using the geometry (origin, size, spacing) of the `newgrid` image, using the energy deposition and mass with some different geometry. A typical use case is that a Gate simulation first computes the dose w.r.t. a patient CT (exporting also the mass image), and then we want to resample this dose distribution to the geometry of the new grid, e.g. from the dose distribution computed by a TPS. """ assert (equal_geometry(dose, mass)) if equal_geometry(dose, newgrid): # If input and output geometry are equal, then we don't need to do anything, just copy the input dose. newdose = itk.image_from_array(itk.array_from_image(dose)) newdose.CopyInformation(dose) return newdose if not enclosing_geometry(dose, newgrid): # In a later release we may provide some smart code to deal with dose resampling outside of the input geometry. raise RuntimeError("new grid must be inside the old one") t0 = datetime.now() xol, yol, zol = [ _overlaps(*xyz) for xyz in zip(dose.GetOrigin(), dose.GetSpacing(), dose.GetLargestPossibleRegion().GetSize(), newgrid.GetOrigin(), newgrid.GetSpacing(), newgrid.GetLargestPossibleRegion().GetSize()) ] nxyz = np.array(dose.GetLargestPossibleRegion().GetSize()) mxyz = np.array(newgrid.GetLargestPossibleRegion().GetSize()) mzyx = mxyz[::-1].tolist() adose = itk.array_from_image(dose) amass = itk.array_from_image(mass) anew = np.zeros(mzyx, dtype=float) wsum = np.zeros(mzyx, dtype=float) N_ops = 0 # loop over nonzero overlaps of x-internvals for (ixs, ixd) in zip(*np.nonzero(xol)): dx = xol[ixs, ixd] # loop over nonzero overlaps of y-internvals for (iys, iyd) in zip(*np.nonzero(yol)): dy = yol[iys, iyd] # loop over nonzero overlaps of z-internvals for (izs, izd) in zip(*np.nonzero(zol)): dz = zol[izs, izd] w = dx * dy * dz * amass[izs, iys, ixs] anew[izd, iyd, ixd] += adose[izs, iys, ixs] * w wsum[izd, iyd, ixd] += w N_ops += 1 mask = (wsum > 0) anew[mask] /= wsum[mask] newdose = itk.image_from_array(anew) newdose.CopyInformation(newgrid) t1 = datetime.now() dt = (t1 - t0).total_seconds() logger.debug( f"resampling using explicit loops over nonzero voxel overlaps took {dt:.3f} seconds" ) return newdose
def test_big(self): resampled_loops = _mwr_with_loops(self.dose, self.mass, self.newdose) resampled = mass_weighted_resampling(self.dose, self.mass, self.newdose) self.assertTrue(equal_geometry(resampled_loops, self.newdose)) self.assertTrue(equal_geometry(resampled, self.newdose)) ar0 = itk.array_from_image(resampled_loops) ar1 = itk.array_from_image(resampled) self.assertTrue(np.allclose(ar0, ar1))
def faf_ACF_image(image, ctCoeff, spectCoeff, weight=None): if image.GetImageDimension() != 3: print("Image dimension (" + str(image.GetImageDimension()) + ") is not 3") sys.exit(1) if ctCoeff is None: print("ctCoeff is mandatory") sys.exit(1) elif len(ctCoeff) != 2: print("ctCoeff size (" + str(len(ctCoeff)) + ") is not 2") sys.exit(1) if weight is None: weight = [1] nbPeak = len(weight) if spectCoeff is None: print("spectCoeff is mandatory") sys.exit(1) elif len(spectCoeff) != 3 * nbPeak: print("ctCoeff size (" + str(len(spectCoeff)) + ") is not 3*nbPeak (" + str(3 * nbPeak) + ")") sys.exit(1) ctArray = itk.array_from_image(image) ctPositiveValueIndex = np.where(ctArray > 0) ctNegativeValueIndex = np.where(ctArray < 0) attenuation = np.zeros(ctArray.shape) for i in range(nbPeak): attenuationTemp = np.zeros(ctArray.shape) attenuationTemp[ctNegativeValueIndex] = spectCoeff[3 * i + 1] + ( spectCoeff[3 * i + 1] - spectCoeff[3 * i]) / 1000.0 * ctArray[ctNegativeValueIndex] attenuationTemp[ctPositiveValueIndex] = spectCoeff[ 3 * i + 1] + ctCoeff[0] / (ctCoeff[1] - ctCoeff[0]) * ( spectCoeff[3 * i + 2] - spectCoeff[3 * i + 1]) / 1000.0 * ctArray[ctPositiveValueIndex] attenuation = attenuation + weight[i] * attenuationTemp attenuation[attenuation < 0] = 0 attenuationImage = itk.image_from_array(attenuation) attenuationImage.CopyInformation(image) projectionAxis = 1 projection = image_projection.image_projection(attenuationImage, projectionAxis) acfArray = np.exp(image.GetSpacing()[projectionAxis] / (2.0 * 10.0) * itk.array_from_image(projection)) acfImage = itk.image_from_array(acfArray) acfImage.CopyInformation(projection) flipFilter = itk.FlipImageFilter.New(Input=acfImage) flipFilter.SetFlipAxes((False, True)) flipFilter.Update() acfImage = flipFilter.GetOutput() return acfImage
def read_images(folder, scale): print(folder) f = os.path.join(folder, 'dose-Edep.mhd') img = itk.imread(f) data = itk.array_from_image(img) data = data * scale f = os.path.join(folder, 'dose-Edep-Uncertainty.mhd') img_s = itk.imread(f) data_s = itk.array_from_image(img_s) return img, data, data_s
def image_filter_wrapper(*args, **kwargs): have_array_input = False have_xarray_input = False args_list = list(args) for index, arg in enumerate(args): if _HAVE_XARRAY and isinstance(arg, xr.DataArray): have_xarray_input = True image = itk.image_from_xarray(arg) args_list[index] = image elif is_arraylike(arg): have_array_input = True array = np.asarray(arg) image = itk.image_view_from_array(array) args_list[index] = image potential_image_input_kwargs = ('input', 'input1', 'input2', 'input3') for key, value in kwargs.items(): if (key.lower() in potential_image_input_kwargs or "image" in key.lower()): if _HAVE_XARRAY and isinstance(value, xr.DataArray): have_xarray_input = True image = itk.image_from_xarray(value) kwargs[key] = image elif is_arraylike(value): have_array_input = True array = np.asarray(value) image = itk.image_view_from_array(array) kwargs[key] = image if have_xarray_input or have_array_input: # Convert output itk.Image's to numpy.ndarray's output = image_filter(*tuple(args_list), **kwargs) if isinstance(output, tuple): output_list = list(output) for index, value in output_list: if isinstance(value, itk.Image): if have_xarray_input: data_array = itk.xarray_from_image(value) output_list[index] = data_array else: array = itk.array_from_image(value) output_list[index] = array return tuple(output_list) else: if isinstance(output, itk.Image): if have_xarray_input: output = itk.xarray_from_image(output) else: output = itk.array_from_image(output) return output else: return image_filter(*args, **kwargs)
def faf_lutetium_calibration(spect, ct, planar, injected_activity, delta_time): if spect.GetImageDimension() != 3: print("spect image dimension (" + str(spect.GetImageDimension()) + ") is not 3") sys.exit(1) if ct.GetImageDimension() != 3: print("ct image dimension (" + str(ct.GetImageDimension()) + ") is not 3") sys.exit(1) if planar.GetImageDimension() != 3: print("planar image dimension (" + str(planar.GetImageDimension()) + ") is not 3") sys.exit(1) if planar.GetLargestPossibleRegion().GetSize()[2] != 8: print("planar image dimension (" + str(planar.GetLargestPossibleRegion().GetSize()[2]) + ") is not 8") sys.exit(1) planarArray = itk.array_from_image(planar) tempArray1 = planarArray[2:4,:,:] tempArray2 = planarArray[6:8,:,:] planar208Array = np.concatenate([tempArray1, tempArray2], axis=0) planar208Image = itk.image_from_array(planar208Array) planar208Image.SetSpacing(planar.GetSpacing()) planar208Image.SetOrigin(planar.GetOrigin()) gmImage = faf_create_planar_geometrical_mean.faf_create_planar_geometrical_mean(planar208Image) registeredGmImage = faf_register_planar_image.faf_register_planar_image(gmImage, spect) acfImage = faf_ACF_image.faf_ACF_image(ct, [0.2068007, 0.57384408], [0.00014657, 0.13597229, 0.24070651]) acgmImage = faf_ACGM_image.faf_ACGM_image(registeredGmImage, acfImage) calibratedSpectImage, fafFactor = faf_calibration.faf_calibration(spect, acgmImage, injected_activity, 6.647*24,delta_time, 900, True) print("Calibration factor with FAF (Bq/count): " + str(fafFactor)) return calibratedSpectImage
def read_file(self, path): image = itk.imread(path, itk.F) data = itk.array_from_image(image) # type: np.ndarray data = data.astype(np.int) dim_x, dim_y, dim_z = data.shape return dim_x, dim_y, dim_z, data
def test_faf_calibration(self): gm = np.ones((32, 12)) * 11.2 spect = np.ones((16, 16, 6)) * 0.33 gmImage = itk.image_from_array(gm) gmImage.SetOrigin(np.array([-6.0, -16.0])) spectImage = itk.image_from_array(spect) spectImage.SetOrigin(np.array([-3.0, -8.0, -8.0])) calibratedSpectImage, calibrationFactor = faf_calibration(spectImage, gmImage, 1.0, half_life=4, delta_time=4, verbose=True) calibratedSpectArray = itk.array_from_image(calibratedSpectImage) self.assertTrue( calibratedSpectImage.GetLargestPossibleRegion().GetSize()[0] == 6) self.assertTrue( calibratedSpectImage.GetLargestPossibleRegion().GetSize()[1] == 16) self.assertTrue( calibratedSpectImage.GetLargestPossibleRegion().GetSize()[2] == 16) theoreticalcalibrationFactor = 1 / (6 * 16 * 16 * 0.33 / (0.5 * 0.25)) * 1000000 self.assertTrue( np.allclose(calibrationFactor, theoreticalcalibrationFactor)) self.assertTrue( np.allclose(calibratedSpectArray[4, 12], 0.33 * theoreticalcalibrationFactor / 1000000))
def Import_data(frames_path, masks_path): print("Import raw frames ...") # Read input image itk_image = itk.imread(frames_path) raw_frames = itk.array_from_image(itk_image).astype(np_.uint8) print("Import masks ...") with h5py.File(masks_path, 'r') as f: # List all groups print("Keys: %s" % f.keys()) a_group_key = list(f.keys())[0] # Get the data data_ = list(f[a_group_key]) masks=[] for mask in data_: masks.append(np_.reshape(mask-1,(mask.shape))) return raw_frames, masks
def label_ROI(roiDir, ROI_exp, allRoisView, roiBaseImage, label): #for ROI in fnmatch.filter(os.listdir(roiDir), ROI_exp): #print(f'{ROI_exp} {label}') found = False for ROI in os.listdir(roiDir): #print('here ' + ROI + ' ' + ROI_exp) if fnmatch.fnmatch(ROI.lower(), ROI_exp): #print('yes ' + ROI) print(os.path.join(roiDir, ROI)) roisImage = itk.imread(os.path.join(roiDir, ROI)) # resize roisImage = gt.applyTransformation(input=roisImage, like=roiBaseImage, force_resample=True, interpolation_mode='NN') #itk.imwrite(roisImage, f'resized_{ROI}') roisView = itk.array_from_image(roisImage) allRoisView += roisView * label if np.any(allRoisView > label): f = open(f'{taskfolder_3D}/errorlog.txt', 'a') f.write("Overlapping structures: " + str(int(allRoisView[allRoisView > label][0] - label)) + " and " + str(label) + "\n") f.close() print("Overlapping structures: " + str(int(allRoisView[allRoisView > label][0] - label)) + " and " + str(label)) allRoisView = np.where(allRoisView > label, label, allRoisView) #return allRoisView, False found = True #return allRoisView, True return allRoisView, found
def faf_create_planar_geometrical_mean(image): if image.GetImageDimension() != 3: print("Image dimension (" + str(image.image.GetImageDimension()) + ") is not 3") sys.exit(1) if image.GetLargestPossibleRegion().GetSize()[2] != 4: print("Image size (" + str(image.GetLargestPossibleRegion().GetSize()[2]) + ") is not 4") sys.exit(1) array = (itk.array_from_image(image)).astype(float) arrayAnt = array[0, :, :] - 1.1*array[2, :, :] arrayPost = array[1, :, :] - 1.1*array[3, :, :] arrayPost = np.flip(arrayPost, 1) arrayAnt[arrayAnt < 0] = 0 arrayPost[arrayPost < 0] = 0 outputArray = np.sqrt(arrayAnt*arrayPost) outputImage = itk.image_from_array(outputArray) spacing = np.array(image.GetSpacing()) spacing = np.delete(spacing, 2) outputImage.SetSpacing(spacing) origin = np.array(image.GetOrigin()) origin = np.delete(origin, 2) outputImage.SetOrigin(origin) return outputImage
def median_filter_with_mask(img, mask, radius_dilatation, radius_median): # debug debug = False if debug: input = img itk.imwrite(img, 'ctvi_before.mhd') ctvim = itk.median_image_filter(img, radius=radius_median) itk.imwrite(ctvim, 'ctvi_median.mhd') dimg = dilate_at_boundaries(img, radius_dilatation) if debug: itk.imwrite(dimg, 'ctvi_before_dilated.mhd') imgm = itk.median_image_filter(dimg, radius=radius_median) if debug: itk.imwrite(imgm, 'ctvi_median_after_dilated.mhd') # reapply mask after median imgm = itk.array_from_image(imgm) imgm = imgm.astype('float') imgm[mask == 0] = 0 if debug: imgm = imgm.astype(np.float32) a = itk.image_from_array(imgm) a.CopyInformation(input) itk.imwrite(a, 'ctvi_median_final.mhd') return imgm
def get_stats_struct(uncertpath, dicom_struct, struct_name): """Generate stats from a dose image and uncertainty image; but only for pixels inside specified structure """ uncertimg = itk.imread(uncertpath) uncert_flat = itk.array_from_image(uncertimg).flatten() ds = pydicom.dcmread(dicom_struct) aroi = roiutils.region_of_interest(ds, struct_name) mask = aroi.get_mask(uncertimg, corrected=False) mask_voxels_flat = itk.array_view_from_image(mask).flatten() relevant_uncerts = [] for i, val in enumerate(mask_voxels_flat): if val == 1: relevant_uncerts.append(uncert_flat[i]) relevant_uncerts = np.array(relevant_uncerts) print() print("Number of voxels in {} = {}".format(struct_name, len(relevant_uncerts))) print("Mean uncertainty = {}".format(relevant_uncerts.mean())) print("Median = {}".format(np.median(relevant_uncerts))) print("Max = {}".format(relevant_uncerts.max())) print("Min = {}".format(relevant_uncerts.min())) print("Std dev = {}".format(relevant_uncerts.std())) # Make histogram ymax = int(relevant_uncerts.max() * 100 + 2) binsize = 0.01 bins = [i * binsize for i in range(int(ymax / binsize))] plt.hist(100 * relevant_uncerts, bins=bins) plt.xlabel("Dose uncertainty (%)") plt.show()
def add(self, dose_file): lockfile = dose_file + ".lock" dose = None n_primaries = 0 t0 = datetime.now() logger.debug("lockfile exists" if os.path. exists(lockfile) else "lockfile does not exist") lock = SoftFileLock(lockfile) try: # TODO: the length of the timeout should maybe be configured in the system configuration with lock.acquire(timeout=3): t1 = datetime.now() logger.debug("acquiring lock file took {} seconds".format( (t1 - t0).total_seconds())) ########################## n_primaries = self.get_nprimaries(dose_file) if n_primaries < 1: logger.warn( f"dose file seems to be based on too few primaries ({n_primaries})" ) elif bool(self.mass) and bool(self.mask): simdose = itk.imread(dose_file) logger.debug( "resampling dose with size {} using mass file of size {} to target size {}" .format(itk.size(simdose), itk.size(self.mass), itk.size(self.mask))) dose = mass_weighted_resampling(simdose, self.mass, self.mask) del simdose else: dose = itk.imread(dose_file) logger.debug("read dose with size {}".format( itk.size(dose))) t2 = datetime.now() logger.debug( "acquiring dose data {} file took {} seconds".format( os.path.basename(dose_file), (t2 - t1).total_seconds())) if self.wmin > n_primaries: self.wmin = n_primaries if self.wmax < n_primaries: self.wmax = n_primaries except Timeout: logger.warn( "failed to acquire lock for {} for 3 seconds, giving up for now" .format(dose_file)) return if not bool(dose): logger.warn("skipping {}".format(dose_file)) return adose = itk.array_from_image(dose) if adose.shape != self.dosesum.shape: raise RuntimeError( "PROGRAMMING ERROR: dose shape {} differs from expected shape {}" .format(adose.shape, self.dosesum.shape)) self.dosesum += adose # n_primaries * (adose / n_primaries) self.dose2sum += adose**2 / n_primaries # n_primaries * (adose / n_primaries)**2 self.weightsum += n_primaries self.n += 1
def write_scaled_dose(mhdfile, output, scalefactor): """Scale provided dose image and save to output""" img = itk.imread(mhdfile) dose = itk.array_from_image(img) dosescaled = dose * scalefactor newimg = itk.image_view_from_array(dosescaled) newimg.CopyInformation(img) itk.imwrite(newimg, output)
def force_positive_directionality( image ): """Return altered mhd image with positive axes directionality Accepts path to mhd image or itk image object Returns mhd image """ #img = itk.imread(imgpath) img = None if type( image )==str: #Assume we have file path img = itk.imread( image ) else: #Assume we have itk image object img = image spacing = img.GetSpacing() origin = img.GetOrigin() size = img.GetLargestPossibleRegion().GetSize() direction = np.array( img.GetDirection()*[1,1,1] ) new_origin=[] for d,o,sz,sp in zip( direction, origin, size, spacing ): if d==-1: orig = o - (sz-1)*sp new_origin.append(orig) else: new_origin.append( o ) arr = itk.array_from_image( img ) rot_arr = None if np.array_equal( direction, np.array([-1,-1,1]) ): #(1,2 for z-axes) (2,1) opposite sense; take care for 90 degrees. rot_arr = np.rot90( arr, k=2, axes=(1,2) ) elif np.array_equal( direction, np.array([1,-1,-1]) ): rot_arr_1 = np.rot90( arr, k=2, axes=(1,2) ) rot_arr = np.rot90( rot_arr_1, k=2, axes=(0,2) ) #(0,2 for y-axis) elif np.array_equal( direction, np.array([1,1,1]) ): pass else: print("TransformMatrix (Direction) of image not handled correctly") print("Exiting") exit(0) if rot_arr is not None: #Image shape will change for 90 degree rotations (decubitis positions) new_img = itk.image_from_array( rot_arr ) new_img.CopyInformation(img) new_img.SetOrigin( new_origin ) new_img.SetDirection( np.array([[1,0,0],[0,1,0],[0,0,1]]) ) return new_img else: return img
def ctvi_slice_nbr_click(input, ct, mask, axis, slice_start, slice_step, slice_stop, output): ''' Doc todo ''' # read images ctvi_itk = itk.imread(input) ct = itk.imread(ct) mask = itk.imread(mask) ctvi = itk.array_from_image(ctvi_itk) ct = itk.array_from_image(ct) mask = itk.array_from_image(mask) print(ctvi.shape, ct.shape, mask.shape) # images MUST be the same size # spacing MUST be isotropic # slice if slice_start == -1: slice_start = int(ctvi.shape[1] / 2) if slice_stop == -1: slice_stop = slice_start + 1 elif slice_stop == -2: slice_stop = ctvi.shape[axis] - 1 print(slice_start, slice_step, slice_stop) # normalisation ? 90% percentile like in Kipritidis2019 ? print('min max', np.min(ctvi), np.max(ctvi)) t = np.quantile(ctvi[ctvi > 0], 0.9) print('Q90%', t) ctvi = ctvi / t # get colormap cmap, cmap_ct, norm = get_colormap1() # loop for slice s = slice_start while s < slice_stop: f = f'{output}_{s:03d}.png' ctvi_s = get_slice(ctvi, axis, s) ct_s = get_slice(ct, axis, s) mask_s = get_slice(mask, axis, s) save_img(f, ctvi_s, ct_s, mask_s, cmap, cmap_ct, norm) s = s + slice_step
def test_five_2D_images(self): logger.info('Test_Sum test_five_2D_images') nx,ny = 4,5 hundred = 100 thousand = 1000 spacing = (42.,24.) origin = (4242.,2424.) # float images imglistF = [ itk.image_from_array(np.arange(nx*ny,dtype=np.float32).reshape(nx,ny).copy()), itk.image_from_array(np.arange(nx*ny,dtype=np.float32)[::-1].reshape(nx,ny).copy()), itk.image_from_array(np.arange(0,nx*ny*hundred,hundred,dtype=np.float32).reshape(nx,ny).copy()), itk.image_from_array(np.arange(0,nx*ny*hundred,hundred,dtype=np.float32)[::-1].reshape(4,5).copy()), itk.image_from_array(thousand*np.ones((nx,ny),dtype=np.float32)) ] for imgF in imglistF: imgF.SetSpacing( spacing ) imgF.SetOrigin( origin ) imgsumF = image_sum(input_list=imglistF) logger.debug("got image with spacing {}".format(imgsumF.GetSpacing())) index = imgsumF.GetLargestPossibleRegion().GetSize() -1 logger.debug("get sum value {} while expecting {}".format(imgsumF.GetPixel(index),4.*5. -1.)) self.assertTrue( np.allclose(itk.array_view_from_image(imgsumF),nx*ny-1.+(nx*ny-1.)*hundred +thousand) ) # floats: approximate equality self.assertTrue( itk.array_from_image(imgsumF).shape == (nx,ny)) self.assertTrue( np.allclose(imgsumF.GetSpacing(),spacing)) self.assertTrue( np.allclose(imgsumF.GetOrigin(),origin)) # unsigned short int images ("US" in itk lingo) nx,ny = 40,50 ten = 10 thirteen = 13 spacing = (32.,23.) origin = (3232.,2323.) imglistUS = [ itk.image_from_array(np.arange(nx*ny,dtype=np.uint16).reshape(nx,ny).copy()), itk.image_from_array(np.arange(nx*ny,dtype=np.uint16)[::-1].reshape(nx,ny).copy()), itk.image_from_array(np.arange(0,ten*nx*ny,ten,dtype=np.uint16).reshape(nx,ny).copy()), itk.image_from_array(np.arange(0,ten*nx*ny,ten,dtype=np.uint16)[::-1].reshape(nx,ny).copy()), itk.image_from_array(thirteen*np.ones((nx,ny),dtype=np.uint16)) ] for imgUS in imglistUS: imgUS.SetSpacing( spacing ) imgUS.SetOrigin( origin ) imgsumUS = image_sum(input_list=imglistUS) logger.debug("got image with spacing {}".format(imgsumUS.GetSpacing())) logger.debug("get sum value {} while expecting {}".format(imgsumUS.GetPixel(index),40*50-1+10*40*50-10+13)) self.assertTrue( (itk.array_view_from_image(imgsumUS)==nx*ny-1+ten*nx*ny-ten+thirteen).all() ) # ints: exact equality self.assertTrue( itk.array_from_image(imgsumUS).shape == (nx,ny)) self.assertTrue( np.allclose(imgsumUS.GetSpacing(),spacing)) self.assertTrue( np.allclose(imgsumUS.GetOrigin(),origin))
def update_plan_dose(pdd, label, beam_dose_image): # 'pdd' is plan dose dictionary # label will be "unresampled", "Physical" or "RBE" if label in pdd: # add dose image to dose image in the dict a_plandose = itk.array_from_image(pdd[label]) a_beamdose = itk.array_view_from_image(beam_dose_image) assert (a_plandose.shape == a_beamdose.shape) a_plandose += a_beamdose img_plandose = itk.image_from_array(a_plandose) img_plandose.CopyInformation(pdd[label]) pdd[label] = img_plandose else: # copy dose image into dict a_plandose = itk.array_from_image(beam_dose_image) img_plandose = itk.image_from_array(a_plandose) img_plandose.CopyInformation(beam_dose_image) pdd[label] = img_plandose
def update_roi_characteristics(db, r, background=0): ''' Update roi volume, density and mass ''' im_roi = syd.find_one(db['Image'], roi_id=r['id']) dicom_struct = syd.find_one(db['DicomStruct'], id=r['dicom_struct_id']) if dicom_struct is not None: im_ct = syd.find_one(db['Image'], dicom_series_id=dicom_struct['dicom_series_id']) else: im_ct = syd.find_one( db['Image'], frame_of_reference_uid=r['frame_of_reference_uid'], modality='CT') file_img = syd.find_one(db['File'], id=im_roi['file_mhd_id']) if im_ct is not None: file_img_ct = syd.find_one(db['File'], id=im_ct['file_mhd_id']) else: print(f'Could not find the CT file image for the roi {r.id}') return 0 filename_im = os.path.join( db.absolute_data_folder, os.path.join(file_img['folder'], file_img['filename'])) filename_ct = os.path.join( db.absolute_data_folder, os.path.join(file_img_ct['folder'], file_img_ct['filename'])) roi = itk.imread(filename_im) ct = itk.imread(filename_ct) array_im = itk.array_from_image(roi) spacing = roi.GetSpacing() ### Volume ### e = np.count_nonzero(array_im != background) volume_elem = spacing[0] * spacing[1] * spacing[2] # spacing is in mm volume_elem = volume_elem * 0.001 # convert mm3 to cm3 (/1000) volume = e * volume_elem r['volume'] = volume syd.update_one(db['Roi'], r) ### Density ### mask = gt.applyTransformation(input=roi, like=ct, force_resample=True, interpolation_mode='NN') stats = gt.imageStatistics(input=ct, mask=mask) HU_mean = stats['mean'] density = 1 + (HU_mean / 1000) r['density'] = density syd.update_one(db['Roi'], r) ### Mass ### mass = np.multiply(volume, density) r['mass'] = mass syd.update_one(db['Roi'], r) return 1
def combine_let(dosefiles, letfiles, outname): """ Combine LET distributions from multiple simulations in a dose-weighted fashion NOTE: The order of dosefiles and letfiles must match, i.e the index corresponds to a specific simulation! """ #Store FLATTENED image arrays dosearrays = [] letarrays = [] # Get shape and image info img1 = itk.imread(dosefiles[0]) shape = itk.array_from_image(img1).shape if len(dosefiles) != len(letfiles): print("Unequal number of dose and LET files") if len(dosefiles) == 0: print("No files given to combine_let method") else: for f in dosefiles: dosearrays.append(itk.array_from_image(itk.imread(f)).flatten()) for f in letfiles: letarrays.append(itk.array_from_image(itk.imread(f)).flatten()) sumdose = sum(dosearrays) #itk.imread(dosefiles[0]) totlet = np.zeros(len(letarrays[0])) for i in range(len(totlet)): tot = 0 for j in range(len(dosearrays)): if sumdose[i] != 0: #TODO this ok for floats? tot = tot + letarrays[j][i] * (dosearrays[j][i] / sumdose[i]) totlet[i] = tot combinedlet = totlet.reshape(shape) letimg = itk.image_from_array(combinedlet.astype( np.float32)) ## ITK CANNOT WRITE DOUBLES, MUST CAST TO FLOAT letimg.CopyInformation(img1) itk.imwrite(letimg, outname)
def combine_uncertainty(dosefiles, dosesquaredfiles, statfiles, output): """ How to combine dose uncertainties from simulations? """ dosearrays = [] dosesq = [] #Gate uncertainty calculation uses NUMBER OF PRIMARIES in simulation, #not the numberOfHits per voxel N = get_tot_primaries(statfiles) # Get shape and image info img1 = itk.imread(dosefiles[0]) shape = itk.array_from_image(img1).shape if len(dosefiles) != len(dosesquaredfiles): print("Unequal number of dose and dosesquared files") if len(dosefiles) == 0: print("No files given to sum_uncertainty method") else: for f in dosefiles: dosearrays.append(itk.array_from_image(itk.imread(f)).flatten()) for f in dosesquaredfiles: dosesq.append(itk.array_from_image(itk.imread(f)).flatten()) sumdose = sum(dosearrays) sumdosesq = sum(dosesq) uncertainty = np.ones(len(dosearrays[0])) for i in range(len(uncertainty)): #Using Eq(2) from Chetty2006, "Reporting and analyzing statistical uncertainties in MC-based #treatment planning" and dividing by dose/N for relative uncertainty... if sumdosesq[i] != 0 and sumdose[i] != 0 and N > 1: uncertainty[i] = math.sqrt( 1.0 / (N - 1) * (sumdosesq[i] / N - (sumdose[i] / N)**2)) / (sumdose[i] / N) combinedUncert = uncertainty.reshape(shape) uncertimg = itk.image_from_array(combinedUncert.astype( np.float32)) ## ITK CANNOT WRITE DOUBLES, MUST CAST TO FLOAT uncertimg.CopyInformation(img1) itk.imwrite(uncertimg, output)
def imageStatistics(input=None, mask=None, resample=False, histogramBins=1000): if input is None: logger.error("Set an input") sys.exit(1) inputArray = itk.array_from_image(input) outputStats = {} outputStats["nbPixel"] = inputArray.size if not mask is None: if resample: mask = gt.applyTransformation(input=mask, like=input, force_resample=True) if not np.allclose(mask.GetSpacing(), input.GetSpacing()): logger.error("Input and mask do not have the same spacing") sys.exit(1) if not np.allclose(mask.GetOrigin(), input.GetOrigin()): logger.error("Input and mask do not have the same origin") sys.exit(1) if not np.allclose(itk.array_from_matrix(mask.GetDirection()), itk.array_from_matrix(input.GetDirection())): logger.error("Input and mask do not have the same direction") sys.exit(1) if not np.allclose(mask.GetLargestPossibleRegion().GetSize(), input.GetLargestPossibleRegion().GetSize()): logger.error("Input and mask do not have the same size") sys.exit(1) maskArray = itk.array_from_image(mask) if len(np.where(maskArray > 1)[0]) >0: logger.error("The mask seems to be a non-binary image") index = np.where(maskArray == 1) outputStats["nbPixel"] = len(index[0]) inputArray = inputArray[index] outputStats["minimum"] = np.amin(inputArray) outputStats["maximum"] = np.amax(inputArray) outputStats["sum"] = np.sum(inputArray) outputStats["median"] = np.median(inputArray) outputStats["mean"] = np.mean(outputStats["sum"]/outputStats["nbPixel"]) outputStats["variance"] = np.var(inputArray) outputStats["sigma"] = np.sqrt(outputStats["variance"]) outputStats["hist"] = np.histogram(inputArray, histogramBins) return outputStats
# test reading image series with `itk.imread()` and check that dimension is # not increased if last dimension is 1. image_series = itk.imread([image_series3d_filename, image_series3d_filename]) assert image_series.GetImageDimension() == 3 # pipeline, auto_pipeline and templated class are tested in other files # BridgeNumPy try: # Images import numpy as np image = itk.imread(fileName) arr = itk.GetArrayFromImage(image) arr.fill(1) assert np.any(arr != itk.GetArrayFromImage(image)) arr = itk.array_from_image(image) arr.fill(1) assert np.any(arr != itk.GetArrayFromImage(image)) view = itk.GetArrayViewFromImage(image) view.fill(1) assert np.all(view == itk.GetArrayFromImage(image)) image = itk.GetImageFromArray(arr) image.FillBuffer(2) assert np.any(arr != itk.GetArrayFromImage(image)) image = itk.GetImageViewFromArray(arr) image.FillBuffer(2) assert np.all(arr == itk.GetArrayFromImage(image)) image = itk.GetImageFromArray(arr, is_vector=True) assert image.GetImageDimension() == 2 image = itk.GetImageViewFromArray(arr, is_vector=True) assert image.GetImageDimension() == 2