From 5c22c5a8aac9acd1d62e52e5204ce3a0c0f7bc5d Mon Sep 17 00:00:00 2001 From: arnaufme8 Date: Tue, 17 Mar 2026 22:07:19 +0100 Subject: [PATCH 01/23] Update submodule to correct version --- submodules/STIR/STIR | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/STIR/STIR b/submodules/STIR/STIR index cab371bc..46da4ef2 160000 --- a/submodules/STIR/STIR +++ b/submodules/STIR/STIR @@ -1 +1 @@ -Subproject commit cab371bcb558a6bc202741f08d4d28b89a816682 +Subproject commit 46da4ef2b8b38487b08f5aa48c6e38b568a584cc From 7baffc526d224174648cff4ed1fbc38b85f8f6f0 Mon Sep 17 00:00:00 2001 From: arnaufme8 <113554412+arnaufme8@users.noreply.github.com> Date: Tue, 17 Mar 2026 23:10:09 +0100 Subject: [PATCH 02/23] Adding file for Total-Body: simpet.py From 96089383c2043e7d3fa3065ca632c91cdf0d35e1 Mon Sep 17 00:00:00 2001 From: arnaufme8 <113554412+arnaufme8@users.noreply.github.com> Date: Tue, 17 Mar 2026 23:10:38 +0100 Subject: [PATCH 03/23] Adding file for Total-Body: tools.py --- utils/tools.py | 2603 +++++++++++++++++++++++++++++------------------- 1 file changed, 1592 insertions(+), 1011 deletions(-) diff --git a/utils/tools.py b/utils/tools.py index 6f411d04..cf22d442 100644 --- a/utils/tools.py +++ b/utils/tools.py @@ -1,1011 +1,1592 @@ -import os, shutil, datetime -import re -from os.path import join, exists, isdir, dirname, basename, splitext -from subprocess import getstatusoutput as getoutput -import nibabel as nib -import nibabel.processing as nibp -from utils import resources as rsc -from utils import spm_tools as spm -import numpy as np -from operator import itemgetter -from nipype.interfaces.dcm2nii import Dcm2nii -from nipype.interfaces import fsl - - -def osrun(command, logfile, catch_out=False): - """ - Executes command, raise an error if it fails and send status to logger - :param command: command to be executed - :param logger: logger file - :return: - """ - if catch_out: - status, out = getoutput(command) - if status != 0: - log_message(logfile, command, 'error') - raise TypeError(command) - else: - log_message(logfile, command) - return out - - else: - if os.system(command) != 0: - - log_message(logfile, command, 'error') - raise TypeError(command) - else: - log_message(logfile, command) - -def nib_load(image, logfile=False): - """ - Load image and return data array - :param image: image to load data from - :return: image data array - """ - try: - img = nib.load(image) - data = img.get_data()[:,:,:] - return img, data - except Exception as e: - message = "Error: " + str(e) - if logfile: - log_message(logfile, message, 'error') - else: - print(message) - -def copy_analyze(image1, image2=False, dest_dir=False, logfile=False): - """ - Create a copy of an Analyze format image - :param image1: (string) path to the original image - :param image2: (string, optional) path to the copy image - :param dest_dir: (string, optional) path to the destination folder - :return: - """ - - if image2: - if image1[-3:] == 'hdr' or image1[-3:] == 'img' and image2[-3:] == 'hdr' or image2[-3:] == 'img': - image1_hdr = image1[0:-3] + 'hdr' - image2_hdr = image2[0:-3] + 'hdr' - shutil.copy(image1_hdr, image2_hdr) - image1_img = image1[0:-3] + 'img' - image2_img = image2[0:-3] + 'img' - shutil.copy(image1_img, image2_img) - - return image2_hdr - else: - message = 'Error! The provided image is not in Analyze format:' + str(image1) - if logfile: log_message(logfile, message, 'error') - raise TypeError(message) - - elif not image2 and isdir(str(dest_dir)): - - ext = splitext(basename(image1))[1] - - if ext == '.img' or ext == '.hdr': - image1_hdr = image1[0:-3] + 'hdr' - image2_hdr = join(dest_dir, basename(image1)[0:-3] + 'hdr') - shutil.copy(image1_hdr, image2_hdr) - image1_img = image1[0:-3] + 'img' - image2_img = join(dest_dir, basename(image1)[0:-3] + 'img') - shutil.copy(image1_img, image2_img) - - return image2_hdr - else: - message = 'Error! The provided image is not in Analyze format:' + str(image1) - if logfile: log_message(logfile, message, 'error') - raise TypeError(message) - else: - message = 'Error! Not image2 path or dest dir path provided: ' + str(image2) + ', ' + str(dest_dir) - if logfile: log_message(logfile, message, 'error') - raise TypeError(message) - -def create_analyze_from_imgdata(data, out, pix_x, pix_y, pix_z, tx, ty, tz, data_type="fl"): - - if data_type == "1b": - dtype=np.int8 - elif data_type == "2b": - dtype=np.int16 - elif data_type == "db": - dtype=np.float64 - else: - dtype=np.float32 - - hdr1 = nib.AnalyzeHeader() - hdr1.set_data_dtype(dtype) - hdr1.set_data_shape((pix_x,pix_y,pix_z)) - hdr1.set_zooms((tx,ty,tz)) - - f = open(data,'rb') - - img_data = hdr1.raw_data_from_fileobj(f) - - analyze_img = nib.AnalyzeImage(img_data, hdr1.get_base_affine(), hdr1) - - nib.save(analyze_img,out) - -def read_analyze_header(header_file,logfile): - - img = nib.load(header_file) - - zpix = img.shape[2] - #zsize = abs(img.get_affine[2,2]) - zsize = abs(img.affine[2,2]) - xpix = img.shape[0] - #xsize = abs(img.get_affine[0,0]) - xsize = abs(img.affine[0,0]) - ypix = img.shape[1] - #ysize = abs(img.get_affine[1,1]) - ysize = abs(img.affine[1,1]) - - return zpix, zsize, xpix, xsize, ypix, ysize - -def write_interfile_header(header_file,matrix_size_x,pixel_size_x, matrix_size_y,pixel_size_y, matrix_size_z,pixel_size_z, offset_z=0): - - image_v = os.path.basename(header_file)[0:-2] + "v" - fheader_hv = open(header_file, "w") - - offset_x = matrix_size_x/2*pixel_size_x + 0.5*pixel_size_x - offset_y = matrix_size_y/2*pixel_size_y + 0.5*pixel_size_y - - fheader_hv.write("!INTERFILE :=\n") - fheader_hv.write("name of data file := %s\n" % image_v) - fheader_hv.write("!GENERAL DATA :=\n") - fheader_hv.write("!GENERAL IMAGE DATA :=\n") - fheader_hv.write("!type of data := PET\n") - fheader_hv.write("imagedata byte order := LITTLEENDIAN\n") - fheader_hv.write("!PET STUDY (General) :=\n") - fheader_hv.write("!PET data type := Image\n") - fheader_hv.write("process status := Reconstructed\n") - fheader_hv.write("!number format := float\n") - fheader_hv.write("!number of bytes per pixel := 4\n") - fheader_hv.write("number of dimensions := 3\n") - fheader_hv.write("matrix axis label [1] := x\n") - fheader_hv.write("!matrix size [1] := %s\n" % matrix_size_x) - fheader_hv.write("scaling factor (mm/pixel) [1] := %s\n" % pixel_size_x) - fheader_hv.write("matrix axis label [2] := y\n") - fheader_hv.write("!matrix size [2] := %s\n" % matrix_size_y) - fheader_hv.write("scaling factor (mm/pixel) [2] := %s\n" % pixel_size_y) - fheader_hv.write("matrix axis label [3] := z\n") - fheader_hv.write("!matrix size [3] := %s\n" % matrix_size_z) - fheader_hv.write("scaling factor (mm/pixel) [3] := %s\n" % pixel_size_z) - fheader_hv.write("first pixel offset (mm) [1] := -%s\n" % offset_x) - fheader_hv.write("first pixel offset (mm) [2] := -%s\n" % offset_y) - fheader_hv.write("first pixel offset (mm) [3] := 0\n") - fheader_hv.write("number of time frames := 1\n") - fheader_hv.write("!END OF INTERFILE :=\n") - - fheader_hv.close() - -def nii_analyze_convert(image, logfile=False, outfile=False): - """ - Converts the provided image file to the analyze or nifti format - depending on the filex extension - :param image: image to be converted - :return: - """ - - # if not logfile: logfile = join(dirname(image), 'log_nii_analyze_convert.txt') - - ext = os.path.splitext(image)[1] - img, data = nib_load(image) - data = data.astype(np.float32) #casting to avoid problems when applying nib.save with no float data - hdr = nib.AnalyzeHeader() - hdr.set_data_dtype(np.float32) - hdr.set_data_shape(data.shape) - - if ext == '.nii': - # n2a = rsc.get_rsc('nii2analyze', 'exe') - if outfile: - cimage = outfile - else: - cimage = image.replace('.nii', '.hdr') - # rcommand = '%s %s %s >> %s' % (n2a, image, cimage, logfile) - # osrun(rcommand, logfile) - imageToWrite = nib.AnalyzeImage(data, img.affine, hdr) - elif ext == '.hdr': - # a2n = rsc.get_rsc('analyze2nii', 'exe') - if outfile: - cimage = outfile - else: - cimage = image.replace('.hdr', '.nii') - # rcommand = '%s %s %s >> %s' % (a2n, image, cimage, logfile) - # osrun(rcommand, logfile) - imageToWrite = nib.Nifti1Image(data, img.affine, hdr) - else: - # a2n = rsc.get_rsc('analyze2nii', 'exe') - if outfile: - cimage = outfile - else: - cimage = image.replace('.img', '.nii') - # image_hdr = str(image).replace(".img", ".hdr") - # rcommand = '%s %s %s >> %s' % (a2n, image_hdr, cimage, logfile) - # osrun(rcommand, logfile) - imageToWrite = nib.Nifti1Image(data, img.affine, hdr) - - nib.save(imageToWrite, cimage) - - if exists(cimage): - if outfile: - shutil.move(cimage, outfile) - return outfile - else: - return cimage - else: - return 0 - -def anything_to_hdr_convert(image, logfile=False, outfile=False ): - """ - This function will try to convert dicom, .nii.gz or .nii to analyze... - """ - - if image[-3:] == "hdr" or image[-3:] == "img": - return image[0:-3]+"hdr" - - elif image[-3:] == "nii": - hdr = nii_analyze_convert(image,logfile=logfile) - if exists(hdr): - return hdr - else: - raise TypeError ("nifti-analyze conversion failed....") - - elif image[-6:]=="nii.gz": - hdr = image[0:-6] + 'hdr' - nii_analyze_convert(image,logfile=logfile, outfile=hdr) - if exists(hdr): - return hdr - else: - raise TypeError ("nifti-analyze conversion failed....") - - elif isdir(image): - print("I think the provided image:\n %s \n is a dicom directory. I will try to convert it...") - dicom2nii = rsc.get_rsc("dicom2nii","exe") - nii = join(dirname(image),"image.nii") - rcommand = '%s %s %s' % (dicom2nii, image, nii) - osrun(rcommand,logfile) - if exists(nii): - hdr = nii_analyze_convert(nii,logfile=logfile) - os.remove(nii) - if exists(hdr): - return hdr - else: - raise TypeError ("nifti-analyze conversion failed....") - else: - raise TypeError ("dicom-nifti conversion failed....") - - elif image[-2:] == "hv": #Only works with floats data - - with open(image) as f: - lines = f.readlines() - - lines = [x.strip() for x in lines] - - pixel = [re.compile(f"\!matrix size \[{i}\].*") for i in range(1, 4)] - pixel_size = [re.compile(f"scaling factor \(mm.pixel\) \[{i}\].*") for i in range(1, 4)] - - pixel_x = list(filter(pixel[0].match, lines))[0].split()[-1] - pixel_size_x = list(filter(pixel_size[0].match, lines))[0].split()[-1] - pixel_y = list(filter(pixel[1].match, lines))[0].split()[-1] - pixel_size_y = list(filter(pixel_size[1].match, lines))[0].split()[-1] - pixel_z = list(filter(pixel[2].match, lines))[0].split()[-1] - pixel_size_z = list(filter(pixel_size[2].match, lines))[0].split()[-1] - - hdr_header = image[0:-2] + "hdr" - # img_file = image[0:-2] + "img" - data_file = image[0:-2] + "v" - - #This will convert .hv to .hdr and copy the data - # gen_hdr = rsc.get_rsc("gen_hdr", "fruitcake") - # rcommand = "%s %s %s %s %s fl %s %s %s 0" % (gen_hdr, hdr_header[0:-4], pixel_x, pixel_y, pixel_z, pixel_size_x, pixel_size_y, pixel_size_z) - # osrun(rcommand, logfile) - # shutil.copy(data_file, img_file) - create_analyze_from_imgdata(data_file,hdr_header,float(pixel_x),float(pixel_y),float(pixel_z),float(pixel_size_x),float(pixel_size_y),float(pixel_size_z)) - #nii_analyze_convert("inicial.hdr",logfile,"aux.nii") - #nii_analyze_convert("aux.nii",logfile,hdr_header) - #os.remove("aux.nii") - - if exists(hdr_header): - return hdr_header - else: - raise TypeError ("Interfile-analyze conversion failed....") - -def prepare_input_image(image_hdr, logfile, min_voxel_size=1): - """ - This method converts input_image to float data type, re-sizes the image to - 1mm size voxels if too large to keep a reasonable analysis execution time and - removes the negative and NaN values. - :param input_image: image to prepare for the analysis - :return: - """ - - # Tools for image manipulation - # change_format = rsc.get_rsc('change_format', 'fruitcake') - # change_matrix = rsc.get_rsc('change_img_matrix', 'fruitcake') - # erase_negs = rsc.get_rsc('erase_negs', 'fruitcake') - # erase_nans = rsc.get_rsc('erase_nans', 'fruitcake') - - # Convert input image to Analyze format and float data type - # rcommand = '%s %s %s fl >> %s' % (change_format, image_hdr, image_hdr, logfile) - # osrun(rcommand, logfile) - - ## # CONTEMPLAR POSIBILIDAD DE CONVERTIR ESTO EN FUNCIÓN PARA SUSTITUIR AL "cambia_formato_hdr" de fruitcake - change_format(image_hdr, "fl", logfile) - - ### - - # Resize image if necessary - # ndims = recalculate_matrix(image_hdr, min_voxel_size) - # rcommand = '%s %s %s %s %s %s novecino >> %s' % (change_matrix, image_hdr, image_hdr, - # str(ndims[0]), str(ndims[1]), str(ndims[2]), logfile) - # osrun(rcommand, logfile) - image_hdr = resampleXYvoxelSizes(image_hdr, min_voxel_size, logfile) - image_hdr = resampleZvoxelSize(image_hdr, min_voxel_size, logfile) - - # Erase negative and NaN values - # rcommand = '%s %s %s >> %s' % (erase_negs, image_hdr, image_hdr, logfile) - # osrun(rcommand, logfile) - # rcommand = '%s %s %s >> %s' % (erase_nans, image_hdr, image_hdr, logfile) - # osrun(rcommand, logfile) - remove_neg_nan(image_hdr) - - - return image_hdr - -def recalculate_matrix(input_image, voxelsize, mode="downsampling"): - - new_dimensions = [] - image_load, data = nib_load(input_image) - header = image_load.header - sizes = header.get_zooms() - dimensions = image_load.shape - x_lenght = int(sizes[0]*dimensions[0]/voxelsize) - y_lenght = int(sizes[1]*dimensions[1]/voxelsize) - z_lenght = int(sizes[2]*dimensions[2]/voxelsize) - if mode == "downsampling": - if sizes[0]voxelsize: - new_dimensions.append(x_lenght) - else: - new_dimensions.append(dimensions[0]) - if sizes[1]>voxelsize: - new_dimensions.append(y_lenght) - else: - new_dimensions.append(dimensions[1]) - if sizes[2]>voxelsize: - new_dimensions.append(z_lenght) - else: - new_dimensions.append(dimensions[2]) - - return new_dimensions - -def verify_roi_exists(rois_image, roi_number): - """ - Compute the number of voxels in each ROI - :param rois_image: (string) path to the ROI parcelled image - :param rois_list: (array) ROIs indexing - :return: False if the number of voxels is zero - :return: nvox is not zero - """ - rois_img = nib.load(rois_image) - rois_data = rois_img.get_data()[:, :, :] - indx = np.where(rois_data == roi_number) - nvox = len(rois_data[indx]) - if nvox == 0: - return False - else: - return True - -def operate_single_image(input_image, operation, factor, output_image, logfile): - """ - Given an input image, multiply or divide it by a numerical factor - saving the result as output_image - :param input_image: image base operation on - #:param operation: 1 = multiply, 2 = divide - :param operation: 'mult' = multiply, 'div' = divide - :param factor: operation factor - :param output_image: output image file - :return: - """ - - img = nib.load(input_image) - data = img.get_data()[:,:,:] - data = np.nan_to_num(data) - - - if operation == 'mult': - data = data * float(factor) - elif operation == 'div': - data = data / float(factor) - else: - message = "Error! Invalid operation: " +str(operation) - print(message) - log_message(logfile, message, 'error') - - hdr1 = nib.AnalyzeHeader() - hdr1.set_data_dtype(img.get_data_dtype()) - hdr1.set_data_shape(img.shape) - #hdr1.set_zooms(abs(np.diag(img.affine))) - #hdr1.set_zooms(abs(np.diag(img.affine))[0:3]) - #hdr1.set_zooms(abs(np.diag(img.affine))[0:4]) - hdr1.set_zooms(abs(np.diag(img.affine))[0:img.ndim]) - - analyze_img = nib.AnalyzeImage(data, hdr1.get_base_affine(), hdr1) - - nib.save(analyze_img,output_image) - - -def operate_images_analyze(image1, image2, out_image, operation='mult'): - """ - Given the input images, calculate the multiplication image or the ratio between them - :param image1: string, path to the first image - :param image2: string, path to the second image - :param operation: string, multi (default) for multiplication divid for division - :param out_image: string (optional), path to the output image - :return: - """ - img1, data1 = nib_load(image1) - img2, data2 = nib_load(image2) - - # TODO CHECK IF NEGATIVE VALUES NEED TO BE REMOVED - # Remove NaN and negative values - data1 = np.nan_to_num(data1) - data2 = np.nan_to_num(data2) - - if operation == 'mult': - res_data = data1 * data2 - elif operation == 'div': - res_data = data1 / data2 - elif operation == 'sum': - res_data = data1 + data2 - elif operation == 'diff': - res_data = data1 - data2 - else: - message = 'Error! Unknown operation: ' + str(operation) - raise TypeError(message) - - hdr1 = nib.AnalyzeHeader() - hdr1.set_data_dtype(img1.get_data_dtype()) - hdr1.set_data_shape(img1.shape) - ##hdr1.set_zooms(abs(np.diag(img1.affine))[0:3]) - ##hdr1.set_zooms(abs(np.diag(img1.affine))[0:4]) - hdr1.set_zooms(abs(np.diag(img1.affine))[0:img1.ndim]) - - analyze_img = nib.AnalyzeImage(res_data, hdr1.get_base_affine(), hdr1) - - nib.save(analyze_img,out_image) - -def smooth_analyze(image,fwhm, output): - - from nibabel import processing as nibproc - - img =nib.load(image) - smoothed=nibproc.smooth_image(img,fwhm,out_class=nib.AnalyzeImage) - nib.save(smoothed,output) - -def log_message(logfile, message, mode='info'): - """" - Print outs logger messages to the specified logfile - """ - - #ctime = read_time(datetime.datetime.now()) - ctime = datetime.datetime.now() - - separator = '\n###########################################################' - - if mode == 'exe': - stream = separator + '\nTIME: ' + str(ctime) + '\n\nEXE: ' + message + separator + '\n' - elif mode == 'info': - stream = separator + '\nTIME: ' + str(ctime) + '\n\nINFO: ' + message + separator + '\n' - elif mode == 'warning': - stream = separator + '\nTIME: ' + str(ctime) + '\n\nWARNING: ' + message + separator + '\n' - elif mode == 'error': - stream = separator + '\nTIME: ' + str(ctime) + '\n\nERROR: ' + message + separator + '\n' - else: - stream = separator + '\nTIME: ' + str(ctime) + '\n\n' + message + '\n' - - if exists(logfile): - with open(logfile, 'a') as lfile: - lfile.write(stream) - else: - with open(logfile, 'w') as lfile: - lfile.write(stream) - -def reorient_dcmtonii(image_path): - - - components=os.path.split(image_path) - path = components[0] - image = components[1] - converter = Dcm2nii() - converter.inputs.source_names=[image_path] - converter.inputs.reorient_and_crop=True - # converter.nii_output=False - converter.run() - - reor_ima_name = "o"+image - if exists(join(path,reor_ima_name)): - shutil.copy(join(path,reor_ima_name),image_path) - os.remove(join(path,reor_ima_name)) - os.remove(join(path,"co"+image)) - else: - # converter.inputs_reorient_and_crop=False - # converter.run() - # shutil.copy(join(path,"f"+image), image_path) - os.remove(join(path,"c"+image)) - # os.remove(join(path,"f"+image)) - - - -def petmr2maps(pet_image, mri_image, ct_image, log_file, spm_run, output_dir, mode="SimSET"): - """ - It will create act and att maps from PET and MR images. - Required inputs are: - pet_image: dicom_dir, nii.gz, nii or Analyze (.hdr or .img) - mri_image: dicom_dir, nii.gz, nii or Analyze (.hdr or .img) - mode: Choose STIR or SIMSET. The maps will be different for each simulation. - The inputs will be stored to Data/simulation_name/Patient as reformatted/corregistered Analyze - #The maps will be stored on Data/simulation_name/Maps - The maps will be stored on Results/output_name/Maps - """ - message = "GENERATING ACT AND ATT MAPS FROM PET, (CT) and MR IMAGES" - log_message(log_file, message, mode='info') - - reorient_dcmtonii(pet_image) - reorient_dcmtonii(mri_image) - reorient_dcmtonii(ct_image) - #First of all lets take all to analyze - pet_hdr = anything_to_hdr_convert(pet_image) - #pet_hdr = copy_analyze(pet_hdr,image2=False,dest_dir=patient_dir) - pet_hdr = prepare_input_image(pet_hdr,log_file,min_voxel_size=1.5) - pet_img = pet_hdr[0:-3]+"img" - - if ct_image: #ct_image is not empty - ct_hdr = anything_to_hdr_convert(ct_image) - ct_hdr = prepare_input_image(ct_hdr,log_file,min_voxel_size=1.5) - ct_img = ct_hdr[0:-3]+"img" - - mri_hdr = anything_to_hdr_convert(mri_image) - #mri_hdr = copy_analyze(mri_hdr,image2=False,dest_dir=patient_dir) - mri_hdr = prepare_input_image(mri_hdr,log_file,min_voxel_size=1.5) - mri_img = mri_hdr[0:-3]+"img" - - #make mri image square - makeImageSquare(mri_hdr, log_file) - - #Performing PET-CT/MR coregister - mfile = os.path.join(output_dir,"fusion_pet_to_mri.m") - # correg_pet_hdr = fsl_flirt(mri_hdr, pet_hdr, log_file) - # correg_pet_img =correg_pet_hdr[0:-3]+"img" - correg_pet_img = spm.image_fusion(spm_run, mfile, mri_img, pet_img, log_file) - correg_ct_img="" - if ct_image: #ct_image is not empty - mfile = os.path.join(output_dir,"fusion_ct_to_mri.m") - correg_ct_img = spm.image_fusion(spm_run, mfile, mri_img, ct_img, log_file) - - #Now the map generation - from utils.patient2maps import patient2maps - - my_map_generation = patient2maps(spm_run, output_dir, log_file, - mri_img, correg_pet_img, correg_ct_img, mode=mode) - activity_map_hdr, attenuation_map_hdr = my_map_generation.run() - - return activity_map_hdr, attenuation_map_hdr - -def convert_map_values(act_map,att_map,output_dir,log_file,mode="SimSET"): - - message = "CONVERTING ACT AND ATT TO %s" % mode - log_message(log_file, message, mode='info') - - act_map = anything_to_hdr_convert(act_map) - att_map = anything_to_hdr_convert(att_map) - - cambia_formato = rsc.get_rsc('change_format', 'fruitcake') - cambia_val = rsc.get_rsc('change_values', 'fruitcake') - - new_att_map = join(output_dir, "att_map_" + mode +".hdr") - new_act_map = join(output_dir,"act_map_" + mode +".hdr") - - if mode == "SimSET": - - #rcommand = '%s %s %s 0.096 1 >> %s' % (cambia_val, att_map, new_att_map, log_file) - rcommand = '%s %s %s 0.096 4 >> %s' % (cambia_val, att_map, new_att_map, log_file) - osrun(rcommand, log_file) - rcommand = '%s %s %s 0.135 3 >> %s' % (cambia_val, new_att_map, new_att_map, log_file) - osrun(rcommand, log_file) - rcommand = '%s %s %s 1B >> %s' % (cambia_formato, new_att_map, new_att_map, log_file) - osrun(rcommand, log_file) - rcommand = '%s %s %s 1B >> %s' % (cambia_formato, act_map, new_act_map, log_file) - osrun(rcommand, log_file) - - if mode == "STIR": - - rcommand = '%s %s %s fl >> %s' % (cambia_formato, att_map, new_att_map, log_file) - osrun(rcommand, log_file) - rcommand = '%s %s %s fl >> %s' % (cambia_formato, new_act_map, new_act_map, log_file) - osrun(rcommand, log_file) - rcommand = '%s %s %s 1 0.096 >> %s' % (cambia_val, new_att_map, new_att_map, log_file) - osrun(rcommand, log_file) - rcommand = '%s %s %s 3 0.135 >> %s' % (cambia_val, new_att_map, new_att_map, log_file) - osrun(rcommand, log_file) - - return new_act_map, new_att_map - -def ncounts(image_hdr): - - img, data = nib_load(image_hdr) - ncounts = np.sum(data) - return ncounts - -def convert_simset_sino_to_stir(input_img, output=False): - - ## To be continued.... - - simset_img = nib.load(input_img) - simset_img_data = simset_img.get_fdata() - shape = simset_img_data.shape - - n_slices = shape[2] - nrings = np.sqrt(n_slices) - n_x = shape[0] - - input_definition = [] - - for i in range(n_slices): - - ring1,ring2 = divmod(i,nrings) - segment = ring1 - ring2 - slice_def = [i, ring1, ring2, segment] - input_definition.append(slice_def) - - output_definition = sorted(input_definition, key=itemgetter(3)) - stir_img_data = np.empty(shape, dtype=float, order='C') - stir_img_data_flip_x = np.empty(shape, dtype=float, order='C') - - for i in range(n_slices): - - output_index = output_definition[i][0] - input_slice = simset_img_data[:,:,output_index] - stir_img_data[:,:,i] = input_slice - - for j in range(n_x): - stir_img_data_flip_x[j,:,:] = stir_img_data[n_x-1-j, :, :] - - #stir_img = nib.AnalyzeImage(stir_img_data, simset_img.affine, simset_img.header) - stir_img = nib.AnalyzeImage(stir_img_data_flip_x, simset_img.affine, simset_img.header) - - if not output: - output = input_img [0:-4] + '_stir.hdr' - - nib.save(stir_img,output) - - -def resampleXYvoxelSizes(image_hdr, xyVoxelSize, log_file): - img = nib.load(image_hdr) - z_VoxelSize =img.header['pixdim'][3] - taget_vsize = (xyVoxelSize,xyVoxelSize,z_VoxelSize) - res_img = nibp.resample_to_output(img, taget_vsize, out_class=img.__class__) - nib.save(res_img,image_hdr) - - return image_hdr - - -def resampleZvoxelSize(image_hdr, zOutputvoxelSize, log_file): - img = nib.load(image_hdr) - x_VoxelSize = img.header['pixdim'][1] - y_VoxelSize = img.header['pixdim'][2] - taget_vsize = (x_VoxelSize,y_VoxelSize,zOutputvoxelSize) - res_img = nibp.resample_to_output(img, taget_vsize, out_class=img.__class__) - nib.save(res_img,image_hdr) - - return image_hdr - - -def makeImageSquare(image_hdr, log_file): - - # Tools for image manipulation - corta_pega_filcol_hdr = rsc.get_rsc('corta_pega_filcol_hdr', 'fruitcake') - zpix, zsize, xpix, xsize, ypix, ysize = read_analyze_header(image_hdr,log_file) - - pixDif = abs(xpix-ypix) - - if pixDif != 0: - if pixDif % 2 == 0: - oneSide = pixDif/2 - otherSide = oneSide - else: - oneSide = np.trunc(pixDif/2) - otherSide = oneSide +1 - - if xpix > ypix: - rcommand1 = '%s %s %s peg %s %s >> %s' % (corta_pega_filcol_hdr, image_hdr, image_hdr, "1", str(oneSide), log_file) - rcommand2 = '%s %s %s peg %s %s >> %s' % (corta_pega_filcol_hdr, image_hdr, image_hdr, "2", str(otherSide), log_file) - elif ypix > xpix: - rcommand1 = '%s %s %s peg %s %s >> %s' % (corta_pega_filcol_hdr, image_hdr, image_hdr, "3", str(oneSide), log_file) - rcommand2 = '%s %s %s peg %s %s >> %s' % (corta_pega_filcol_hdr, image_hdr, image_hdr, "4", str(otherSide), log_file) - - osrun(rcommand1, log_file) - osrun(rcommand2, log_file) - - -def scalImage(image_hdr, maxValue, log_file): - """ - Given the input image_hdr (analyze format), this function scales their values to the input maxValue - - """ - cambia_val_interval = rsc.get_rsc('change_interval', 'fruitcake') - img, data =nib_load(image_hdr, log_file) - - factor = maxValue/np.max(data) - - mask_hdr="mask.hdr" - rcommand = '%s %s %s 0 0.9 0 >> %s' % (cambia_val_interval, image_hdr, mask_hdr, log_file) - osrun(rcommand, log_file) - rcommand = '%s %s %s 0.9 10000000000 1 >> %s' % (cambia_val_interval, mask_hdr, mask_hdr, log_file) - osrun(rcommand, log_file) - - operate_single_image(image_hdr, "mult", factor, image_hdr, log_file) - - rcommand = '%s %s %s 0 1 1 >> %s' % (cambia_val_interval, image_hdr, image_hdr, log_file) - osrun(rcommand, log_file) - - operate_images_analyze(image_hdr, mask_hdr, image_hdr, "mult") - - os.remove(mask_hdr) - os.remove("mask.img") - -def remove_neg_nan(image_hdr): - - img, data = nib_load(image_hdr) - indx = np.where(data<0) - data[indx] = 0 - indx = np.where(np.isnan(data)) - data[indx] = 0 - - imageToWrite = nib.AnalyzeImage(data,img.affine,img.header) - nib.save(imageToWrite, "aux.hdr") - copy_analyze("aux.hdr",image_hdr) - os.remove("aux.hdr") - os.remove("aux.img") - - #return image_hdr - -def update_act_map(spmrun, act_map, att_map, orig_pet, simu_pet, output_act_map, axialFOV, log_file): - - output_dir = dirname(output_act_map) - # mfileFusion = join(dirname(output_act_map),"fusion.m") - - # Getting the necesary resources - # cambia_formato = rsc.get_rsc('change_format', 'fruitcake') - # cambia_val_interval = rsc.get_rsc('change_interval', 'fruitcake') - # operate_image_hdr = rsc.get_rsc('operate_image', 'fruitcake') - # remove_nan_hdr = rsc.get_rsc('erase_nans', 'fruitcake') - # remove_neg_hdr = rsc.get_rsc('erase_negs', 'fruitcake') - - # First step is coregistering the output image with the orig pet (coregistered to the mri) - #act_map_img = act_map[0:-3]+"img" - # simu_pet_img = simu_pet[0:-3]+"img" - orig_pet_img = orig_pet[0:-3]+"img" - # coreg_simpet_img = spm.image_fusion(spmrun, mfileFusion, orig_pet_img, simu_pet_img, log_file) - coreg_simpet_hdr = fsl_flirt(orig_pet, simu_pet, log_file) - coreg_simpet_img =coreg_simpet_hdr[0:-3]+"img" - - act, act_data = nib_load(act_map) - - - # Next, we will do a scaling by the mean - norm_factor = proportional_scaling(coreg_simpet_hdr, orig_pet, orig_pet, log_file) - operate_single_image(coreg_simpet_hdr,'mult',norm_factor, coreg_simpet_hdr, log_file) - - # Now we do a smoothing of both data to avoid multiply noise and perform the division - mfileSmooth = join(dirname(output_act_map),"smooth.m") - division_hdr = join(output_dir, "division.hdr") - s_coreg_simpet_img = spm.smoothing(spmrun, mfileSmooth, coreg_simpet_img, 5, "s", log_file) - s_orig_pet_img = spm.smoothing(spmrun, mfileSmooth, orig_pet_img, 5, "s", log_file) - - s_coreg_simpet_hdr = s_coreg_simpet_img[0:-3]+"hdr" - s_orig_pet_hdr = s_orig_pet_img[0:-3]+"hdr" - operate_images_analyze(s_orig_pet_hdr, s_coreg_simpet_hdr, division_hdr, 'div') - # rcommand = '%s %s %s %s fl divid' % (operate_image_hdr, s_orig_pet_hdr, s_coreg_simpet_hdr, division_hdr) - # osrun(rcommand, log_file) - - # Now we do some stuff on the division image to avoid problems - pet_mask_hdr = join(dirname(orig_pet), "pet_mask.hdr") - deleteValuesOutFov(pet_mask_hdr, axialFOV/2, act.shape[2]/2) - - # rcommand = '%s %s %s %s fl multi' % (operate_image_hdr, division_hdr, pet_mask_hdr, division_hdr) - # osrun(rcommand, log_file) - # rcommand = '%s %s %s >> %s' % (remove_nan_hdr, division_hdr, division_hdr, log_file) - # osrun(rcommand, log_file) - # rcommand = '%s %s %s >> %s' % (remove_neg_hdr, division_hdr, division_hdr, log_file) - # osrun(rcommand, log_file) - fix_4d_image(pet_mask_hdr) - operate_images_analyze(division_hdr, pet_mask_hdr, division_hdr, "mult") - remove_neg_nan(division_hdr) - change_interval_values(division_hdr, division_hdr, 5, 100000000000000000000000000000000000,1) - # rcommand = '%s %s %s 5 100000000000000000000000000000000000 1' % (cambia_val_interval, division_hdr, division_hdr) - # osrun(rcommand, log_file) - - #operate_images_analyze(division_hdr, pet_mask_hdr, division_hdr, 'mult') - operate_images_analyze(division_hdr, pet_mask_hdr, division_hdr, "mult") - # rcommand = '%s %s %s %s fl multi' % (operate_image_hdr, division_hdr, pet_mask_hdr, division_hdr) - # osrun(rcommand, log_file) - - #and finally, we calculate the new activity image - fix_4d_image(act_map) - operate_images_analyze(division_hdr, act_map, output_act_map, "mult") - # rcommand = '%s %s %s %s fl multi' % (operate_image_hdr, act_map, division_hdr, output_act_map) - # osrun(rcommand, log_file) - scalImage(output_act_map, 127, log_file) - change_format(output_act_map,"1B", log_file) - # rcommand = '%s %s %s 1B >> %s' % (cambia_formato, output_act_map, output_act_map, log_file) - # osrun(rcommand, log_file) - -def fsl_flirt(reference_hdr, input_hdr, log_file): - components = os.path.split(input_hdr) - coreg_hdr = os.path.join(components[0], 'r' + components[1]) - flt = fsl.FLIRT(bins=640, cost_func='mutualinfo') - flt.inputs.in_file = input_hdr - flt.inputs.reference = reference_hdr - flt.out_file = coreg_hdr - flt.out_log=log_file - flt.save_log=True - flt.inputs.output_type = "NIFTI_GZ" - flt.cmdline - 'flirt -in %s -ref %s -out %s -dof 6 -cost mutualinfo -searchrx -30 30 -searchry -30 30 -searchrz -30 30' %(input_hdr, reference_hdr, coreg_hdr) - aux = os.getcwd() - os.chdir(components[0]) - flt.run() - os.chdir(aux) - - anything_to_hdr_convert(input_hdr[0:-4]+"_flirt.nii.gz") - copy_analyze(input_hdr[0:-4]+"_flirt.hdr", coreg_hdr) - os.system("rm %s*" % input_hdr[0:-4]+"_flirt.*") - - return coreg_hdr - - -def proportional_scaling(img,ref_img,mask_img, log_file): - - img_max, img_mean = compute_vmax_vmean(img, mask_img) - ref_max, ref_mean = compute_vmax_vmean(ref_img, mask_img) - - if float(img_mean) != 0: - fnorm = ref_mean / img_mean - return fnorm - else: - message = 'Error scaling image. Image maean value is zero: ' + str(img) - log_message(log_file, message, 'error') - #print message - -def compute_vmax_vmean(img, mask_img): - """ - Compute maximum and mean intensity values on input image counting - on voxels inside the reference image (brain mask) - :param img: (string) input image path - :param ref_img: (string) reference image path - :return: - """ - - img, data = nib_load(img) - data = np.nan_to_num(data) - - ref_img, data_ref = nib_load(mask_img) - data_ref = np.nan_to_num(data_ref) - - i_max = np.amax(data_ref) - - super_threshold_indices = data_ref > 0.2*i_max - data_ref[super_threshold_indices] = 0 - - # Compute values restricted to voxels inside mask (ref image) - indx = np.where((data>0) & (data_ref.reshape(data.shape)>0)) - - # Maximum intensity value - v_max = np.max(data[indx]) - # Mean intensity value - v_mean = np.mean(data[indx]) - - return v_max, v_mean - - -def deleteValuesOutFov(mask_hdr, max_z, central_slice): - - img, data = nib_load(mask_hdr) - data = np.nan_to_num(data) - - z = img.shape[2] - z_size = img.header['pixdim'][3] #mm - max_z = int(round((float(max_z)*10)/float(z_size))) - - i_min = int(central_slice)-max_z - i_max = int(central_slice)+max_z - - for i in range(0, i_min): - data[:,:,i]=0 - - for i in range(i_max,z): - data[:,:,i]=0 - - img_to_write = nib.Nifti1Image(data, img.affine, img.header) - nib.save(img_to_write,mask_hdr) - -def compute_corr_coeff(img1, img2, log_file): - """ - Compute the correlation coefficiente between img1 and img2 - :param img1: (string) input header image1 path - :param img2: (string) input header image2 path - :return: - """ - - img1_img, img1_data = nib_load(img1, log_file) - img2_img, img2_data = nib_load(img2, log_file) - corrCoefmtx = np.corrcoef(img1_data.flatten(),img2_data.flatten()) - - return corrCoefmtx[0,1] - -def fix_4d_image(image_hdr): - img, data = nib_load(image_hdr) - shape = data.shape - - if len(shape) != 3: - data_new = data[:,:,:,0] - - imageToWrite = nib.AnalyzeImage(data_new,img.affine,img.header) - nib.save(imageToWrite, "aux.hdr") - copy_analyze("aux.hdr",image_hdr) - os.remove("aux.hdr") - os.remove("aux.img") - -def change_interval_values(input_hdr, output_hdr, min_value, max_value, rep_value): - img, data = nib_load(input_hdr) - - - indx = np.where(datamin_value) - - data[indx] = rep_value - - imageToWrite = nib.AnalyzeImage(data,img.affine,img.header) - nib.save(imageToWrite, "aux.hdr") - copy_analyze("aux.hdr",output_hdr) - os.remove("aux.hdr") - os.remove("aux.img") - -def change_format(image_hdr, newFormat, logfile): - message="" - img, data = nib_load(image_hdr) - - if newFormat == "fl": - form=np.float32 - elif newFormat == "1B": - form=np.uint8 - else: - message = "Error! Invalid format (or not implemented yet): " +str(newFormat) - print(message) - log_message(logfile, message, 'error') - - if message=="": - data = data.astype(form) - hdr = nib.AnalyzeHeader() - hdr.set_data_dtype(form) - hdr.set_data_shape(data.shape) - imageToWrite = nib.AnalyzeImage(data, img.affine, hdr) - nib.save(imageToWrite, image_hdr) - -def fix_4d_data(data): - shape = data.shape - - if len(shape) == 3: - return data - else: - return data[:, :, :, 0] +import os, shutil, datetime +import re +from os.path import join, exists, isdir, dirname, basename, splitext +from subprocess import getstatusoutput as getoutput +import nibabel as nib +import nibabel.processing as nibp +from utils import resources as rsc +from utils import spm_tools as spm +import numpy as np +from operator import itemgetter +from nipype.interfaces.dcm2nii import Dcm2nii +from nipype.interfaces import fsl + + +def osrun(command, logfile, catch_out=False): + """ + Executes command, raise an error if it fails and send status to logger + :param command: command to be executed + :param logger: logger file + :return: + """ + if catch_out: + status, out = getoutput(command) + if status != 0: + log_message(logfile, command, 'error') + raise TypeError(command) + else: + log_message(logfile, command) + return out + + else: + if os.system(command) != 0: + + log_message(logfile, command, 'error') + raise TypeError(command) + else: + log_message(logfile, command) + +def nib_load(image, logfile=False): + """ + Load image and return data array + :param image: image to load data from + :return: image data array + """ + try: + img = nib.load(image) + data = img.get_data()[:,:,:] + return img, data + except Exception as e: + message = "Error: " + str(e) + if logfile: + log_message(logfile, message, 'error') + else: + print(message) + +def copy_analyze(image1, image2=False, dest_dir=False, logfile=False): + """ + Create a copy of an Analyze format image + :param image1: (string) path to the original image + :param image2: (string, optional) path to the copy image + :param dest_dir: (string, optional) path to the destination folder + :return: + """ + + if image2: + if image1[-3:] == 'hdr' or image1[-3:] == 'img' and image2[-3:] == 'hdr' or image2[-3:] == 'img': + image1_hdr = image1[0:-3] + 'hdr' + image2_hdr = image2[0:-3] + 'hdr' + shutil.copy(image1_hdr, image2_hdr) + image1_img = image1[0:-3] + 'img' + image2_img = image2[0:-3] + 'img' + shutil.copy(image1_img, image2_img) + + return image2_hdr + else: + message = 'Error! The provided image is not in Analyze format:' + str(image1) + if logfile: log_message(logfile, message, 'error') + raise TypeError(message) + + elif not image2 and isdir(str(dest_dir)): + + ext = splitext(basename(image1))[1] + + if ext == '.img' or ext == '.hdr': + image1_hdr = image1[0:-3] + 'hdr' + image2_hdr = join(dest_dir, basename(image1)[0:-3] + 'hdr') + shutil.copy(image1_hdr, image2_hdr) + image1_img = image1[0:-3] + 'img' + image2_img = join(dest_dir, basename(image1)[0:-3] + 'img') + shutil.copy(image1_img, image2_img) + + return image2_hdr + else: + message = 'Error! The provided image is not in Analyze format:' + str(image1) + if logfile: log_message(logfile, message, 'error') + raise TypeError(message) + else: + message = 'Error! Not image2 path or dest dir path provided: ' + str(image2) + ', ' + str(dest_dir) + if logfile: log_message(logfile, message, 'error') + raise TypeError(message) + + +def copy_nifti(image1, image2=False, dest_dir=False, logfile=False): + """ + Create a copy of an Analyze format image + :param image1: (string) path to the original image + :param image2: (string, optional) path to the copy image + :param dest_dir: (string, optional) path to the destination folder + :return: + """ + + if image2: + if image1[-3:] == 'nii' and image2[-3:] == 'nii': + image1_img = image1[0:-3] + 'nii' + image2_img = image2[0:-3] + 'nii' + shutil.copy(image1_img, image2_img) + + return image2_img + + elif image1[-6:] == 'nii.gz' and image2[-6:] == 'nii.gz': + image1_img = image1[0:-6] + 'nii.gz' + image2_img = image2[0:-6] + 'nii.gz' + shutil.copy(image1_img, image2_img) + + return image2_img + + else: + message = 'Error! The provided image is not in Nifti format:' + str(image1) + if logfile: log_message(logfile, message, 'error') + raise TypeError(message) + + elif not image2 and isdir(str(dest_dir)): + + ext = splitext(basename(image1))[1] + + if ext == '.nii': + image1_img = image1[0:-3] + 'nii' + image2_img = join(dest_dir, basename(image1)[0:-3] + 'nii') + shutil.copy(image1_img, image2_img) + + return image2_img + + if ext == '.nii.gz': + image1_img = image1[0:-6] + 'nii.gz' + image2_img = join(dest_dir, basename(image1)[0:-6] + 'nii.gz') + shutil.copy(image1_img, image2_img) + + return image2_img + + else: + message = 'Error! The provided image is not in Nifti format:' + str(image1) + if logfile: log_message(logfile, message, 'error') + raise TypeError(message) + else: + message = 'Error! Not image2 path or dest dir path provided: ' + str(image2) + ', ' + str(dest_dir) + if logfile: log_message(logfile, message, 'error') + raise TypeError(message) + + + + +def create_analyze_from_imgdata(data, out, pix_x, pix_y, pix_z, tx, ty, tz, data_type="fl"): + + if data_type == "1b": + dtype=np.int8 + elif data_type == "2b": + dtype=np.int16 + elif data_type == "db": + dtype=np.float64 + else: + dtype=np.float32 + + hdr1 = nib.AnalyzeHeader() + hdr1.set_data_dtype(dtype) + hdr1.set_data_shape((pix_x,pix_y,pix_z)) + hdr1.set_zooms((tx,ty,tz)) + + f = open(data,'rb') + + img_data = hdr1.raw_data_from_fileobj(f) + + analyze_img = nib.AnalyzeImage(img_data, hdr1.get_base_affine(), hdr1) + + nib.save(analyze_img,out) + + +def create_nifti_from_imgdata(data, out, pix_x, pix_y, pix_z, tx, ty, tz, data_type="fl"): + + if data_type == "1b": + dtype=np.int8 + elif data_type == "2b": + dtype=np.int16 + elif data_type == "db": + dtype=np.float64 + else: + dtype=np.float32 + + hdr1 = nib.Nifti2Header() + #hdr1 = nib.Nifti1Header() + hdr1.set_data_dtype(dtype) + hdr1.set_data_shape((pix_x,pix_y,pix_z)) + hdr1.set_zooms((tx,ty,tz)) + + f = open(data,'rb') + + img_data = hdr1.raw_data_from_fileobj(f) + + nifti_img = nib.Nifti2Image(img_data, hdr1.get_base_affine(), hdr1) + #nifti_img = nib.Nifti1Image(img_data, hdr1.get_base_affine(), hdr1) + + nib.save(nifti_img,out) + + +def create_nifti1_from_imgdata(data, out, pix_x, pix_y, pix_z, tx, ty, tz, data_type="fl"): + + if data_type == "1b": + dtype=np.int8 + elif data_type == "2b": + dtype=np.int16 + elif data_type == "db": + dtype=np.float64 + else: + dtype=np.float32 + + hdr1 = nib.Nifti1Header() + hdr1.set_data_dtype(dtype) + hdr1.set_data_shape((pix_x,pix_y,pix_z)) + hdr1.set_zooms((tx,ty,tz)) + + f = open(data,'rb') + + img_data = hdr1.raw_data_from_fileobj(f) + + nifti_img = nib.Nifti1Image(img_data, hdr1.get_base_affine(), hdr1) + + nib.save(nifti_img,out) + + + +def read_analyze_header(header_file,logfile): + + img = nib.load(header_file) + + zpix = img.shape[2] + #zsize = abs(img.get_affine[2,2]) + zsize = abs(img.affine[2,2]) + xpix = img.shape[0] + #xsize = abs(img.get_affine[0,0]) + xsize = abs(img.affine[0,0]) + ypix = img.shape[1] + #ysize = abs(img.get_affine[1,1]) + ysize = abs(img.affine[1,1]) + + return zpix, zsize, xpix, xsize, ypix, ysize + +def write_interfile_header(header_file,matrix_size_x,pixel_size_x, matrix_size_y,pixel_size_y, matrix_size_z,pixel_size_z, offset_z=0): + + image_v = os.path.basename(header_file)[0:-2] + "v" + fheader_hv = open(header_file, "w") + + offset_x = matrix_size_x/2*pixel_size_x + 0.5*pixel_size_x + offset_y = matrix_size_y/2*pixel_size_y + 0.5*pixel_size_y + + fheader_hv.write("!INTERFILE :=\n") + fheader_hv.write("name of data file := %s\n" % image_v) + fheader_hv.write("!GENERAL DATA :=\n") + fheader_hv.write("!GENERAL IMAGE DATA :=\n") + fheader_hv.write("!type of data := PET\n") + fheader_hv.write("imagedata byte order := LITTLEENDIAN\n") + fheader_hv.write("!PET STUDY (General) :=\n") + fheader_hv.write("!PET data type := Image\n") + fheader_hv.write("process status := Reconstructed\n") + fheader_hv.write("!number format := float\n") + fheader_hv.write("!number of bytes per pixel := 4\n") + fheader_hv.write("number of dimensions := 3\n") + fheader_hv.write("matrix axis label [1] := x\n") + fheader_hv.write("!matrix size [1] := %s\n" % matrix_size_x) + fheader_hv.write("scaling factor (mm/pixel) [1] := %s\n" % pixel_size_x) + fheader_hv.write("matrix axis label [2] := y\n") + fheader_hv.write("!matrix size [2] := %s\n" % matrix_size_y) + fheader_hv.write("scaling factor (mm/pixel) [2] := %s\n" % pixel_size_y) + fheader_hv.write("matrix axis label [3] := z\n") + fheader_hv.write("!matrix size [3] := %s\n" % matrix_size_z) + fheader_hv.write("scaling factor (mm/pixel) [3] := %s\n" % pixel_size_z) + fheader_hv.write("first pixel offset (mm) [1] := -%s\n" % offset_x) + fheader_hv.write("first pixel offset (mm) [2] := -%s\n" % offset_y) + fheader_hv.write("first pixel offset (mm) [3] := 0\n") + fheader_hv.write("number of time frames := 1\n") + fheader_hv.write("!END OF INTERFILE :=\n") + + fheader_hv.close() + +def nii_analyze_convert(image, logfile=False, outfile=False): + """ + Converts the provided image file to the analyze or nifti format + depending on the filex extension + :param image: image to be converted + :return: + """ + + # if not logfile: logfile = join(dirname(image), 'log_nii_analyze_convert.txt') + + ext = os.path.splitext(image)[1] + img, data = nib_load(image) + data = data.astype(np.float32) #casting to avoid problems when applying nib.save with no float data + hdr = nib.AnalyzeHeader() + hdr.set_data_dtype(np.float32) + hdr.set_data_shape(data.shape) + + if ext == '.nii': + # n2a = rsc.get_rsc('nii2analyze', 'exe') + if outfile: + cimage = outfile + else: + cimage = image.replace('.nii', '.hdr') + # rcommand = '%s %s %s >> %s' % (n2a, image, cimage, logfile) + # osrun(rcommand, logfile) + imageToWrite = nib.AnalyzeImage(data, img.affine, hdr) + elif ext == '.hdr': + # a2n = rsc.get_rsc('analyze2nii', 'exe') + if outfile: + cimage = outfile + else: + cimage = image.replace('.hdr', '.nii') + # rcommand = '%s %s %s >> %s' % (a2n, image, cimage, logfile) + # osrun(rcommand, logfile) + imageToWrite = nib.Nifti1Image(data, img.affine, hdr) + else: + # a2n = rsc.get_rsc('analyze2nii', 'exe') + if outfile: + cimage = outfile + else: + cimage = image.replace('.img', '.nii') + # image_hdr = str(image).replace(".img", ".hdr") + # rcommand = '%s %s %s >> %s' % (a2n, image_hdr, cimage, logfile) + # osrun(rcommand, logfile) + imageToWrite = nib.Nifti1Image(data, img.affine, hdr) + + nib.save(imageToWrite, cimage) + + if exists(cimage): + if outfile: + shutil.move(cimage, outfile) + return outfile + else: + return cimage + else: + return 0 + +def anything_to_hdr_convert(image, logfile=False, outfile=False ): + """ + This function will try to convert dicom, .nii.gz or .nii to analyze... + """ + + if image[-3:] == "hdr" or image[-3:] == "img": + return image[0:-3]+"hdr" + + elif image[-3:] == "nii": + hdr = nii_analyze_convert(image,logfile=logfile) + if exists(hdr): + return hdr + else: + raise TypeError ("nifti-analyze conversion failed....") + + elif image[-6:]=="nii.gz": + hdr = image[0:-6] + 'hdr' + nii_analyze_convert(image,logfile=logfile, outfile=hdr) + if exists(hdr): + return hdr + else: + raise TypeError ("nifti-analyze conversion failed....") + + elif isdir(image): + print("I think the provided image:\n %s \n is a dicom directory. I will try to convert it...") + dicom2nii = rsc.get_rsc("dicom2nii","exe") + nii = join(dirname(image),"image.nii") + rcommand = '%s %s %s' % (dicom2nii, image, nii) + osrun(rcommand,logfile) + if exists(nii): + hdr = nii_analyze_convert(nii,logfile=logfile) + os.remove(nii) + if exists(hdr): + return hdr + else: + raise TypeError ("nifti-analyze conversion failed....") + else: + raise TypeError ("dicom-nifti conversion failed....") + + elif image[-2:] == "hv": #Only works with floats data + + with open(image) as f: + lines = f.readlines() + + lines = [x.strip() for x in lines] + + pixel = [re.compile(f"\!matrix size \[{i}\].*") for i in range(1, 4)] + pixel_size = [re.compile(f"scaling factor \(mm.pixel\) \[{i}\].*") for i in range(1, 4)] + + pixel_x = list(filter(pixel[0].match, lines))[0].split()[-1] + pixel_size_x = list(filter(pixel_size[0].match, lines))[0].split()[-1] + pixel_y = list(filter(pixel[1].match, lines))[0].split()[-1] + pixel_size_y = list(filter(pixel_size[1].match, lines))[0].split()[-1] + pixel_z = list(filter(pixel[2].match, lines))[0].split()[-1] + pixel_size_z = list(filter(pixel_size[2].match, lines))[0].split()[-1] + + hdr_header = image[0:-2] + "hdr" + # img_file = image[0:-2] + "img" + data_file = image[0:-2] + "v" + + #This will convert .hv to .hdr and copy the data + # gen_hdr = rsc.get_rsc("gen_hdr", "fruitcake") + # rcommand = "%s %s %s %s %s fl %s %s %s 0" % (gen_hdr, hdr_header[0:-4], pixel_x, pixel_y, pixel_z, pixel_size_x, pixel_size_y, pixel_size_z) + # osrun(rcommand, logfile) + # shutil.copy(data_file, img_file) + create_analyze_from_imgdata(data_file,hdr_header,float(pixel_x),float(pixel_y),float(pixel_z),float(pixel_size_x),float(pixel_size_y),float(pixel_size_z)) + #nii_analyze_convert("inicial.hdr",logfile,"aux.nii") + #nii_analyze_convert("aux.nii",logfile,hdr_header) + #os.remove("aux.nii") + + if exists(hdr_header): + return hdr_header + else: + raise TypeError ("Interfile-analyze conversion failed....") + +def convert_hv_to_nii(image, logfile=False, outfile=False): + + with open(image) as f: + lines = f.readlines() + + lines = [x.strip() for x in lines] + + pixel = [re.compile(f"\!matrix size \[{i}\].*") for i in range(1, 4)] + pixel_size = [re.compile(f"scaling factor \(mm.pixel\) \[{i}\].*") for i in range(1, 4)] + + pixel_x = list(filter(pixel[0].match, lines))[0].split()[-1] + pixel_size_x = list(filter(pixel_size[0].match, lines))[0].split()[-1] + pixel_y = list(filter(pixel[1].match, lines))[0].split()[-1] + pixel_size_y = list(filter(pixel_size[1].match, lines))[0].split()[-1] + pixel_z = list(filter(pixel[2].match, lines))[0].split()[-1] + pixel_size_z = list(filter(pixel_size[2].match, lines))[0].split()[-1] + + hdr_header = image[0:-2] + "nii" + data_file = image[0:-2] + "v" + + create_nifti1_from_imgdata(data_file,hdr_header,float(pixel_x),float(pixel_y),float(pixel_z),float(pixel_size_x),float(pixel_size_y),float(pixel_size_z)) + + if exists(hdr_header): + return hdr_header + else: + raise TypeError ("Interfile-analyze conversion failed....") + + + + +def prepare_input_image(image_hdr, logfile, min_voxel_size=1): + """ + This method converts input_image to float data type, re-sizes the image to + 1mm size voxels if too large to keep a reasonable analysis execution time and + removes the negative and NaN values. + :param input_image: image to prepare for the analysis + :return: + """ + + # Tools for image manipulation + # change_format = rsc.get_rsc('change_format', 'fruitcake') + # change_matrix = rsc.get_rsc('change_img_matrix', 'fruitcake') + # erase_negs = rsc.get_rsc('erase_negs', 'fruitcake') + # erase_nans = rsc.get_rsc('erase_nans', 'fruitcake') + + # Convert input image to Analyze format and float data type + # rcommand = '%s %s %s fl >> %s' % (change_format, image_hdr, image_hdr, logfile) + # osrun(rcommand, logfile) + + ## # CONTEMPLAR POSIBILIDAD DE CONVERTIR ESTO EN FUNCIÓN PARA SUSTITUIR AL "cambia_formato_hdr" de fruitcake + change_format(image_hdr, "fl", logfile) + + ### + + # Resize image if necessary + # ndims = recalculate_matrix(image_hdr, min_voxel_size) + # rcommand = '%s %s %s %s %s %s novecino >> %s' % (change_matrix, image_hdr, image_hdr, + # str(ndims[0]), str(ndims[1]), str(ndims[2]), logfile) + # osrun(rcommand, logfile) + image_hdr = resampleXYvoxelSizes(image_hdr, min_voxel_size, logfile) + image_hdr = resampleZvoxelSize(image_hdr, min_voxel_size, logfile) + + # Erase negative and NaN values + # rcommand = '%s %s %s >> %s' % (erase_negs, image_hdr, image_hdr, logfile) + # osrun(rcommand, logfile) + # rcommand = '%s %s %s >> %s' % (erase_nans, image_hdr, image_hdr, logfile) + # osrun(rcommand, logfile) + remove_neg_nan(image_hdr) + + + return image_hdr + +def recalculate_matrix(input_image, voxelsize, mode="downsampling"): + + new_dimensions = [] + image_load, data = nib_load(input_image) + header = image_load.header + sizes = header.get_zooms() + dimensions = image_load.shape + x_lenght = int(sizes[0]*dimensions[0]/voxelsize) + y_lenght = int(sizes[1]*dimensions[1]/voxelsize) + z_lenght = int(sizes[2]*dimensions[2]/voxelsize) + if mode == "downsampling": + if sizes[0]voxelsize: + new_dimensions.append(x_lenght) + else: + new_dimensions.append(dimensions[0]) + if sizes[1]>voxelsize: + new_dimensions.append(y_lenght) + else: + new_dimensions.append(dimensions[1]) + if sizes[2]>voxelsize: + new_dimensions.append(z_lenght) + else: + new_dimensions.append(dimensions[2]) + + return new_dimensions + +def verify_roi_exists(rois_image, roi_number): + """ + Compute the number of voxels in each ROI + :param rois_image: (string) path to the ROI parcelled image + :param rois_list: (array) ROIs indexing + :return: False if the number of voxels is zero + :return: nvox is not zero + """ + rois_img = nib.load(rois_image) + rois_data = rois_img.get_data()[:, :, :] + indx = np.where(rois_data == roi_number) + nvox = len(rois_data[indx]) + if nvox == 0: + return False + else: + return True + +def operate_single_image(input_image, operation, factor, output_image, logfile): + """ + Given an input image, multiply or divide it by a numerical factor + saving the result as output_image + :param input_image: image base operation on + #:param operation: 1 = multiply, 2 = divide + :param operation: 'mult' = multiply, 'div' = divide + :param factor: operation factor + :param output_image: output image file + :return: + """ + + img = nib.load(input_image) + data = img.get_data()[:,:,:] + data = np.nan_to_num(data) + + + if operation == 'mult': + data = data * float(factor) + elif operation == 'div': + data = data / float(factor) + else: + message = "Error! Invalid operation: " +str(operation) + print(message) + log_message(logfile, message, 'error') + + hdr1 = nib.AnalyzeHeader() + hdr1.set_data_dtype(img.get_data_dtype()) + hdr1.set_data_shape(img.shape) + #hdr1.set_zooms(abs(np.diag(img.affine))) + #hdr1.set_zooms(abs(np.diag(img.affine))[0:3]) + #hdr1.set_zooms(abs(np.diag(img.affine))[0:4]) + hdr1.set_zooms(abs(np.diag(img.affine))[0:img.ndim]) + + analyze_img = nib.AnalyzeImage(data, hdr1.get_base_affine(), hdr1) + + nib.save(analyze_img,output_image) + + +def operate_single_image_nii(input_image, operation, factor, output_image, logfile, check_nans=True): + """ + Given an input image, multiply or divide it by a numerical factor + saving the result as output_image + :param input_image: image base operation on + #:param operation: 1 = multiply, 2 = divide + :param operation: 'mult' = multiply, 'div' = divide + :param factor: operation factor + :param output_image: output image file + :return: + """ + + img = nib.load(input_image) + data = img.get_data()[:,:,:] + data = np.nan_to_num(data) #re-added + """ + if check_nans: + data = np.nan_to_num(data) + #data[np.isnan(data)]=0 + """ + if operation == 'mult': + #data = data * float(factor) + data *= float(factor) #re-added + elif operation == 'div': + #data = data / float(factor) + data /= float(factor) + else: + message = "Error! Invalid operation: " +str(operation) + print(message) + log_message(logfile, message, 'error') + + hdr1 = nib.Nifti2Header() + #hdr1 = nib.Nifti1Header() + hdr1.set_data_dtype(img.get_data_dtype()) + hdr1.set_data_shape(img.shape) + #hdr1.set_zooms(abs(np.diag(img.affine))) + #hdr1.set_zooms(abs(np.diag(img.affine))[0:3]) + #hdr1.set_zooms(abs(np.diag(img.affine))[0:4]) + hdr1.set_zooms(abs(np.diag(img.affine))[0:img.ndim]) + + nifti_img = nib.Nifti2Image(data, hdr1.get_base_affine(), hdr1) + #nifti_img = nib.Nifti1Image(data, hdr1.get_base_affine(), hdr1) + + nib.save(nifti_img,output_image) + + +def operate_images_analyze(image1, image2, out_image, operation='mult'): + """ + Given the input images, calculate the multiplication image or the ratio between them + :param image1: string, path to the first image + :param image2: string, path to the second image + :param operation: string, multi (default) for multiplication divid for division + :param out_image: string (optional), path to the output image + :return: + """ + img1, data1 = nib_load(image1) + img2, data2 = nib_load(image2) + + # TODO CHECK IF NEGATIVE VALUES NEED TO BE REMOVED + # Remove NaN and negative values + #data1 = np.nan_to_num(data1) + #data2 = np.nan_to_num(data2) + + if operation == 'mult': + res_data = data1 * data2 + elif operation == 'div': + res_data = data1 / data2 + elif operation == 'sum': + res_data = data1 + data2 + elif operation == 'diff': + res_data = data1 - data2 + else: + message = 'Error! Unknown operation: ' + str(operation) + raise TypeError(message) + + hdr1 = nib.AnalyzeHeader() + hdr1.set_data_dtype(img1.get_data_dtype()) + hdr1.set_data_shape(img1.shape) + ##hdr1.set_zooms(abs(np.diag(img1.affine))[0:3]) + ##hdr1.set_zooms(abs(np.diag(img1.affine))[0:4]) + hdr1.set_zooms(abs(np.diag(img1.affine))[0:img1.ndim]) + + analyze_img = nib.AnalyzeImage(res_data, hdr1.get_base_affine(), hdr1) + + nib.save(analyze_img,out_image) + + +def operate_images_nii(image1, image2, out_image, operation='mult', check_nans=True): + """ + Given the input images, calculate the multiplication image or the ratio between them + :param image1: string, path to the first image + :param image2: string, path to the second image + :param operation: string, multi (default) for multiplication divid for division + :param out_image: string (optional), path to the output image + :return: + """ + img1, data1 = nib_load(image1) + img2, data2 = nib_load(image2) + + #data1 = np.nan_to_num(data1) #re-added + #data2 = np.nan_to_num(data2) #re-added + + """ + if check_nans: + # TODO CHECK IF NEGATIVE VALUES NEED TO BE REMOVED + # Remove NaN and negative values + #data1 = np.nan_to_num(data1) + #data2 = np.nan_to_num(data2) + + data1[np.isnan(data1)]=0 + data2[np.isnan(data2)]=0 + """ + + if operation == 'mult': + #res_data = data1 * data2 #re-added + data1 *= data2 + elif operation == 'div': + #res_data = data1 / data2 + data1 /= data2 + elif operation == 'sum': + #res_data = data1 + data2 #re-added + data1 += data2 + elif operation == 'diff': + #res_data = data1 - data2 + data1 -= data2 + else: + message = 'Error! Unknown operation: ' + str(operation) + raise TypeError(message) + + hdr1 = nib.Nifti2Header() + hdr1.set_data_dtype(img1.get_data_dtype()) + hdr1.set_data_shape(img1.shape) + ##hdr1.set_zooms(abs(np.diag(img1.affine))[0:3]) + ##hdr1.set_zooms(abs(np.diag(img1.affine))[0:4]) + hdr1.set_zooms(abs(np.diag(img1.affine))[0:img1.ndim]) + + #nifti_img = nib.Nifti2Image(res_data, hdr1.get_base_affine(), hdr1) + nifti_img = nib.Nifti2Image(data1, hdr1.get_base_affine(), hdr1) + + nib.save(nifti_img,out_image) + + +def operate_sinograms_nii(image1, image2, out_image, operation='mult'): + """ + Given the input images, calculate the multiplication image or the ratio between them + :param image1: string, path to the first image + :param image2: string, path to the second image + :param operation: string, multi (default) for multiplication divid for division + :param out_image: string (optional), path to the output image + :return: + """ + img1, data1 = nib_load(image1) + img2, data2 = nib_load(image2) + + if operation == 'mult': + res_data = data1 * data2 + elif operation == 'div': + res_data = data1 / data2 + elif operation == 'sum': + res_data = data1 + data2 + elif operation == 'diff': + res_data = data1 - data2 + else: + message = 'Error! Unknown operation: ' + str(operation) + raise TypeError(message) + + hdr1 = nib.Nifti2Header() + hdr1.set_data_dtype(img1.get_data_dtype()) + hdr1.set_data_shape(img1.shape) + hdr1.set_zooms(abs(np.diag(img1.affine))[0:img1.ndim]) + + nifti_img = nib.Nifti2Image(res_data, hdr1.get_base_affine(), hdr1) + + nib.save(nifti_img,out_image) + + + +def smooth_analyze(image,fwhm, output): + + from nibabel import processing as nibproc + + img =nib.load(image) + smoothed=nibproc.smooth_image(img,fwhm,out_class=nib.AnalyzeImage) + nib.save(smoothed,output) + + +def smooth_nifti(image,fwhm, output): + + from nibabel import processing as nibproc + + img =nib.load(image) + smoothed=nibproc.smooth_image(img,fwhm,out_class=nib.Nifti2Image) + #smoothed=nibproc.smooth_image(img,fwhm,out_class=nib.Nifti1Image) + nib.save(smoothed,output) + + +def log_message(logfile, message, mode='info'): + """" + Print outs logger messages to the specified logfile + """ + + #ctime = read_time(datetime.datetime.now()) + ctime = datetime.datetime.now() + + separator = '\n###########################################################' + + if mode == 'exe': + stream = separator + '\nTIME: ' + str(ctime) + '\n\nEXE: ' + message + separator + '\n' + elif mode == 'info': + stream = separator + '\nTIME: ' + str(ctime) + '\n\nINFO: ' + message + separator + '\n' + elif mode == 'warning': + stream = separator + '\nTIME: ' + str(ctime) + '\n\nWARNING: ' + message + separator + '\n' + elif mode == 'error': + stream = separator + '\nTIME: ' + str(ctime) + '\n\nERROR: ' + message + separator + '\n' + else: + stream = separator + '\nTIME: ' + str(ctime) + '\n\n' + message + '\n' + + if exists(logfile): + with open(logfile, 'a') as lfile: + lfile.write(stream) + else: + with open(logfile, 'w') as lfile: + lfile.write(stream) + +def reorient_dcmtonii(image_path): + + + components=os.path.split(image_path) + path = components[0] + image = components[1] + converter = Dcm2nii() + converter.inputs.source_names=[image_path] + converter.inputs.reorient_and_crop=True + # converter.nii_output=False + converter.run() + + reor_ima_name = "o"+image + if exists(join(path,reor_ima_name)): + shutil.copy(join(path,reor_ima_name),image_path) + os.remove(join(path,reor_ima_name)) + os.remove(join(path,"co"+image)) + else: + # converter.inputs_reorient_and_crop=False + # converter.run() + # shutil.copy(join(path,"f"+image), image_path) + os.remove(join(path,"c"+image)) + # os.remove(join(path,"f"+image)) + + + +def petmr2maps(pet_image, mri_image, ct_image, log_file, spm_run, output_dir, mode="SimSET"): + """ + It will create act and att maps from PET and MR images. + Required inputs are: + pet_image: dicom_dir, nii.gz, nii or Analyze (.hdr or .img) + mri_image: dicom_dir, nii.gz, nii or Analyze (.hdr or .img) + mode: Choose STIR or SIMSET. The maps will be different for each simulation. + The inputs will be stored to Data/simulation_name/Patient as reformatted/corregistered Analyze + #The maps will be stored on Data/simulation_name/Maps + The maps will be stored on Results/output_name/Maps + """ + message = "GENERATING ACT AND ATT MAPS FROM PET, (CT) and MR IMAGES" + log_message(log_file, message, mode='info') + + reorient_dcmtonii(pet_image) + reorient_dcmtonii(mri_image) + reorient_dcmtonii(ct_image) + #First of all lets take all to analyze + pet_hdr = anything_to_hdr_convert(pet_image) + #pet_hdr = copy_analyze(pet_hdr,image2=False,dest_dir=patient_dir) + pet_hdr = prepare_input_image(pet_hdr,log_file,min_voxel_size=1.5) + pet_img = pet_hdr[0:-3]+"img" + + if ct_image: #ct_image is not empty + ct_hdr = anything_to_hdr_convert(ct_image) + ct_hdr = prepare_input_image(ct_hdr,log_file,min_voxel_size=1.5) + ct_img = ct_hdr[0:-3]+"img" + + mri_hdr = anything_to_hdr_convert(mri_image) + #mri_hdr = copy_analyze(mri_hdr,image2=False,dest_dir=patient_dir) + mri_hdr = prepare_input_image(mri_hdr,log_file,min_voxel_size=1.5) + mri_img = mri_hdr[0:-3]+"img" + + #make mri image square + makeImageSquare(mri_hdr, log_file) + + #Performing PET-CT/MR coregister + mfile = os.path.join(output_dir,"fusion_pet_to_mri.m") + # correg_pet_hdr = fsl_flirt(mri_hdr, pet_hdr, log_file) + # correg_pet_img =correg_pet_hdr[0:-3]+"img" + correg_pet_img = spm.image_fusion(spm_run, mfile, mri_img, pet_img, log_file) + correg_ct_img="" + if ct_image: #ct_image is not empty + mfile = os.path.join(output_dir,"fusion_ct_to_mri.m") + correg_ct_img = spm.image_fusion(spm_run, mfile, mri_img, ct_img, log_file) + + #Now the map generation + from utils.patient2maps import patient2maps + + my_map_generation = patient2maps(spm_run, output_dir, log_file, + mri_img, correg_pet_img, correg_ct_img, mode=mode) + activity_map_hdr, attenuation_map_hdr = my_map_generation.run() + + return activity_map_hdr, attenuation_map_hdr + +def convert_map_values(act_map,att_map,output_dir,log_file,mode="SimSET"): + + message = "CONVERTING ACT AND ATT TO %s" % mode + log_message(log_file, message, mode='info') + + act_map = anything_to_hdr_convert(act_map) + att_map = anything_to_hdr_convert(att_map) + + cambia_formato = rsc.get_rsc('change_format', 'fruitcake') + cambia_val = rsc.get_rsc('change_values', 'fruitcake') + + new_att_map = join(output_dir, "att_map_" + mode +".hdr") + new_act_map = join(output_dir,"act_map_" + mode +".hdr") + + if mode == "SimSET": + + #rcommand = '%s %s %s 0.096 1 >> %s' % (cambia_val, att_map, new_att_map, log_file) + rcommand = '%s %s %s 0.096 4 >> %s' % (cambia_val, att_map, new_att_map, log_file) + osrun(rcommand, log_file) + rcommand = '%s %s %s 0.135 3 >> %s' % (cambia_val, new_att_map, new_att_map, log_file) + osrun(rcommand, log_file) + rcommand = '%s %s %s 1B >> %s' % (cambia_formato, new_att_map, new_att_map, log_file) + osrun(rcommand, log_file) + rcommand = '%s %s %s 1B >> %s' % (cambia_formato, act_map, new_act_map, log_file) + osrun(rcommand, log_file) + + if mode == "STIR": + + rcommand = '%s %s %s fl >> %s' % (cambia_formato, att_map, new_att_map, log_file) + osrun(rcommand, log_file) + rcommand = '%s %s %s fl >> %s' % (cambia_formato, new_act_map, new_act_map, log_file) + osrun(rcommand, log_file) + rcommand = '%s %s %s 1 0.096 >> %s' % (cambia_val, new_att_map, new_att_map, log_file) + osrun(rcommand, log_file) + rcommand = '%s %s %s 3 0.135 >> %s' % (cambia_val, new_att_map, new_att_map, log_file) + osrun(rcommand, log_file) + + return new_act_map, new_att_map + +def ncounts(image_hdr): + + img, data = nib_load(image_hdr) + ncounts = np.sum(data) + return ncounts + +def convert_simset_sino_to_stir(input_img, output=False): + + ## To be continued.... + + simset_img = nib.load(input_img) + simset_img_data = simset_img.get_fdata() + shape = simset_img_data.shape + + n_slices = shape[2] + nrings = np.sqrt(n_slices) + n_x = shape[0] + + input_definition = [] + + for i in range(n_slices): + + ring1,ring2 = divmod(i,nrings) + segment = ring1 - ring2 + slice_def = [i, ring1, ring2, segment] + input_definition.append(slice_def) + + output_definition = sorted(input_definition, key=itemgetter(3)) + stir_img_data = np.empty(shape, dtype=float, order='C') + stir_img_data_flip_x = np.empty(shape, dtype=float, order='C') + + for i in range(n_slices): + + output_index = output_definition[i][0] + input_slice = simset_img_data[:,:,output_index] + stir_img_data[:,:,i] = input_slice + + for j in range(n_x): + stir_img_data_flip_x[j,:,:] = stir_img_data[n_x-1-j, :, :] + + #stir_img = nib.AnalyzeImage(stir_img_data, simset_img.affine, simset_img.header) + stir_img = nib.AnalyzeImage(stir_img_data_flip_x, simset_img.affine, simset_img.header) + + if not output: + output = input_img [0:-4] + '_stir.hdr' + + (stir_img_data_flip_x.astype(np.float32)).tofile(output[0:-4] + '.bin') #ADDED, DELETE. + + nib.save(stir_img,output) + +#Re-added: WARNING! THIS NEEDS TO BE REVISED. BUT THIS IS THE GOOD ONE (INITIAL) +""" +def convert_simset_sino_to_stir_nii(input_img, output=False): + + ## To be continued.... + + simset_img = nib.load(input_img) + simset_img_data = simset_img.get_fdata() + shape = simset_img_data.shape + + n_slices = shape[2] + nrings = np.sqrt(n_slices) + n_x = shape[0] + + input_definition = [] + + for i in range(n_slices): + + ring1,ring2 = divmod(i,nrings) + segment = ring1 - ring2 + slice_def = [i, ring1, ring2, segment] + input_definition.append(slice_def) + + output_definition = sorted(input_definition, key=itemgetter(3)) + stir_img_data = np.empty(shape, dtype=float, order='C') + stir_img_data_flip_x = np.empty(shape, dtype=float, order='C') + + for i in range(n_slices): + + output_index = output_definition[i][0] + input_slice = simset_img_data[:,:,output_index] + stir_img_data[:,:,i] = input_slice + + #THIS FLIP IS NOT NECESSARY! [MAYBE IT IS]: + for j in range(n_x): + stir_img_data_flip_x[j,:,:] = stir_img_data[n_x-1-j, :, :] + + #stir_img = nib.AnalyzeImage(stir_img_data_flip_x, simset_img.affine, simset_img.header) + #stir_img = nib.Nifti2Image(stir_img_data, simset_img.affine, simset_img.header) + stir_img = nib.Nifti2Image(stir_img_data_flip_x, simset_img.affine, simset_img.header) + #stir_img = nib.Nifti1Image(stir_img_data_flip_x, simset_img.affine, simset_img.header) + + if not output: + #output = input_img [0:-4] + '_stir.nii' + output = input_img [0:-7] + '_stir.nii.gz' + + #nib.save(stir_img,output[0:-4] + '.nii') + #nib.save(stir_img,output[0:-4] + '.hdr') + #nib.save(stir_img,output[0:-7] + '.hdr') + #nib.save(stir_img, output[0:-7] + '.nii.gz') + nib.save(stir_img, output) +""" + +""" +def convert_simset_sino_to_stir_nii(input_img, output=False): + + simset_img = nib.load(input_img) + simset_img_data = simset_img.get_fdata() + shape = simset_img_data.shape + + n_slices = shape[2] + nrings = np.sqrt(n_slices) + n_x = shape[0] + + input_definition = [] + + for i in range(n_slices): + + ring1,ring2 = divmod(i,nrings) + segment = ring1 - ring2 + slice_def = [i, ring1, ring2, segment] + input_definition.append(slice_def) + + indices = np.arange(n_slices).astype(int) + newring1 = indices // nrings + newring2 = indices % nrings + newsegment = newring1 - newring2 + + newinput_definition = np.column_stack([indices, newring1, newring2, newsegment]) + #newoutput_definition = newinput_definition[np.argsort(newinput_definition[:, 3])] + #newoutput_definition = newoutput_definition.tolist() + newoutput_definition = sorted(newinput_definition, key=itemgetter(3)) + + + #print("OLD INPUT DEFINITION:", input_definition) + #print("\n\n\n\n") + #print("NEW INPUT DEFINITION:", newinput_definition) + + output_definition = sorted(input_definition, key=itemgetter(3)) + stir_img_data = np.empty(shape, dtype=float, order='C') + stir_img_data_flip_x = np.empty(shape, dtype=float, order='C') + + print("OLD INPUT DEFINITION:", output_definition) + print("\n\n\n\n") + print("NEW INPUT DEFINITION:", newoutput_definition) + + + for i in range(n_slices): + + output_index = output_definition[i][0] + input_slice = simset_img_data[:,:,output_index] + stir_img_data[:,:,i] = input_slice + + stir_img_data_flip_x = stir_img_data[::-1, :, :] + + stir_img = nib.Nifti2Image(stir_img_data_flip_x, simset_img.affine, simset_img.header) + + if not output: + output = input_img [0:-7] + '_stir.nii.gz' + + nib.save(stir_img, output) +""" + + + +def convert_simset_sino_to_stir_nii(input_img, output=False): + + simset_img = nib.load(input_img) + simset_img_data = simset_img.get_fdata() + shape = simset_img_data.shape + + n_slices = shape[2] + nrings = int(np.sqrt(n_slices)) # assuming perfect square + n_x = shape[0] + + # Build definition table + indices = np.arange(n_slices) + ring1 = indices // nrings + ring2 = indices % nrings + segment = ring1 - ring2 + + input_definition = np.column_stack([indices, ring1, ring2, segment]) + + # Sort by segment + #output_definition = input_definition[np.argsort(input_definition[:, 3])] #not working. + output_definition = sorted(input_definition, key=itemgetter(3)) + output_definition = np.array(output_definition) + + # Reorder slices in one vectorized step + order = output_definition[:, 0].astype(int) + stir_img_data = simset_img_data[:, :, order] + + # Flip X dimension + stir_img_data_flip_x = stir_img_data[::-1, :, :] + + # Save + stir_img = nib.Nifti2Image(stir_img_data_flip_x, simset_img.affine, simset_img.header) + + if not output: + output = input_img[:-7] + '_stir.nii.gz' + + nib.save(stir_img, output) + + + + +""" +def convert_simset_sino_to_stir_nii(input_img, output=False): + + simset_img = nib.load(input_img) + simset_img_data = simset_img.get_data() #simset_img.get_fdata() + shape = simset_img_data.shape + + n_slices = shape[2] + nrings = int(np.sqrt(n_slices)) + + indices = np.arange(n_slices) + ring1 = indices // nrings + ring2 = indices % nrings + segment = ring1 - ring2 + + order = np.argsort(segment) + + stir_img_data = simset_img_data[:, :, order] + stir_img_data = stir_img_data[::-1, :, :] #flip_x + + stir_img = nib.Nifti2Image(stir_img_data, simset_img.affine, simset_img.header) + + if not output: + output = input_img[:-7] + '_stir.nii.gz' + + nib.save(stir_img, output) +""" + + +""" +def convert_simset_sino_to_stir_nii(input_img, output): + + simset_img = nib.load(input_img) + data = simset_img.dataobj # memory-mapped + + print("Check 1") + + shape = simset_img.shape + n_slices = shape[2] + nrings = int(np.sqrt(n_slices)) + + indices = np.arange(n_slices) + ring1 = indices // nrings + ring2 = indices % nrings + segment = ring1 - ring2 + order = np.argsort(segment) + + print("Check 2") + + # Preallocate output (float32 recommended if safe) + out_data = np.empty(shape, dtype=data.dtype) + + for new_z, old_z in enumerate(order): + out_data[:, :, new_z] = data[:, :, old_z] + + print("Check 3") + + # Flip in-place + out_data[:] = out_data[::-1, :, :] + + print("Check 4") + + stir_img = nib.Nifti2Image(out_data, simset_img.affine, simset_img.header) + nib.save(stir_img, output) + print("Check 5") +""" + + +def copy_sinogram_stir_to_output(input_img, output_img): + + + simset_sino = nib.load(input_img) + + if input_img[-7:] == '.nii.gz': + nib.save(simset_sino, input_img[0:-7] + '.hdr') + shutil.copy(input_img[0:-6] + "img", output_img) + os.remove(input_img[0:-6] + "img") + os.remove(input_img[0:-6] + "hdr") + + elif input_img[-4:] == '.nii': + nib.save(simset_sino, input_img[0:-4] + '.hdr') + shutil.copy(input_img[0:-3] + "img", output_img) + os.remove(input_img[0:-3] + "img") + os.remove(input_img[0:-3] + "hdr") + + else: + print("Image is not in the NifTi format!") + + + #Re-added: + #shutil.copy(input_img[0:-3] + "nii", output_img) + #os.remove(input_img[0:-3] + "nii") + #os.remove(input_img[0:-3] + "hdr") + + #shutil.copy(input_img[0:-6] + "img", output_img) + #os.remove(input_img[0:-6] + "img") + #os.remove(input_img[0:-6] + "hdr") + + +def copy_reduced_sinogram_stir_to_output(input_img, output_img, nrings, max_segment): + + simset_sino = nib.load(input_img) + sino_dataobj = simset_sino.dataobj + + init_idx = int(np.sum([i for i in range(1, nrings-max_segment)])) #+1)])) + final_idx = nrings*nrings - init_idx + + sino_reduced = sino_dataobj[..., init_idx:final_idx].astype(sino_dataobj.dtype) + + sino_reduced_nii = nib.Nifti2Image(sino_reduced, simset_sino.affine) + nib.save(sino_reduced_nii, input_img[0:-7] + '.hdr') + + shutil.copy(input_img[0:-6] + "img", output_img) + os.remove(input_img[0:-6] + "img") + os.remove(input_img[0:-6] + "hdr") + + + +def resampleXYvoxelSizes(image_hdr, xyVoxelSize, log_file): + img = nib.load(image_hdr) + z_VoxelSize =img.header['pixdim'][3] + taget_vsize = (xyVoxelSize,xyVoxelSize,z_VoxelSize) + res_img = nibp.resample_to_output(img, taget_vsize, out_class=img.__class__) + nib.save(res_img,image_hdr) + + return image_hdr + + +def resampleZvoxelSize(image_hdr, zOutputvoxelSize, log_file): + img = nib.load(image_hdr) + x_VoxelSize = img.header['pixdim'][1] + y_VoxelSize = img.header['pixdim'][2] + taget_vsize = (x_VoxelSize,y_VoxelSize,zOutputvoxelSize) + res_img = nibp.resample_to_output(img, taget_vsize, out_class=img.__class__) + nib.save(res_img,image_hdr) + + return image_hdr + + +def makeImageSquare(image_hdr, log_file): + + # Tools for image manipulation + corta_pega_filcol_hdr = rsc.get_rsc('corta_pega_filcol_hdr', 'fruitcake') + zpix, zsize, xpix, xsize, ypix, ysize = read_analyze_header(image_hdr,log_file) + + pixDif = abs(xpix-ypix) + + if pixDif != 0: + if pixDif % 2 == 0: + oneSide = pixDif/2 + otherSide = oneSide + else: + oneSide = np.trunc(pixDif/2) + otherSide = oneSide +1 + + if xpix > ypix: + rcommand1 = '%s %s %s peg %s %s >> %s' % (corta_pega_filcol_hdr, image_hdr, image_hdr, "1", str(oneSide), log_file) + rcommand2 = '%s %s %s peg %s %s >> %s' % (corta_pega_filcol_hdr, image_hdr, image_hdr, "2", str(otherSide), log_file) + elif ypix > xpix: + rcommand1 = '%s %s %s peg %s %s >> %s' % (corta_pega_filcol_hdr, image_hdr, image_hdr, "3", str(oneSide), log_file) + rcommand2 = '%s %s %s peg %s %s >> %s' % (corta_pega_filcol_hdr, image_hdr, image_hdr, "4", str(otherSide), log_file) + + osrun(rcommand1, log_file) + osrun(rcommand2, log_file) + + +def scalImage(image_hdr, maxValue, log_file): + """ + Given the input image_hdr (analyze format), this function scales their values to the input maxValue + + """ + cambia_val_interval = rsc.get_rsc('change_interval', 'fruitcake') + img, data =nib_load(image_hdr, log_file) + + factor = maxValue/np.max(data) + + mask_hdr="mask.hdr" + rcommand = '%s %s %s 0 0.9 0 >> %s' % (cambia_val_interval, image_hdr, mask_hdr, log_file) + osrun(rcommand, log_file) + rcommand = '%s %s %s 0.9 10000000000 1 >> %s' % (cambia_val_interval, mask_hdr, mask_hdr, log_file) + osrun(rcommand, log_file) + + operate_single_image(image_hdr, "mult", factor, image_hdr, log_file) + + rcommand = '%s %s %s 0 1 1 >> %s' % (cambia_val_interval, image_hdr, image_hdr, log_file) + osrun(rcommand, log_file) + + operate_images_analyze(image_hdr, mask_hdr, image_hdr, "mult") + + os.remove(mask_hdr) + os.remove("mask.img") + +def remove_neg_nan(image_hdr): + + img, data = nib_load(image_hdr) + indx = np.where(data<0) + data[indx] = 0 + indx = np.where(np.isnan(data)) + data[indx] = 0 + + imageToWrite = nib.AnalyzeImage(data,img.affine,img.header) + nib.save(imageToWrite, "aux.hdr") + copy_analyze("aux.hdr",image_hdr) + os.remove("aux.hdr") + os.remove("aux.img") + + #return image_hdr + +def update_act_map(spmrun, act_map, att_map, orig_pet, simu_pet, output_act_map, axialFOV, log_file): + + output_dir = dirname(output_act_map) + # mfileFusion = join(dirname(output_act_map),"fusion.m") + + # Getting the necesary resources + # cambia_formato = rsc.get_rsc('change_format', 'fruitcake') + # cambia_val_interval = rsc.get_rsc('change_interval', 'fruitcake') + # operate_image_hdr = rsc.get_rsc('operate_image', 'fruitcake') + # remove_nan_hdr = rsc.get_rsc('erase_nans', 'fruitcake') + # remove_neg_hdr = rsc.get_rsc('erase_negs', 'fruitcake') + + # First step is coregistering the output image with the orig pet (coregistered to the mri) + #act_map_img = act_map[0:-3]+"img" + # simu_pet_img = simu_pet[0:-3]+"img" + orig_pet_img = orig_pet[0:-3]+"img" + # coreg_simpet_img = spm.image_fusion(spmrun, mfileFusion, orig_pet_img, simu_pet_img, log_file) + coreg_simpet_hdr = fsl_flirt(orig_pet, simu_pet, log_file) + coreg_simpet_img =coreg_simpet_hdr[0:-3]+"img" + + act, act_data = nib_load(act_map) + + + # Next, we will do a scaling by the mean + norm_factor = proportional_scaling(coreg_simpet_hdr, orig_pet, orig_pet, log_file) + operate_single_image(coreg_simpet_hdr,'mult',norm_factor, coreg_simpet_hdr, log_file) + + # Now we do a smoothing of both data to avoid multiply noise and perform the division + mfileSmooth = join(dirname(output_act_map),"smooth.m") + division_hdr = join(output_dir, "division.hdr") + s_coreg_simpet_img = spm.smoothing(spmrun, mfileSmooth, coreg_simpet_img, 5, "s", log_file) + s_orig_pet_img = spm.smoothing(spmrun, mfileSmooth, orig_pet_img, 5, "s", log_file) + + s_coreg_simpet_hdr = s_coreg_simpet_img[0:-3]+"hdr" + s_orig_pet_hdr = s_orig_pet_img[0:-3]+"hdr" + operate_images_analyze(s_orig_pet_hdr, s_coreg_simpet_hdr, division_hdr, 'div') + # rcommand = '%s %s %s %s fl divid' % (operate_image_hdr, s_orig_pet_hdr, s_coreg_simpet_hdr, division_hdr) + # osrun(rcommand, log_file) + + # Now we do some stuff on the division image to avoid problems + pet_mask_hdr = join(dirname(orig_pet), "pet_mask.hdr") + deleteValuesOutFov(pet_mask_hdr, axialFOV/2, act.shape[2]/2) + + # rcommand = '%s %s %s %s fl multi' % (operate_image_hdr, division_hdr, pet_mask_hdr, division_hdr) + # osrun(rcommand, log_file) + # rcommand = '%s %s %s >> %s' % (remove_nan_hdr, division_hdr, division_hdr, log_file) + # osrun(rcommand, log_file) + # rcommand = '%s %s %s >> %s' % (remove_neg_hdr, division_hdr, division_hdr, log_file) + # osrun(rcommand, log_file) + fix_4d_image(pet_mask_hdr) + operate_images_analyze(division_hdr, pet_mask_hdr, division_hdr, "mult") + remove_neg_nan(division_hdr) + change_interval_values(division_hdr, division_hdr, 5, 100000000000000000000000000000000000,1) + # rcommand = '%s %s %s 5 100000000000000000000000000000000000 1' % (cambia_val_interval, division_hdr, division_hdr) + # osrun(rcommand, log_file) + + #operate_images_analyze(division_hdr, pet_mask_hdr, division_hdr, 'mult') + operate_images_analyze(division_hdr, pet_mask_hdr, division_hdr, "mult") + # rcommand = '%s %s %s %s fl multi' % (operate_image_hdr, division_hdr, pet_mask_hdr, division_hdr) + # osrun(rcommand, log_file) + + #and finally, we calculate the new activity image + fix_4d_image(act_map) + operate_images_analyze(division_hdr, act_map, output_act_map, "mult") + # rcommand = '%s %s %s %s fl multi' % (operate_image_hdr, act_map, division_hdr, output_act_map) + # osrun(rcommand, log_file) + scalImage(output_act_map, 127, log_file) + change_format(output_act_map,"1B", log_file) + # rcommand = '%s %s %s 1B >> %s' % (cambia_formato, output_act_map, output_act_map, log_file) + # osrun(rcommand, log_file) + +def fsl_flirt(reference_hdr, input_hdr, log_file): + components = os.path.split(input_hdr) + coreg_hdr = os.path.join(components[0], 'r' + components[1]) + flt = fsl.FLIRT(bins=640, cost_func='mutualinfo') + flt.inputs.in_file = input_hdr + flt.inputs.reference = reference_hdr + flt.out_file = coreg_hdr + flt.out_log=log_file + flt.save_log=True + flt.inputs.output_type = "NIFTI_GZ" + flt.cmdline + 'flirt -in %s -ref %s -out %s -dof 6 -cost mutualinfo -searchrx -30 30 -searchry -30 30 -searchrz -30 30' %(input_hdr, reference_hdr, coreg_hdr) + aux = os.getcwd() + os.chdir(components[0]) + flt.run() + os.chdir(aux) + + anything_to_hdr_convert(input_hdr[0:-4]+"_flirt.nii.gz") + copy_analyze(input_hdr[0:-4]+"_flirt.hdr", coreg_hdr) + os.system("rm %s*" % input_hdr[0:-4]+"_flirt.*") + + return coreg_hdr + + +def proportional_scaling(img,ref_img,mask_img, log_file): + + img_max, img_mean = compute_vmax_vmean(img, mask_img) + ref_max, ref_mean = compute_vmax_vmean(ref_img, mask_img) + + if float(img_mean) != 0: + fnorm = ref_mean / img_mean + return fnorm + else: + message = 'Error scaling image. Image maean value is zero: ' + str(img) + log_message(log_file, message, 'error') + #print message + +def compute_vmax_vmean(img, mask_img): + """ + Compute maximum and mean intensity values on input image counting + on voxels inside the reference image (brain mask) + :param img: (string) input image path + :param ref_img: (string) reference image path + :return: + """ + + img, data = nib_load(img) + data = np.nan_to_num(data) + + ref_img, data_ref = nib_load(mask_img) + data_ref = np.nan_to_num(data_ref) + + i_max = np.amax(data_ref) + + super_threshold_indices = data_ref > 0.2*i_max + data_ref[super_threshold_indices] = 0 + + # Compute values restricted to voxels inside mask (ref image) + indx = np.where((data>0) & (data_ref.reshape(data.shape)>0)) + + # Maximum intensity value + v_max = np.max(data[indx]) + # Mean intensity value + v_mean = np.mean(data[indx]) + + return v_max, v_mean + + +def deleteValuesOutFov(mask_hdr, max_z, central_slice): + + img, data = nib_load(mask_hdr) + data = np.nan_to_num(data) + + z = img.shape[2] + z_size = img.header['pixdim'][3] #mm + max_z = int(round((float(max_z)*10)/float(z_size))) + + i_min = int(central_slice)-max_z + i_max = int(central_slice)+max_z + + for i in range(0, i_min): + data[:,:,i]=0 + + for i in range(i_max,z): + data[:,:,i]=0 + + img_to_write = nib.Nifti1Image(data, img.affine, img.header) + nib.save(img_to_write,mask_hdr) + +def compute_corr_coeff(img1, img2, log_file): + """ + Compute the correlation coefficiente between img1 and img2 + :param img1: (string) input header image1 path + :param img2: (string) input header image2 path + :return: + """ + + img1_img, img1_data = nib_load(img1, log_file) + img2_img, img2_data = nib_load(img2, log_file) + corrCoefmtx = np.corrcoef(img1_data.flatten(),img2_data.flatten()) + + return corrCoefmtx[0,1] + +def fix_4d_image(image_hdr): + img, data = nib_load(image_hdr) + shape = data.shape + + if len(shape) != 3: + data_new = data[:,:,:,0] + + imageToWrite = nib.AnalyzeImage(data_new,img.affine,img.header) + nib.save(imageToWrite, "aux.hdr") + copy_analyze("aux.hdr",image_hdr) + os.remove("aux.hdr") + os.remove("aux.img") + +def change_interval_values(input_hdr, output_hdr, min_value, max_value, rep_value): + img, data = nib_load(input_hdr) + + + indx = np.where(datamin_value) + + data[indx] = rep_value + + imageToWrite = nib.AnalyzeImage(data,img.affine,img.header) + nib.save(imageToWrite, "aux.hdr") + copy_analyze("aux.hdr",output_hdr) + os.remove("aux.hdr") + os.remove("aux.img") + +def change_format(image_hdr, newFormat, logfile): + message="" + img, data = nib_load(image_hdr) + + if newFormat == "fl": + form=np.float32 + elif newFormat == "1B": + form=np.uint8 + else: + message = "Error! Invalid format (or not implemented yet): " +str(newFormat) + print(message) + log_message(logfile, message, 'error') + + if message=="": + data = data.astype(form) + hdr = nib.AnalyzeHeader() + hdr.set_data_dtype(form) + hdr.set_data_shape(data.shape) + imageToWrite = nib.AnalyzeImage(data, img.affine, hdr) + nib.save(imageToWrite, image_hdr) + +def fix_4d_data(data): + shape = data.shape + + if len(shape) == 3: + return data + else: + return data[:, :, :, 0] + + +def mu_coef_511keV(tissue_n): + rows_per_tissue = 1000 + + start = tissue_n * (rows_per_tissue + 1) + end = start + rows_per_tissue + + PHG_TABLE_PATH = "include/SimSET/2.9.2/phg.data/phg_att_table" + data = np.loadtxt(PHG_TABLE_PATH, skiprows=2+start, max_rows=rows_per_tissue) + + mu = data[510,1] + + return mu From 3a54ae51ef23eba08171ddc31790237110b84f7f Mon Sep 17 00:00:00 2001 From: arnaufme8 <113554412+arnaufme8@users.noreply.github.com> Date: Tue, 17 Mar 2026 23:11:33 +0100 Subject: [PATCH 04/23] Adding files for Total-Body: simset src --- src/simset/simset_sim.py | 456 +++++++++-- src/simset/simset_tools.py | 1464 ++++++++++++++++++++---------------- 2 files changed, 1204 insertions(+), 716 deletions(-) diff --git a/src/simset/simset_sim.py b/src/simset/simset_sim.py index f06ddfcc..39ddac19 100644 --- a/src/simset/simset_sim.py +++ b/src/simset/simset_sim.py @@ -115,7 +115,9 @@ def run(self): div_0_dir = join(self.output_dir, "division_0") for j in ['trues', 'scatter', 'randoms']: - hdr_ = join(div_0_dir, "%s.hdr" % j) + #hdr_ = join(div_0_dir, "%s.hdr" % j) + hdr_ = join(div_0_dir, "%s.nii" % j) + #hdr_ = join(div_0_dir, "%s.nii.gz" % j) if exists(hdr_): counts_ = tools.ncounts(hdr_) print("Number of %s in simulation: %s" % (j, counts_)) @@ -343,21 +345,33 @@ def simulation_postprocessing(self): division_zero = join(self.output_dir, "division_0") for image in ["trues", "scatter", "randoms"]: - zero_image = join(division_zero, image + ".hdr") + #zero_image = join(division_zero, image + ".hdr") + zero_image = join(division_zero, image + ".nii") + #zero_image = join(division_zero, image + ".nii.gz") if exists(zero_image): print("Adding sinograms for %s" % image) - + + #This is to create the postlog file in case there is only one simulation, to avoid double attenuation computing! + message = "Adding sinograms for %s" % image + tools.log_message(log_file, message) + for division in range(1, self.divisions): division_dir = join(self.output_dir, "division_" + str(division)) - division_image = join(division_dir, image + ".hdr") + #division_image = join(division_dir, image + ".hdr") + division_image = join(division_dir, image + ".nii") + #division_image = join(division_dir, image + ".nii.gz") message = "Adding %s from simulation %s" % (image, division) tools.log_message(log_file, message) - tools.operate_images_analyze( + + #tools.operate_images_nii( #RESTABLISH IF FAILS [NOT COMPUTING NANS!] + tools.operate_sinograms_nii( zero_image, division_image, zero_image, "sum" ) - os.remove(division_image) - os.remove(division_image[0:-3] + "img") + #os.remove(division_image) + #os.remove(division_image[0:-3] + "img") + os.remove(division_image[0:-3] + "nii") + #os.remove(division_image[0:-6] + "nii.gz") for hist in ["phg_hf.hist", "det_hf.hist"]: zero_hist = join(division_zero, hist) @@ -365,19 +379,23 @@ def simulation_postprocessing(self): if exists(zero_hist): print(" ") print("Adding History Files for %s" % hist) - + + #PUT IN FUNCTION OF DETLISTMODE (MISSING NOW?) output = join(division_zero, "tmp_" + hist) for division in range(1, self.divisions): division_dir = join(self.output_dir, "division_" + str(division)) division_hist = join(division_dir, hist) file_list = zero_hist + " " + division_hist - simset_tools.combine_history_files( - self.simset_dir, file_list, output, log_file - ) - shutil.move(output, zero_hist) + #simset_tools.combine_history_files( + # self.simset_dir, file_list, output, log_file + #) #IMPORTANT! RESTABLISH IF FAILS! + # shutil.move(output, zero_hist) # Once everything is combined in division_0, remove the other division shutil.rmtree(division_dir) + + os.remove(join(division_zero, "det_hf.hist")) #IMPORTANT! REMOVE IF FAILS. + if self.add_randoms == 1: # To have randoms in the final history file, we need to add randoms to the final det_hf.hist @@ -404,17 +422,34 @@ def simulation_postprocessing(self): simset_tools.combine_history_files( self.simset_dir, file_list, output, log_file ) - - print("Calculating attenuation map...") - print(" ") - - output_atten = "attenuationsino" - hdr_to_copy = join("trues.hdr") - - simset_tools.simset_calcattenuation( - self.simset_dir, division_zero, output_atten, hdr_to_copy, nrays=1 - ) - + + self.stir_norm_from_att_map = self.scanner.get("stir_norm_from_att_map") + + if self.stir_norm_from_att_map != 1: + + print("Calculating attenuation map...") + print(" ") + + output_atten = "attenuationsino" + #RESTABLISH IF FAILS: + #hdr_to_copy = join("trues.hdr") + hdr_to_copy = join("trues.nii") + #hdr_to_copy = join("trues.nii.gz") + + simset_tools.simset_calcattenuation( + self.simset_dir, division_zero, output_atten, hdr_to_copy, nrays=1, timeout=None + ) + + #To remove unnecessary files once endeded the sim. Add condition: If exists. + #Also: Change directory name to "simuulation". IMPORTANT! REMOVE IF FAILS: + #os.remove(join(division_zero, "rec.weight")) + #os.remove(join(division_zero, "rec.act_indexes")) + #os.remove(join(division_zero, "rec.activity_image")) + #os.remove(join(division_zero, "rec.att_indexes")) + #os.remove(join(division_zero, "rec.attenuation_image")) + #os.remove(join(division_zero, "sampling_rec")) + #os.remove(join(division_zero, "attenuationsino")) + class SimSET_Reconstruction(object): """This class provides functions to reconstruct a SimSET simulation.""" @@ -426,7 +461,7 @@ def __init__( self.simpet_dir = dirname(abspath(__file__)) self.dir_stir = config.get("dir_stir") - self.input_dir = join(projections_dir, "division_0") + self.input_dir = join(projections_dir, "division_0") #CHANGE TO "SIMULATION"!!! self.output_dir = reconstructions_dir self.params = params @@ -451,7 +486,9 @@ def __init__( def run(self): if not exists(self.output_dir): os.makedirs(self.output_dir) - + + #if (not exists(join(self.output_dir, "stir_sinogram.nii.gz")) or (not exists(join(self.output_dir, "stir_sinogram.nii.gz")) + self.prepare_recons() self.run_recons() @@ -459,26 +496,78 @@ def prepare_recons(self): from src.stir import stir_tools print("Preparing files for reconstruction") - - trues_sino = join(self.input_dir, "trues.hdr") - scatter_sino = join(self.input_dir, "scatter.hdr") - randoms_sino = join(self.input_dir, "randoms.hdr") - - corr_scatter_sino = join(self.input_dir, "corr_scatter.hdr") - corr_randoms_sino = join(self.input_dir, "corr_randoms.hdr") - my_simset_sino = join(self.input_dir, "my_sinogram.hdr") - additive_sinogram = join(self.input_dir, "additive_sinogram.hdr") - - tools.operate_single_image( - scatter_sino, - "mult", - self.scatt_corr_factor, - corr_scatter_sino, - self.log_file, - ) - tools.operate_images_analyze( - trues_sino, corr_scatter_sino, my_simset_sino, operation="sum" - ) + + self.stir_norm_from_att_map = self.scanner.get("stir_norm_from_att_map") + + + #RESTABLISH IF FAILS: + #trues_sino = join(self.input_dir, "trues.hdr") + #scatter_sino = join(self.input_dir, "scatter.hdr") + #randoms_sino = join(self.input_dir, "randoms.hdr") + + #corr_scatter_sino = join(self.input_dir, "corr_scatter.hdr") + #corr_randoms_sino = join(self.input_dir, "corr_randoms.hdr") + #my_simset_sino = join(self.input_dir, "my_sinogram.hdr") + #additive_sinogram = join(self.input_dir, "additive_sinogram.hdr") + #att_sino = join(self.input_dir, "attenuationsino.hdr") + + + #trues_sino = join(self.input_dir, "trues.nii") + #scatter_sino = join(self.input_dir, "scatter.nii") + #randoms_sino = join(self.input_dir, "randoms.nii") + + #corr_scatter_sino = join(self.input_dir, "corr_scatter.nii") + #corr_randoms_sino = join(self.input_dir, "corr_randoms.nii") + #my_simset_sino = join(self.input_dir, "my_sinogram.nii") + #additive_sinogram = join(self.input_dir, "additive_sinogram.nii") + #att_sino = join(self.input_dir, "attenuationsino.nii") + + """ + trues_sino = join(self.input_dir, "trues.nii.gz") + scatter_sino = join(self.input_dir, "scatter.nii.gz") + randoms_sino = join(self.input_dir, "randoms.nii.gz") + + corr_scatter_sino = join(self.input_dir, "corr_scatter.nii.gz") + corr_randoms_sino = join(self.input_dir, "corr_randoms.nii.gz") + my_simset_sino = join(self.input_dir, "my_sinogram.nii.gz") + additive_sinogram = join(self.input_dir, "additive_sinogram.nii.gz") + att_sino = join(self.input_dir, "attenuationsino.nii.gz") + """ + + #Delete: + trues_sino = join(self.input_dir, "trues.nii") + scatter_sino = join(self.input_dir, "scatter.nii") + randoms_sino = join(self.input_dir, "randoms.nii") + + corr_scatter_sino = join(self.input_dir, "corr_scatter.nii") + corr_randoms_sino = join(self.input_dir, "corr_randoms.nii") + my_simset_sino = join(self.input_dir, "my_sinogram.nii") + additive_sinogram = join(self.input_dir, "additive_sinogram.nii") + att_sino = join(self.input_dir, "attenuationsino.nii") + + #RESTABLISH IF FAILS: + #tools.operate_single_image( + + print("MULTIPLYING CORR_SCATTER_SINO") + + if not exists(corr_scatter_sino): + tools.operate_single_image_nii( + scatter_sino, + "mult", + self.scatt_corr_factor, + corr_scatter_sino, + self.log_file, + check_nans = False, + ) + + print("SUMMING TRUES AND CORR SCATTER") + + #RESTABLISH IF FAILS: + #tools.operate_images_analyze( + if not exists(my_simset_sino): + tools.operate_images_nii( + trues_sino, corr_scatter_sino, my_simset_sino, operation="sum", check_nans = False, + ) if self.add_randoms == 1: tools.operate_single_image( @@ -497,33 +586,261 @@ def prepare_recons(self): scatter_sino, randoms_sino, additive_sinogram, operation="sum" ) else: - tools.copy_analyze(scatter_sino, additive_sinogram) + #RESTABLISH IF FAILS + #tools.copy_analyze(scatter_sino, additive_sinogram) + tools.copy_nifti(scatter_sino, additive_sinogram) else: - tools.copy_analyze(scatter_sino, additive_sinogram) - - tools.smooth_analyze(additive_sinogram, 10, additive_sinogram) - + #RESTABLISH IF FAILS + #tools.copy_analyze(scatter_sino, additive_sinogram) + + print("COPYING ADDITIVE SINO") + if not exists(additive_sinogram): + tools.copy_nifti(scatter_sino, additive_sinogram) + print("SMOOTHING ADDITIVE SINO") + tools.smooth_nifti(additive_sinogram, 10, additive_sinogram) + + #RESTABLISH IF FAILS + #tools.smooth_analyze(additive_sinogram, 10, additive_sinogram) + + #if not exists(additive_sinogram): + # tools.smooth_nifti(additive_sinogram, 10, additive_sinogram) + + + #RESTABLISH IF FAILS: + """ sinogram_stir = join(self.output_dir, "stir_sinogram.hdr") tools.convert_simset_sino_to_stir(my_simset_sino, sinogram_stir) - shutil.copy(sinogram_stir[0:-3] + "img", sinogram_stir[0:-3] + "s") + shutil.copy(sinogram_stir[0:-3] + "bin", sinogram_stir[0:-3] + "s") #PREVIOUS: img stir_tools.create_stir_hs_from_detparams( self.scanner, sinogram_stir[0:-3] + "hs" ) additive_sino_stir = join(self.output_dir, "stir_additivesino.hdr") tools.convert_simset_sino_to_stir(additive_sinogram, additive_sino_stir) - shutil.copy(additive_sino_stir[0:-3] + "img", additive_sino_stir[0:-3] + "s") + shutil.copy(additive_sino_stir[0:-3] + "img", additive_sino_stir[0:-3] + "s") #PREVIOUS: img stir_tools.create_stir_hs_from_detparams( self.scanner, additive_sino_stir[0:-3] + "hs" ) - att_sino = join(self.input_dir, "attenuationsino.hdr") + #att_sino = join(self.input_dir, "attenuationsino.hdr") + #att_sino = join(self.input_dir, "attenuationsino.nii") att_stir = join(self.output_dir, "stir_att.hdr") tools.convert_simset_sino_to_stir(att_sino, att_stir) - shutil.copy(att_stir[0:-3] + "img", att_stir[0:-3] + "s") + shutil.copy(att_stir[0:-3] + "bin", att_stir[0:-3] + "s") #PREVIOUS: img stir_tools.create_stir_hs_from_detparams(self.scanner, att_stir[0:-3] + "hs") + """ + + ##### THIS IS FOR .nii: + #sinogram_stir = join(self.output_dir, "stir_sinogram.nii") + #tools.convert_simset_sino_to_stir_nii(my_simset_sino, sinogram_stir) + #shutil.copy(sinogram_stir[0:-3] + "img", sinogram_stir[0:-3] + "s") + #stir_tools.create_stir_hs_from_detparams( + # self.scanner, sinogram_stir[0:-3] + "hs" + #) + #os.remove(sinogram_stir[0:-3] + "img") + #os.remove(sinogram_stir[0:-3] + "hdr") + + #additive_sino_stir = join(self.output_dir, "stir_additivesino.nii") + #tools.convert_simset_sino_to_stir_nii(additive_sinogram, additive_sino_stir) + #shutil.copy(additive_sino_stir[0:-3] + "img", additive_sino_stir[0:-3] + "s") + #stir_tools.create_stir_hs_from_detparams( + # self.scanner, additive_sino_stir[0:-3] + "hs" + #) + #os.remove(additive_sino_stir[0:-3] + "img") + #os.remove(additive_sino_stir[0:-3] + "hdr") + + #att_sino = join(self.input_dir, "attenuationsino.nii") + #att_stir = join(self.output_dir, "stir_att.nii") + #tools.convert_simset_sino_to_stir_nii(att_sino, att_stir) + #shutil.copy(att_stir[0:-3] + "img", att_stir[0:-3] + "s") + #stir_tools.create_stir_hs_from_detparams( + # self.scanner, att_stir[0:-3] + "hs" + #) + #os.remove(att_stir[0:-3] + "img") + #os.remove(att_stir[0:-3] + "hdr") + + + ##### THIS IS FOR .nii.gz: + #sinogram_stir = join(self.output_dir, "stir_sinogram.nii.gz") + #tools.convert_simset_sino_to_stir_nii(my_simset_sino, sinogram_stir) + #shutil.copy(sinogram_stir[0:-6] + "img", sinogram_stir[0:-6] + "s") + #stir_tools.create_stir_hs_from_detparams( + # self.scanner, sinogram_stir[0:-6] + "hs" + #) + #os.remove(sinogram_stir[0:-6] + "img") + #os.remove(sinogram_stir[0:-6] + "hdr") + + #additive_sino_stir = join(self.output_dir, "stir_additivesino.nii.gz") + #tools.convert_simset_sino_to_stir_nii(additive_sinogram, additive_sino_stir) + #shutil.copy(additive_sino_stir[0:-6] + "img", additive_sino_stir[0:-6] + "s") + #stir_tools.create_stir_hs_from_detparams( + # self.scanner, additive_sino_stir[0:-6] + "hs" + #) + #os.remove(additive_sino_stir[0:-6] + "img") + #os.remove(additive_sino_stir[0:-6] + "hdr") + + #att_stir = join(self.output_dir, "stir_att.nii.gz") + #tools.convert_simset_sino_to_stir_nii(att_sino, att_stir) + #shutil.copy(att_stir[0:-6] + "img", att_stir[0:-6] + "s") + #stir_tools.create_stir_hs_from_detparams( + # self.scanner, att_stir[0:-6] + "hs" + #) + #os.remove(att_stir[0:-6] + "img") + #os.remove(att_stir[0:-6] + "hdr") + + + ##### THIS IS FOR .nii.gz [NEW VERSION (FILE REOIRGANIZATION): + + num_rings = self.scanner.get("num_rings") + max_segment = self.scanner.get("max_segment") + + print("GENERATING STIR SINOGRAM") + + #sinogram_stir_nii = join(self.input_dir, "stir_sinogram.nii.gz") + sinogram_stir_nii = join(self.input_dir, "stir_sinogram.nii") + sinogram_stir_s = join(self.output_dir, "stir_sinogram.s") + sinogram_stir_hs = join(self.output_dir, "stir_sinogram.hs") + + if not exists(sinogram_stir_nii): + tools.convert_simset_sino_to_stir_nii(my_simset_sino, sinogram_stir_nii) + + tools.copy_sinogram_stir_to_output(sinogram_stir_nii, sinogram_stir_s) + #tools.copy_reduced_sinogram_stir_to_output(sinogram_stir_nii, sinogram_stir_s, num_rings, max_segment) + + stir_tools.create_stir_hs_from_detparams( + #stir_tools.create_reduced_stir_hs_from_detparams( + self.scanner, sinogram_stir_hs + ) + + print("GENERATING STIR ADDITIVE") + + #additivesino_stir_nii = join(self.input_dir, "stir_additivesino.nii.gz") + additivesino_stir_nii = join(self.input_dir, "stir_additivesino.nii") + additivesino_stir_s = join(self.output_dir, "stir_additivesino.s") + additivesino_stir_hs = join(self.output_dir, "stir_additivesino.hs") + + if not exists(additivesino_stir_nii): + tools.convert_simset_sino_to_stir_nii(additive_sinogram, additivesino_stir_nii) + + tools.copy_sinogram_stir_to_output(additivesino_stir_nii, additivesino_stir_s) + #tools.copy_reduced_sinogram_stir_to_output(additivesino_stir_nii, additivesino_stir_s, num_rings, max_segment) + + stir_tools.create_stir_hs_from_detparams( + #stir_tools.create_reduced_stir_hs_from_detparams( + self.scanner, additivesino_stir_hs + ) + + print("GENERATING STIR ATT") + + if self.stir_norm_from_att_map != 1: + + #att_stir_nii = join(self.input_dir, "stir_att.nii.gz") + att_stir_nii = join(self.input_dir, "stir_att.nii") + att_stir_s = join(self.output_dir, "stir_att.s") + att_stir_hs = join(self.output_dir, "stir_att.hs") + + if not exists(att_stir_nii): + tools.convert_simset_sino_to_stir_nii(att_sino, att_stir_nii) + + tools.copy_sinogram_stir_to_output(att_stir_nii, att_stir_s) + #tools.copy_reduced_sinogram_stir_to_output(att_stir_nii, att_stir_s, num_rings, max_segment) + + stir_tools.create_stir_hs_from_detparams( + #stir_tools.create_reduced_stir_hs_from_detparams( + self.scanner, att_stir_hs + ) + + """ #RESTABLISH IF FAILS: + if self.stir_norm_from_att_map == 1: + + attmap = nib.load(join('Data', self.params.get("patient_dirname"), self.params.get("act_map"))) + + attmap_dim = np.shape(attmap) + attmap_pixsize = attmap.header['pixdim'][1:4] + + + filedims = [attmap_dim[2], attmap_dim[0], attmap_dim[1]] #[618, 440, 440] + dtype = np.uint32 #np.float32 # or np.uint8, np.float32, etc. + + with open(join(self.input_dir, "rec.att_indexes"), "rb") as f: + data = np.frombuffer(f.read(), dtype=dtype) + + att_indexes = data.reshape(filedims) + + mu_map = np.zeros(np.shape(att_indexes)) + + for attidx in np.unique(att_indexes): + mu_map[att_indexes == attidx] = tools.mu_coef_511keV(attidx) + + + mu_map_img = nib.Nifti2Image(mu_map, np.eye(4)) + nib.save(mu_map_img, join(self.input_dir, 'mu_map.hdr')) + + shutil.copy(join(self.input_dir, 'mu_map.img'), join(self.output_dir, 'mu_map.v')) + tools.write_interfile_header(join(self.output_dir, 'mu_map.hv'), attmap_dim[2], attmap_pixsize[2], + attmap_dim[0], attmap_pixsize[0], + attmap_dim[1], attmap_pixsize[1]) + + """ + + + if self.stir_norm_from_att_map == 1: + + attmap = nib.load(join('Data', self.params.get("patient_dirname"), self.params.get("att_map"))) + + attmap_data = (attmap.get_fdata()).astype(np.uint8) + + attmap_dim = np.shape(attmap) + attmap_pixsize = attmap.header['pixdim'][1:4] + + mu_map = np.zeros(attmap_dim) + + for attidx in np.unique(attmap_data): + mu_map[attmap_data == attidx] = tools.mu_coef_511keV(attidx) + + mu_map_img = nib.Nifti1Image(mu_map, np.eye(4)) + nib.save(mu_map_img, join(self.input_dir, 'mu_map.hdr')) + + shutil.copy(join(self.input_dir, 'mu_map.img'), join(self.output_dir, 'mu_map.v')) + tools.write_interfile_header(join(self.output_dir, 'mu_map.hv'), attmap_dim[0], attmap_pixsize[0], + attmap_dim[1], attmap_pixsize[1], + attmap_dim[2], attmap_pixsize[2]) + + + + + ######THIS WAS FOR .nii.gz [NOT WORKING AT THE MOMENT]: + """ + sinogram_stir = join(self.output_dir, "stir_sinogram.nii.gz") + tools.convert_simset_sino_to_stir_nii(my_simset_sino, sinogram_stir) + nib.save(nib.load(sinogram_stir), join(self.output_dir, "stir_sinogram.nii")) + shutil.copy(sinogram_stir[0:-6] + "nii", sinogram_stir[0:-6] + "s") + stir_tools.create_stir_hs_from_detparams( + self.scanner, sinogram_stir[0:-6] + "hs" + ) + os.remove(join(self.output_dir, "stir_sinogram.nii")) + additive_sino_stir = join(self.output_dir, "stir_additivesino.nii.gz") + tools.convert_simset_sino_to_stir_nii(additive_sinogram, additive_sino_stir) + nib.save(nib.load(additive_sino_stir), join(self.output_dir, "stir_additivesino.nii")) + shutil.copy(additive_sino_stir[0:-6] + "nii", additive_sino_stir[0:-6] + "s") + stir_tools.create_stir_hs_from_detparams( + self.scanner, additive_sino_stir[0:-6] + "hs" + ) + os.remove(join(self.output_dir, "stir_additivesino.nii")) + + att_sino = join(self.input_dir, "attenuationsino.nii.gz") + att_stir = join(self.output_dir, "stir_att.nii.gz") + tools.convert_simset_sino_to_stir_nii(att_sino, att_stir) + nib.save(nib.load(att_stir), join(self.output_dir, "stir_att.nii")) + shutil.copy(att_stir[0:-6] + "nii", att_stir[0:-6] + "s") + stir_tools.create_stir_hs_from_detparams(self.scanner, att_stir[0:-6] + "hs") + os.remove(join(self.output_dir, "stir_att.nii")) + """ + + + #EDIT IN FUTURE: if self.scanner.get("analytical_att_correction") == 1: catt_sino = join(self.output_dir, "catt_sinogram.hdr") tools.operate_images_analyze( @@ -547,20 +864,37 @@ def prepare_recons(self): def run_recons(self): from src.stir import stir_tools - + + start_recons = False + print("Starting STIR reconstruction") recons_algorithm = self.scanner.get("recons_type") sinogram_stir = join(self.output_dir, "stir_sinogram.hs") additive_sino_stir = join(self.output_dir, "stir_additivesino.hs") att_stir = join(self.output_dir, "stir_att.hs") - - if any( - exists(i) == False for i in [sinogram_stir, additive_sino_stir, att_stir] - ): - print("Something is not ready for the reconstruction") + + if self.scanner.get("stir_norm_from_att_map") != 1: + + if any( + exists(i) == False for i in [sinogram_stir, additive_sino_stir, att_stir] + ): + print("Something is not ready for the reconstruction") + else: + start_recons = True + print("Starting STIR reconstruction") + else: - print("Starting STIR reconstruction") + + if any( + exists(i) == False for i in [sinogram_stir, additive_sino_stir] + ): + print("Something is not ready for the reconstruction") + else: + start_recons = True + print("Starting STIR reconstruction") + + if start_recons: if recons_algorithm == "FBP2D": reconsFile_hdr = stir_tools.FBP2D_recons( diff --git a/src/simset/simset_tools.py b/src/simset/simset_tools.py index a92b6b7f..6b139f47 100644 --- a/src/simset/simset_tools.py +++ b/src/simset/simset_tools.py @@ -1,655 +1,809 @@ -import random -import os -import shutil -import subprocess as sp -from os.path import join, exists -import pexpect -from utils import tools - - -def make_simset_act_table(act_table_factor, my_act_table, log_file=False): - with open(my_act_table, "w") as f: - f.write("256\n") - for i in range(256): - act_value = i * 0.000001 * act_table_factor - f.write(str("{:.12f}".format(act_value)) + "\n") - f.close() - - if log_file: - message = "Wrote act_table with factor %s" % act_table_factor - tools.log_message(log_file, message, "info") - - -def make_simset_phg( - config, - output_file, - simulation_dir, - act, - scanner_radius, - scanner_axial_fov, - center_slice, - photons, - sim_time, - add_randoms=False, - phg_hf=False, - S=0, - log_file=False, -): - stir_identifier = "# Hello, I am a SimSET PHG file!\n" - - # define types to be used - boolean = "BOOL " - real = "REAL " - longl = "LONGLONG " - integer = "INT " - enum = "ENUM " - string = "STR " - - # Configuring the importance sampling parameters - if add_randoms == 1: - stratification = "false" - forced_detection = "false" - non_absortion = "false" - # We need to force coincidences + singles for randoms - sim_PET_coinc_only = "false" - sim_PET_coinc_plus_singles = "true" - else: - stratification = config.get("stratification") - forced_detection = config.get("forced_detection") - non_absortion = config.get("forced_non_absortion") - sim_PET_coinc_only = "false" - sim_PET_coinc_plus_singles = "true" - - # Configuration of photons an simulation time - - num_to_simulate = str(photons) - length_of_scan = str(sim_time) - - # Import other configurations form config.yml - acceptance_angle = str(config.get("acceptance_angle")) - positron_range = config.get("positron_range") - non_col = config.get("non_colinearity") - minimum_energy = str(config.get("minimum_energy")) - weight_window_ratio = str(config.get("weight_window_ratio")) - point_source_voxels = config.get("point_source_voxels") - coherent_scatter_object = config.get("coherent_scatter_object") - coherent_scatter_detector = config.get("coherent_scatter_detector") - simulated_isotope = config.get("isotope") - - # Generate a random seed for the MC - random_seed = str(random.randint(1, 1e12)) - - # Object stuff - nslices = act.shape[2] - xbins = act.shape[0] - ybins = act.shape[1] - - act_fov = abs(act.shape * act.affine) - xMin, xMax = round(-act_fov[0, 0] / 20, 3), round( - act_fov[0, 0] / 20, 3 - ) # 20 is because it must be cm - yMin, yMax = round(-act_fov[1, 1] / 20, 3), round(act_fov[1, 1] / 20, 3) - - z_offset = abs( - act.affine[2, 2] * center_slice / 10 + 0.5 * act.affine[2, 2] / 10 - ) # cm - zMin, zMax = round(-z_offset, 3), round(act_fov[2, 2] / 10 - z_offset, 3) - - dz = round((zMax - zMin) / nslices, 2) - - max_z_target = scanner_axial_fov / 2 - min_z_target = -scanner_axial_fov / 2 - - # Directory variables - simset_dir = config.get("dir_simset") - simset_phgdata_dir = join(simset_dir, "phg.data") - - # Now we write the phg file - with open(output_file, "w") as f: - # First we write the configuration stuff for the simulation - f.write(stir_identifier) - f.write("\n\n# Runtime options\n") - f.write(boolean + "simulate_stratification = %s \n" % stratification) - f.write(boolean + "simulate_forced_detection = " + forced_detection + "\n") - f.write(boolean + "forced_non_absorbtion = " + non_absortion + "\n") - f.write(real + "acceptance_angle = " + acceptance_angle + "\n") - f.write(longl + "num_to_simulate = " + num_to_simulate + "\n") - f.write(real + "length_of_scan = " + length_of_scan + "\n") - f.write(boolean + "simulate_SPECT = false\n") - f.write( - boolean + "simulate_PET_coincidences_only = " + sim_PET_coinc_only + "\n" - ) - f.write( - boolean - + "simulate_PET_coincidences_plus_singles = " - + sim_PET_coinc_plus_singles - + "\n" - ) - f.write(boolean + "adjust_for_positron_range = " + positron_range + "\n") - f.write(boolean + "adjust_for_collinearity = " + non_col + "\n") - f.write(real + "minimum_energy = " + minimum_energy + "\n") - f.write(real + "photon_energy = 511.0\n") - f.write(real + "weight_window_ratio = " + weight_window_ratio + "\n") - f.write(boolean + "point_source_voxels = " + point_source_voxels + "\n") - f.write(integer + "random_seed = " + random_seed + "\n") - f.write( - boolean - + "model_coherent_scatter_in_obj = " - + coherent_scatter_object - + "\n" - ) - f.write( - boolean - + "model_coherent_scatter_in_tomo = " - + coherent_scatter_detector - + "\n" - ) - f.write(enum + "isotope = " + simulated_isotope + "\n") - - # Now comes the object stuff (old make_phg_simset from fruitcake) - f.write("\n\n# OBJECT GEOMETRY VALUES\n") - f.write("\nNUM_ELEMENTS_IN_LIST object = %s" % str(nslices + 1)) - f.write("\n INT num_slices = %s" % str(nslices)) - - for i in range(nslices): - zMin_value = round(zMin + i * dz, 2) - zMax_value = round(zMin + (i + 1) * dz, 2) - f.write( - "\n NUM_ELEMENTS_IN_LIST slice = 9 " - + "\n INT slice_number = %s" % str(i) - + "\n REAL zMin = %s" % str(zMin_value) - + "\n REAL zMax = %s" % str(zMax_value) - + "\n REAL xMin = %s" % str(xMin) - + "\n REAL xMax = %s" % str(xMax) - + "\n REAL yMin = %s" % str(yMin) - + "\n REAL yMax = %s" % str(yMax) - + "\n INT num_X_bins = %s" % str(xbins) - + "\n INT num_Y_bins = %s" % str(ybins) - ) - - # Now we fill the values for the target cylinder - - f.write("\n\n# TARGET CYLINDER INFORMATION") - f.write("\nNUM_ELEMENTS_IN_LIST target_cylinder = 3") - f.write("\n REAL target_zMin = %s" % str(min_z_target)) - f.write("\n REAL target_zMax = %s" % str(max_z_target)) - f.write("\n REAL radius = %s\n\n" % str(scanner_radius)) - - # Now we need the directory stuff - f.write( - string - + 'coherent_scatter_table = "' - + join(simset_phgdata_dir, "coh.tables") - + '"\n' - ) - f.write( - string - + 'activity_indexes = "' - + join(simulation_dir, "rec.act_indexes") - + '"\n' - ) - f.write( - string - + 'activity_table = "' - + join(simulation_dir, "phg_act_table") - + '"\n' - ) - f.write( - string - + 'activity_index_trans = "' - + join(simset_phgdata_dir, "phg_act_index_trans") - + '"\n' - ) - f.write( - string - + 'activity_image = "' - + join(simulation_dir, "rec.activity_image") - + '"\n' - ) - f.write( - string - + 'attenuation_indexes = "' - + join(simulation_dir, "rec.att_indexes") - + '"\n' - ) - f.write( - string - + 'attenuation_table = "' - + join(simset_phgdata_dir, "phg_att_table") - + '"\n' - ) - f.write( - string - + 'attenuation_index_trans = "' - + join(simset_phgdata_dir, "phg_att_index_trans") - + '"\n' - ) - f.write( - string - + 'attenuation_image = "' - + join(simulation_dir, "rec.attenuation_image") - + '"\n' - ) - - # This part changes productivity tables between S=0 and S=1 adq - if S == 1: - f.write( - string - + 'productivity_input_table = "' - + join(simulation_dir, "sampling_rec") - + '"\n' - ) - f.write(string + 'productivity_output_table = ""\n') - else: - f.write(string + 'productivity_input_table = ""\n') - f.write( - string - + 'productivity_output_table = "' - + join(simulation_dir, "sampling_rec") - + '"\n' - ) - - f.write( - string + 'statistics_file = "' + join(simulation_dir, "rec.stat") + '"\n' - ) - f.write( - string - + 'isotope_data_file = "' - + join(simset_phgdata_dir, "isotope_positron_energy_data") - + '"\n' - ) - f.write( - string - + 'detector_params_file = "' - + join(simulation_dir, "det.rec") - + '"\n' - ) - f.write( - string + 'bin_params_file = "' + join(simulation_dir, "bin.rec") + '"\n' - ) - # Adds the phg history file if it is activated - if phg_hf == 1: - f.write( - string - + 'history_file = "' - + join(simulation_dir, "phg_hf.hist" + '"\n') - ) - - f.close() - - if log_file: - message = "Created phg_file in: %s" % output_file - tools.log_message(log_file, message, "info") - - -def make_simset_bin( - config, output_file, simulation_dir, scanner, add_randoms=False, log_file=False -): - stir_identifier = "# Hello, I am a SimSET BIN file!\n" - - # define types to be used - boolean = "BOOL " - real = "REAL " - integer = "INT " - string = "STR " - - # We will create an extra bin for randoms if the randoms simulation is activated. - if add_randoms: - accept_randoms = "true" - scatter_param = "6" - else: - accept_randoms = "false" - scatter_param = "1" - - min_s = "0" - max_s = "9" - - # Here we get the parameters from the parameter files. - num_z_bins = str(scanner.get("num_rings")) - axial_fov = scanner.get("axial_fov") - min_z, max_z = -axial_fov / 2, axial_fov / 2 - num_aa_bins = str(scanner.get("num_aa_bins")) - num_td_bins = str(scanner.get("num_td_bins")) - min_td = str(-scanner.get("scanner_radius")) - max_td = str(scanner.get("scanner_radius")) - min_e = str(scanner.get("min_energy_window")) - max_e = str(scanner.get("max_energy_window")) - rec_weight_file = join(simulation_dir, "rec.weight") - - with open(output_file, "w") as f: - f.write(stir_identifier) - f.write(boolean + " accept_randoms = " + accept_randoms + "\n") - f.write(integer + "scatter_param = " + scatter_param + "\n") - f.write(integer + "min_s = " + min_s + "\n") - f.write(integer + "max_s = " + max_s + "\n") - f.write(integer + "num_z_bins = " + num_z_bins + "\n") - f.write(real + "min_z = " + str(min_z) + "\n") - f.write(real + "max_z = " + str(max_z) + "\n") - f.write(integer + "num_aa_bins = " + num_aa_bins + "\n") - f.write(integer + "num_td_bins = " + num_td_bins + "\n") - f.write(real + "min_td = " + min_td + "\n") - f.write(real + "max_td = " + max_td + "\n") - f.write("INT num_e1_bins = 1\n") - f.write("INT num_e2_bins = 1\n") - f.write(real + "min_e = " + min_e + "\n") - f.write(real + "max_e = " + max_e + "\n") - f.write("INT weight_image_type = 2\n") - f.write("INT count_image_type = 2\n") - f.write("BOOL add_to_existing_img = false\n") - f.write(string + 'weight_image_path = "' + rec_weight_file + '"\n') - - f.close() - - if log_file: - message = "Created bin_file in: %s" % output_file - tools.log_message(log_file, message, "info") - - -def make_simset_simp_det(scanner_params, output, sim_dir, det_hf=0, log_file=False): - energy_resolution = scanner_params.get("energy_resolution") - new_file = open(output, "w") - new_file.write( - "ENUM detector_type = simple_pet \n\n" - + "REAL reference_energy_keV = 511.0 \n" - + "REAL energy_resolution_percentage = %s \n" % energy_resolution - ) - - if det_hf == 1: - new_file.write( - 'STR history_file = "' + join(sim_dir, "det_hf.hist" + '"\n') - ) - - new_file.close() - - if log_file: - message = ( - "Created det_file with:\n" + "Energy resolution: %s" % energy_resolution - ) - - tools.log_message(log_file, message, "info") - - -def make_simset_cyl_det(scanner_params, output, sim_dir, det_hf=0, log_file=False): - num_rings = scanner_params.get("num_rings") - z_crystal_size = scanner_params.get("z_crystal_size") - axial_fov = scanner_params.get("axial_fov") - max_z = axial_fov / 2 - min_z = -axial_fov / 2 - gap_z_size = (max_z - min_z - z_crystal_size * num_rings) / (num_rings - 1) - cyln_inner_radius = scanner_params.get("scanner_radius") - cyln_outer_radius = cyln_inner_radius + scanner_params.get("crystal_thickness") - energy_resolution = scanner_params.get("energy_resolution") - timing_resolution = scanner_params.get("timing_resolution") - material = scanner_params.get("simset_material") - - nrings_total = 2 * num_rings - 1 - - new_file = open(output, "w") - new_file.write( - "ENUM detector_type = cylindrical \n\n" - + "# This detector example has %s axial rings and %s gaps \n" - % (num_rings, num_rings - 1) - + "INT cyln_num_rings = %s \n\n" % nrings_total - ) - - for i in range(1, num_rings + 1): - ring_zmin = min_z + (i - 1) * z_crystal_size + (i - 1) * gap_z_size - ring_zmax = ring_zmin + z_crystal_size - gap_zmax = ring_zmin + z_crystal_size + gap_z_size - - new_file.write( - "# RING #%s \n" % i - + "# The following defines the ring parameters \n" - + "LIST cyln_ring_info_list = 5 \n" - + "INT cyln_num_layers = 1 \n" - + "LIST cyln_layer_info_list = 4 \n" - + "BOOL cyln_layer_is_active = TRUE \n" - + "INT cyln_layer_material = %s \n" % material - + "REAL cyln_layer_inner_radius = %s \n" % cyln_inner_radius - + "REAL cyln_layer_outer_radius = %s \n" % cyln_outer_radius - + "REAL cyln_min_z = %s \n" % ring_zmin - + "REAL cyln_max_z = %s \n\n" % ring_zmax - ) - - if not i == num_rings: - new_file.write( - "# GAP #%s \n" % i - + "# The following defines the gap parameters \n" - + "LIST cyln_ring_info_list = 5 \n" - + "INT cyln_num_layers = 1 \n" - + "LIST cyln_layer_info_list = 4 \n" - + "BOOL cyln_layer_is_active = FALSE \n" - + "INT cyln_layer_material = 0 \n" - + "REAL cyln_layer_inner_radius = %s \n" % cyln_inner_radius - + "REAL cyln_layer_outer_radius = %s \n" % cyln_outer_radius - + "REAL cyln_min_z = %s \n" % ring_zmax - + "REAL cyln_max_z = %s \n\n" % gap_zmax - ) - - new_file.write( - "REAL reference_energy_keV = 511.0 \n" - + "REAL energy_resolution_percentage = %s \n" % energy_resolution - + "REAL photon_time_fwhm_ns = %s \n" % timing_resolution - ) - if det_hf == 1: - new_file.write( - 'STR history_file = "' + join(sim_dir, "det_hf.hist" + '"\n') - ) - - new_file.close() - - if log_file: - message = ( - "Created det_file with:\n" - + "Cristal z: %s cm\n" % z_crystal_size - + "Gap size: %s cm\n" % gap_z_size - + "Ring thickness: %s cm" % (cyln_outer_radius - cyln_inner_radius) - + "Energy resolution: %s" % energy_resolution - + "Timing resolution: %s" % timing_resolution - ) - - tools.log_message(log_file, message, "info") - - -def make_index_file(simulation_dir, simset_dir, log_file=False): - output = join(simulation_dir, "index_file.log") - phg_file = join(simulation_dir, "phg.rec") - act_dat = join(simulation_dir, "act.dat") - att_dat = join(simulation_dir, "att.dat") - make_index_file = join(simset_dir, "bin", "makeindexfile") - - prompts = ( - phg_file - + "\n" - + "y\n" - + "y\n" - + "0\n" - + "y\n" - + act_dat - + "\n" - + "0\n" - + "0\n" - + "1\n" - + "n\n" - + "n\n" - + "y\n" - + "y\n" - + "0\n" - + "y\n" - + att_dat - + "\n" - + "0\n" - + "0\n" - + "1\n" - + "n\n" - + "n\n" - ) - - os.system('printf "%s" | %s > %s' % (prompts, make_index_file, output)) - - if log_file: - message = 'printf "%s" | %s > %s' % (prompts, make_index_file, output) - tools.log_message(log_file, message, "info") - - -def process_weights(weights_file, output_dir, scanner, add_randoms=0): - nbins = scanner.get("num_td_bins") - nangles = scanner.get("num_aa_bins") - nrings = scanner.get("num_rings") - nslices = nrings * nrings - - Simset_offset = 32768 - block_size = nbins * nangles * nslices * 4 - - trues_start = Simset_offset - trues_end = Simset_offset + block_size - trues_file = join(output_dir, "w1") - - with open(weights_file, "rb") as in_file: - with open(trues_file, "wb") as out_file: - out_file.write(in_file.read()[trues_start:trues_end]) - - output = join(output_dir, "trues.hdr") - tools.create_analyze_from_imgdata( - trues_file, output, nbins, nangles, nslices, 1, 1, 1, "fl" - ) - os.remove(trues_file) - - scatter_start = trues_end - scatter_end = trues_end + block_size - scatter_file = join(output_dir, "w2") - - with open(weights_file, "rb") as in_file: - with open(scatter_file, "wb") as out_file: - out_file.write(in_file.read()[scatter_start:scatter_end]) - - output = join(output_dir, "scatter.hdr") - tools.create_analyze_from_imgdata( - scatter_file, output, nbins, nangles, nslices, 1, 1, 1, "fl" - ) - os.remove(scatter_file) - - if add_randoms == 1: - randoms_start = scatter_end - randoms_end = scatter_end + block_size - randoms_file = join(output_dir, "w3") - - with open(weights_file, "rb") as in_file: - with open(randoms_file, "wb") as out_file: - out_file.write(in_file.read()[randoms_start:randoms_end]) - - output = join(output_dir, "randoms.hdr") - tools.create_analyze_from_imgdata( - randoms_file, output, nbins, nangles, nslices, 1, 1, 1, "fl" - ) - os.remove(randoms_file) - - -def add_randoms(sim_dir, simset_dir, coincidence_window, rebin=True, log_file=False): - string = "STR " - integer = "INT " - enum = "ENUM " - real = "REAL " - - sorted_file = join(sim_dir, "sorted_det_hf.hist") - template = join(sim_dir, "sort.params") - det_hf = join(sim_dir, "det_hf.hist") - - with open(template, "w") as f: - f.write(string + 'history_file = "' + det_hf + '"\n') - f.write(string + 'sorted_history_file = "' + sorted_file + '"\n') - f.write(integer + "buffer_size = 1024\n") - - f.close() - - sorting_bin = join(simset_dir, "bin", "timesort -d") - - command = command = "%s %s >> %s" % (sorting_bin, template, log_file) - tools.osrun(command, log_file) - - randoms_file = join(sim_dir, "randoms.hist") - template = join(sim_dir, "randoms.params") - - with open(template, "w") as f: - f.write(enum + "detector_type = cylindrical\n") - f.write(string + 'history_file = "' + sorted_file + '"\n') - f.write(string + 'randoms_history_file = "' + randoms_file + '"\n') - f.write( - real + "coincidence_timing_window_in_ns = " + str(coincidence_window) + "\n" - ) - - f.close() - - addrand_bin = join(simset_dir, "bin", "addrandoms") - - command = "%s %s >> %s" % (addrand_bin, template, log_file) - tools.osrun(command, log_file) - - if exists(sorted_file): - os.remove(sorted_file) - - # The following will replace the existing det-hist file with the new including randoms - - if rebin: - det_file = join(sim_dir, "det.rec") - - with open(det_file, "r") as f: - lines = f.readlines() - with open(det_file, "w") as f: - for line in lines: - line = line.replace("det_hf.hist", "randoms.hist") - f.write(line) - - binfile = join(sim_dir, "phg.rec") - phgbin = join(simset_dir, "bin", "bin -d") - - command = command = "%s %s >> %s" % (phgbin, binfile, log_file) - tools.osrun(command, log_file) - - -def combine_history_files(simset_dir, history_files, output, log_file): - combinehist = join(simset_dir, "bin", "combinehist") - - rcommand = "%s %s %s" % (combinehist, history_files, output) - - proc = sp.Popen( - rcommand, - universal_newlines=True, - shell=True, - stdin=sp.PIPE, - stdout=sp.PIPE, - stderr=sp.PIPE, - ).communicate("Yes\n") - - # tools.osrun(rcommand, log_file) - - -def simset_calcattenuation( - simset_dir, sim_dir, output, hdr_to_copy, nrays=1, timeout=36000 -): - calcattenuation = join(simset_dir, "bin", "calcattenuation") - - current_dir = os.getcwd() - os.chdir(sim_dir) - - child = pexpect.spawn(calcattenuation, timeout=timeout) - # child.logfile = sys.stdout.buffer #I comment it because it failed when I run the code on the spyder console - child.expect("Enter name of param file: ") - child.sendline("phg.rec") - child.expect("Enter name of output file: ") - child.sendline(output) - child.expect("Enter the number of sub-samples*") - child.sendline(str(nrays)) - child.wait() - - CHUNK_SIZE = os.path.getsize(hdr_to_copy[0:-3] + "img") - print(CHUNK_SIZE) - with open(output, "rb") as f: - chunk = f.read(CHUNK_SIZE) - with open(output + ".img", "wb") as chunk_file: - chunk_file.write(chunk) - chunk_file.close() - - shutil.copy(hdr_to_copy, output + ".hdr") - - os.chdir(current_dir) +import random +import os +import shutil +import subprocess as sp +from os.path import join, exists +import pexpect +from utils import tools + +import nibabel as nib + + +def make_simset_act_table(act_table_factor, my_act_table, log_file=False): + with open(my_act_table, "w") as f: + f.write("256\n") + for i in range(256): + act_value = i * 0.000001 * act_table_factor + f.write(str("{:.12f}".format(act_value)) + "\n") + f.close() + + if log_file: + message = "Wrote act_table with factor %s" % act_table_factor + tools.log_message(log_file, message, "info") + + +def make_simset_phg( + config, + output_file, + simulation_dir, + act, + scanner_radius, + scanner_axial_fov, + center_slice, + photons, + sim_time, + add_randoms=False, + phg_hf=False, + S=0, + log_file=False, +): + stir_identifier = "# Hello, I am a SimSET PHG file!\n" + + # define types to be used + boolean = "BOOL " + real = "REAL " + longl = "LONGLONG " + integer = "INT " + enum = "ENUM " + string = "STR " + + # Configuring the importance sampling parameters + if add_randoms == 1: + stratification = "false" + forced_detection = "false" + non_absortion = "false" + # We need to force coincidences + singles for randoms + sim_PET_coinc_only = "false" + sim_PET_coinc_plus_singles = "true" + else: + stratification = config.get("stratification") + forced_detection = config.get("forced_detection") + non_absortion = config.get("forced_non_absortion") + sim_PET_coinc_only = "false" + sim_PET_coinc_plus_singles = "true" + + # Configuration of photons an simulation time + + num_to_simulate = str(photons) + length_of_scan = str(sim_time) + + # Import other configurations form config.yml + acceptance_angle = str(config.get("acceptance_angle")) + positron_range = config.get("positron_range") + non_col = config.get("non_colinearity") + minimum_energy = str(config.get("minimum_energy")) + weight_window_ratio = str(config.get("weight_window_ratio")) + point_source_voxels = config.get("point_source_voxels") + coherent_scatter_object = config.get("coherent_scatter_object") + coherent_scatter_detector = config.get("coherent_scatter_detector") + simulated_isotope = config.get("isotope") + + # Generate a random seed for the MC + random_seed = str(random.randint(1, 1e12)) + + # Object stuff + nslices = act.shape[2] + xbins = act.shape[0] + ybins = act.shape[1] + + act_fov = abs(act.shape * act.affine) + xMin, xMax = round(-act_fov[0, 0] / 20, 3), round( + act_fov[0, 0] / 20, 3 + ) # 20 is because it must be cm + yMin, yMax = round(-act_fov[1, 1] / 20, 3), round(act_fov[1, 1] / 20, 3) + + z_offset = abs( + act.affine[2, 2] * center_slice / 10 + 0.5 * act.affine[2, 2] / 10 + ) # cm + zMin, zMax = round(-z_offset, 3), round(act_fov[2, 2] / 10 - z_offset, 3) + + dz = round((zMax - zMin) / nslices, 2) + + max_z_target = scanner_axial_fov / 2 + min_z_target = -scanner_axial_fov / 2 + + # Directory variables + simset_dir = config.get("dir_simset") + simset_phgdata_dir = join(simset_dir, "phg.data") + + # Now we write the phg file + with open(output_file, "w") as f: + # First we write the configuration stuff for the simulation + f.write(stir_identifier) + f.write("\n\n# Runtime options\n") + f.write(boolean + "simulate_stratification = %s \n" % stratification) + f.write(boolean + "simulate_forced_detection = " + forced_detection + "\n") + f.write(boolean + "forced_non_absorbtion = " + non_absortion + "\n") + f.write(real + "acceptance_angle = " + acceptance_angle + "\n") + f.write(longl + "num_to_simulate = " + num_to_simulate + "\n") + f.write(real + "length_of_scan = " + length_of_scan + "\n") + f.write(boolean + "simulate_SPECT = false\n") + f.write( + boolean + "simulate_PET_coincidences_only = " + sim_PET_coinc_only + "\n" + ) + f.write( + boolean + + "simulate_PET_coincidences_plus_singles = " + + sim_PET_coinc_plus_singles + + "\n" + ) + f.write(boolean + "adjust_for_positron_range = " + positron_range + "\n") + f.write(boolean + "adjust_for_collinearity = " + non_col + "\n") + f.write(real + "minimum_energy = " + minimum_energy + "\n") + f.write(real + "photon_energy = 511.0\n") + f.write(real + "weight_window_ratio = " + weight_window_ratio + "\n") + f.write(boolean + "point_source_voxels = " + point_source_voxels + "\n") + f.write(integer + "random_seed = " + random_seed + "\n") + f.write( + boolean + + "model_coherent_scatter_in_obj = " + + coherent_scatter_object + + "\n" + ) + f.write( + boolean + + "model_coherent_scatter_in_tomo = " + + coherent_scatter_detector + + "\n" + ) + f.write(enum + "isotope = " + simulated_isotope + "\n") + + # Now comes the object stuff (old make_phg_simset from fruitcake) + f.write("\n\n# OBJECT GEOMETRY VALUES\n") + f.write("\nNUM_ELEMENTS_IN_LIST object = %s" % str(nslices + 1)) + f.write("\n INT num_slices = %s" % str(nslices)) + + for i in range(nslices): + zMin_value = round(zMin + i * dz, 2) + zMax_value = round(zMin + (i + 1) * dz, 2) + f.write( + "\n NUM_ELEMENTS_IN_LIST slice = 9 " + + "\n INT slice_number = %s" % str(i) + + "\n REAL zMin = %s" % str(zMin_value) + + "\n REAL zMax = %s" % str(zMax_value) + + "\n REAL xMin = %s" % str(xMin) + + "\n REAL xMax = %s" % str(xMax) + + "\n REAL yMin = %s" % str(yMin) + + "\n REAL yMax = %s" % str(yMax) + + "\n INT num_X_bins = %s" % str(xbins) + + "\n INT num_Y_bins = %s" % str(ybins) + ) + + # Now we fill the values for the target cylinder + + f.write("\n\n# TARGET CYLINDER INFORMATION") + f.write("\nNUM_ELEMENTS_IN_LIST target_cylinder = 3") + f.write("\n REAL target_zMin = %s" % str(min_z_target)) + f.write("\n REAL target_zMax = %s" % str(max_z_target)) + f.write("\n REAL radius = %s\n\n" % str(scanner_radius)) + + # Now we need the directory stuff + f.write( + string + + 'coherent_scatter_table = "' + + join(simset_phgdata_dir, "coh.tables") + + '"\n' + ) + f.write( + string + + 'activity_indexes = "' + + join(simulation_dir, "rec.act_indexes") + + '"\n' + ) + f.write( + string + + 'activity_table = "' + + join(simulation_dir, "phg_act_table") + + '"\n' + ) + f.write( + string + + 'activity_index_trans = "' + + join(simset_phgdata_dir, "phg_act_index_trans") + + '"\n' + ) + f.write( + string + + 'activity_image = "' + + join(simulation_dir, "rec.activity_image") + + '"\n' + ) + f.write( + string + + 'attenuation_indexes = "' + + join(simulation_dir, "rec.att_indexes") + + '"\n' + ) + f.write( + string + + 'attenuation_table = "' + + join(simset_phgdata_dir, "phg_att_table") + + '"\n' + ) + f.write( + string + + 'attenuation_index_trans = "' + + join(simset_phgdata_dir, "phg_att_index_trans") + + '"\n' + ) + f.write( + string + + 'attenuation_image = "' + + join(simulation_dir, "rec.attenuation_image") + + '"\n' + ) + + # This part changes productivity tables between S=0 and S=1 adq + if S == 1: + f.write( + string + + 'productivity_input_table = "' + + join(simulation_dir, "sampling_rec") + + '"\n' + ) + f.write(string + 'productivity_output_table = ""\n') + else: + f.write(string + 'productivity_input_table = ""\n') + f.write( + string + + 'productivity_output_table = "' + + join(simulation_dir, "sampling_rec") + + '"\n' + ) + + f.write( + string + 'statistics_file = "' + join(simulation_dir, "rec.stat") + '"\n' + ) + f.write( + string + + 'isotope_data_file = "' + + join(simset_phgdata_dir, "isotope_positron_energy_data") + + '"\n' + ) + f.write( + string + + 'detector_params_file = "' + + join(simulation_dir, "det.rec") + + '"\n' + ) + f.write( + string + 'bin_params_file = "' + join(simulation_dir, "bin.rec") + '"\n' + ) + # Adds the phg history file if it is activated + if phg_hf == 1: + f.write( + string + + 'history_file = "' + + join(simulation_dir, "phg_hf.hist" + '"\n') + ) + + f.close() + + if log_file: + message = "Created phg_file in: %s" % output_file + tools.log_message(log_file, message, "info") + + +def make_simset_bin( + config, output_file, simulation_dir, scanner, add_randoms=False, log_file=False +): + stir_identifier = "# Hello, I am a SimSET BIN file!\n" + + # define types to be used + boolean = "BOOL " + real = "REAL " + integer = "INT " + string = "STR " + + # We will create an extra bin for randoms if the randoms simulation is activated. + if add_randoms: + accept_randoms = "true" + scatter_param = "6" + else: + accept_randoms = "false" + scatter_param = "1" + + min_s = "0" + max_s = "9" + + # Here we get the parameters from the parameter files. + num_z_bins = str(scanner.get("num_rings")) + axial_fov = scanner.get("axial_fov") + min_z, max_z = -axial_fov / 2, axial_fov / 2 + num_aa_bins = str(scanner.get("num_aa_bins")) + num_td_bins = str(scanner.get("num_td_bins")) + min_td = str(-scanner.get("scanner_radius")) + max_td = str(scanner.get("scanner_radius")) + min_e = str(scanner.get("min_energy_window")) + max_e = str(scanner.get("max_energy_window")) + rec_weight_file = join(simulation_dir, "rec.weight") + + with open(output_file, "w") as f: + f.write(stir_identifier) + f.write(boolean + " accept_randoms = " + accept_randoms + "\n") + f.write(integer + "scatter_param = " + scatter_param + "\n") + f.write(integer + "min_s = " + min_s + "\n") + f.write(integer + "max_s = " + max_s + "\n") + f.write(integer + "num_z_bins = " + num_z_bins + "\n") + f.write(real + "min_z = " + str(min_z) + "\n") + f.write(real + "max_z = " + str(max_z) + "\n") + f.write(integer + "num_aa_bins = " + num_aa_bins + "\n") + f.write(integer + "num_td_bins = " + num_td_bins + "\n") + f.write(real + "min_td = " + min_td + "\n") + f.write(real + "max_td = " + max_td + "\n") + f.write("INT num_e1_bins = 1\n") + f.write("INT num_e2_bins = 1\n") + f.write(real + "min_e = " + min_e + "\n") + f.write(real + "max_e = " + max_e + "\n") + f.write("INT weight_image_type = 2\n") #BEFORE: 3 + #f.write("INT weight_image_type = 2\n") #CAREFUL. CHANGED FOR TOTAL-BODY!! + f.write("INT count_image_type = 2\n") + f.write("BOOL add_to_existing_img = false\n") + f.write(string + 'weight_image_path = "' + rec_weight_file + '"\n') + + f.close() + + if log_file: + message = "Created bin_file in: %s" % output_file + tools.log_message(log_file, message, "info") + + +def make_simset_simp_det(scanner_params, output, sim_dir, det_hf=0, log_file=False): + energy_resolution = scanner_params.get("energy_resolution") + new_file = open(output, "w") + new_file.write( + "ENUM detector_type = simple_pet \n\n" + + "REAL reference_energy_keV = 511.0 \n" + + "REAL energy_resolution_percentage = %s \n" % energy_resolution + ) + + if det_hf == 1: + new_file.write( + 'STR history_file = "' + join(sim_dir, "det_hf.hist" + '"\n') + ) + + new_file.close() + + if log_file: + message = ( + "Created det_file with:\n" + "Energy resolution: %s" % energy_resolution + ) + + tools.log_message(log_file, message, "info") + + +def make_simset_cyl_detOLD(scanner_params, output, sim_dir, det_hf=0, log_file=False): + num_rings = scanner_params.get("num_rings") + z_crystal_size = scanner_params.get("z_crystal_size") + axial_fov = scanner_params.get("axial_fov") + max_z = axial_fov / 2 + min_z = -axial_fov / 2 + gap_z_size = (max_z - min_z - z_crystal_size * num_rings) / (num_rings - 1) + cyln_inner_radius = scanner_params.get("scanner_radius") + cyln_outer_radius = cyln_inner_radius + scanner_params.get("crystal_thickness") + energy_resolution = scanner_params.get("energy_resolution") + timing_resolution = scanner_params.get("timing_resolution") + material = scanner_params.get("simset_material") + + nrings_total = 2 * num_rings - 1 + + new_file = open(output, "w") + new_file.write( + "ENUM detector_type = cylindrical \n\n" + + "# This detector example has %s axial rings and %s gaps \n" + % (num_rings, num_rings - 1) + + "INT cyln_num_rings = %s \n\n" % nrings_total + ) + + for i in range(1, num_rings + 1): + ring_zmin = min_z + (i - 1) * z_crystal_size + (i - 1) * gap_z_size + ring_zmax = ring_zmin + z_crystal_size + gap_zmax = ring_zmin + z_crystal_size + gap_z_size + + new_file.write( + "# RING #%s \n" % i + + "# The following defines the ring parameters \n" + + "LIST cyln_ring_info_list = 5 \n" + + "INT cyln_num_layers = 1 \n" + + "LIST cyln_layer_info_list = 4 \n" + + "BOOL cyln_layer_is_active = TRUE \n" + + "INT cyln_layer_material = %s \n" % material + + "REAL cyln_layer_inner_radius = %s \n" % cyln_inner_radius + + "REAL cyln_layer_outer_radius = %s \n" % cyln_outer_radius + + "REAL cyln_min_z = %s \n" % ring_zmin + + "REAL cyln_max_z = %s \n\n" % ring_zmax + ) + + if not i == num_rings: + new_file.write( + "# GAP #%s \n" % i + + "# The following defines the gap parameters \n" + + "LIST cyln_ring_info_list = 5 \n" + + "INT cyln_num_layers = 1 \n" + + "LIST cyln_layer_info_list = 4 \n" + + "BOOL cyln_layer_is_active = FALSE \n" + + "INT cyln_layer_material = 0 \n" + + "REAL cyln_layer_inner_radius = %s \n" % cyln_inner_radius + + "REAL cyln_layer_outer_radius = %s \n" % cyln_outer_radius + + "REAL cyln_min_z = %s \n" % ring_zmax + + "REAL cyln_max_z = %s \n\n" % gap_zmax + ) + + new_file.write( + "REAL reference_energy_keV = 511.0 \n" + + "REAL energy_resolution_percentage = %s \n" % energy_resolution + + "REAL photon_time_fwhm_ns = %s \n" % timing_resolution + ) + if det_hf == 1: + new_file.write( + 'STR history_file = "' + join(sim_dir, "det_hf.hist" + '"\n') + ) + + new_file.close() + + if log_file: + message = ( + "Created det_file with:\n" + + "Cristal z: %s cm\n" % z_crystal_size + + "Gap size: %s cm\n" % gap_z_size + + "Ring thickness: %s cm" % (cyln_outer_radius - cyln_inner_radius) + + "Energy resolution: %s" % energy_resolution + + "Timing resolution: %s" % timing_resolution + ) + + tools.log_message(log_file, message, "info") + + + +def make_simset_cyl_det(scanner_params, output, sim_dir, det_hf=0, log_file=False): + num_rings = scanner_params.get("num_rings") + z_crystal_size = scanner_params.get("z_crystal_size") + axial_fov = scanner_params.get("axial_fov") + max_z = axial_fov / 2 + min_z = -axial_fov / 2 + + + gap_z_size = (max_z - min_z - z_crystal_size * num_rings) + cyln_inner_radius = scanner_params.get("scanner_radius") + cyln_outer_radius = cyln_inner_radius + scanner_params.get("crystal_thickness") + energy_resolution = scanner_params.get("energy_resolution") + timing_resolution = scanner_params.get("timing_resolution") + material = scanner_params.get("simset_material") + + nrings_total = num_rings + 1 + + new_file = open(output, "w") + new_file.write( + "ENUM detector_type = cylindrical \n\n" + + "# This detector example has %s axial rings \n" + % (num_rings) + + "INT cyln_num_rings = %s \n\n" % nrings_total + ) + + for i in range(1, num_rings + 1): + ring_zmin = min_z + (i - 1) * z_crystal_size #+ (i - 1) * gap_z_size + ring_zmax = ring_zmin + z_crystal_size + gap_zmax = ring_zmin + z_crystal_size + gap_z_size + + new_file.write( + "# RING #%s \n" % i + + "# The following defines the ring parameters \n" + + "LIST cyln_ring_info_list = 5 \n" + + "INT cyln_num_layers = 1 \n" + + "LIST cyln_layer_info_list = 4 \n" + + "BOOL cyln_layer_is_active = TRUE \n" + + "INT cyln_layer_material = %s \n" % material + + "REAL cyln_layer_inner_radius = %s \n" % cyln_inner_radius + + "REAL cyln_layer_outer_radius = %s \n" % cyln_outer_radius + + "REAL cyln_min_z = %s \n" % ring_zmin + + "REAL cyln_max_z = %s \n\n" % ring_zmax + ) + + if i == num_rings: + new_file.write( + "# GAP #%s \n" % i + + "# The following defines the gap parameters \n" + + "LIST cyln_ring_info_list = 5 \n" + + "INT cyln_num_layers = 1 \n" + + "LIST cyln_layer_info_list = 4 \n" + + "BOOL cyln_layer_is_active = FALSE \n" + + "INT cyln_layer_material = 0 \n" + + "REAL cyln_layer_inner_radius = %s \n" % cyln_inner_radius + + "REAL cyln_layer_outer_radius = %s \n" % cyln_outer_radius + + "REAL cyln_min_z = %s \n" % ring_zmax + + "REAL cyln_max_z = %s \n\n" % max_z + ) + + new_file.write( + "REAL reference_energy_keV = 511.0 \n" + + "REAL energy_resolution_percentage = %s \n" % energy_resolution + + "REAL photon_time_fwhm_ns = %s \n" % timing_resolution + ) + if det_hf == 1: + new_file.write( + 'STR history_file = "' + join(sim_dir, "det_hf.hist" + '"\n') + ) + + new_file.close() + + if log_file: + message = ( + "Created det_file with:\n" + + "Cristal z: %s cm\n" % z_crystal_size + + "Gap size: %s cm\n" % gap_z_size + + "Ring thickness: %s cm" % (cyln_outer_radius - cyln_inner_radius) + + "Energy resolution: %s" % energy_resolution + + "Timing resolution: %s" % timing_resolution + ) + + tools.log_message(log_file, message, "info") + + +def make_index_file(simulation_dir, simset_dir, log_file=False): + output = join(simulation_dir, "index_file.log") + phg_file = join(simulation_dir, "phg.rec") + act_dat = join(simulation_dir, "act.dat") + att_dat = join(simulation_dir, "att.dat") + make_index_file = join(simset_dir, "bin", "makeindexfile") + + prompts = ( + phg_file + + "\n" + + "y\n" + + "y\n" + + "0\n" + + "y\n" + + act_dat + + "\n" + + "0\n" + + "0\n" + + "1\n" + + "n\n" + + "n\n" + + "y\n" + + "y\n" + + "0\n" + + "y\n" + + att_dat + + "\n" + + "0\n" + + "0\n" + + "1\n" + + "n\n" + + "n\n" + ) + + os.system('printf "%s" | %s > %s' % (prompts, make_index_file, output)) + + if log_file: + message = 'printf "%s" | %s > %s' % (prompts, make_index_file, output) + tools.log_message(log_file, message, "info") + + +def process_weights(weights_file, output_dir, scanner, add_randoms=0): + nbins = scanner.get("num_td_bins") + nangles = scanner.get("num_aa_bins") + nrings = scanner.get("num_rings") + nslices = nrings * nrings + + Simset_offset = 32768 + block_size = nbins * nangles * nslices * 4 + + trues_start = Simset_offset + trues_end = Simset_offset + block_size + trues_file = join(output_dir, "w1") + + with open(weights_file, "rb") as in_file: + with open(trues_file, "wb") as out_file: + out_file.write(in_file.read()[trues_start:trues_end]) + + ############ RESTABLISH IF FAILS: + #output = join(output_dir, "trues.hdr") + #tools.create_analyze_from_imgdata( + # trues_file, output, nbins, nangles, nslices, 1, 1, 1, "fl" + #) + #os.remove(trues_file) + + output = join(output_dir, "trues.nii") + #output = join(output_dir, "trues.nii.gz") + tools.create_nifti_from_imgdata( + trues_file, output, nbins, nangles, nslices, 1, 1, 1, "fl" + ) + os.remove(trues_file) + + + scatter_start = trues_end + scatter_end = trues_end + block_size + scatter_file = join(output_dir, "w2") + + with open(weights_file, "rb") as in_file: + with open(scatter_file, "wb") as out_file: + out_file.write(in_file.read()[scatter_start:scatter_end]) + + ############ RESTABLISH IF FAILS: + #output = join(output_dir, "scatter.hdr") + #tools.create_analyze_from_imgdata( + # scatter_file, output, nbins, nangles, nslices, 1, 1, 1, "fl" + #) + #os.remove(scatter_file) + + output = join(output_dir, "scatter.nii") + #output = join(output_dir, "scatter.nii.gz") + tools.create_nifti_from_imgdata( + scatter_file, output, nbins, nangles, nslices, 1, 1, 1, "fl" + ) + os.remove(scatter_file) + + #EDIT IN FUTURE: + if add_randoms == 1: + randoms_start = scatter_end + randoms_end = scatter_end + block_size + randoms_file = join(output_dir, "w3") + + with open(weights_file, "rb") as in_file: + with open(randoms_file, "wb") as out_file: + out_file.write(in_file.read()[randoms_start:randoms_end]) + + output = join(output_dir, "randoms.hdr") + tools.create_analyze_from_imgdata( + randoms_file, output, nbins, nangles, nslices, 1, 1, 1, "fl" + ) + os.remove(randoms_file) + + +def add_randoms(sim_dir, simset_dir, coincidence_window, rebin=True, log_file=False): + string = "STR " + integer = "INT " + enum = "ENUM " + real = "REAL " + + sorted_file = join(sim_dir, "sorted_det_hf.hist") + template = join(sim_dir, "sort.params") + det_hf = join(sim_dir, "det_hf.hist") + + with open(template, "w") as f: + f.write(string + 'history_file = "' + det_hf + '"\n') + f.write(string + 'sorted_history_file = "' + sorted_file + '"\n') + f.write(integer + "buffer_size = 1024\n") + + f.close() + + sorting_bin = join(simset_dir, "bin", "timesort -d") + + command = command = "%s %s >> %s" % (sorting_bin, template, log_file) + tools.osrun(command, log_file) + + randoms_file = join(sim_dir, "randoms.hist") + template = join(sim_dir, "randoms.params") + + with open(template, "w") as f: + f.write(enum + "detector_type = cylindrical\n") + f.write(string + 'history_file = "' + sorted_file + '"\n') + f.write(string + 'randoms_history_file = "' + randoms_file + '"\n') + f.write( + real + "coincidence_timing_window_in_ns = " + str(coincidence_window) + "\n" + ) + + f.close() + + addrand_bin = join(simset_dir, "bin", "addrandoms") + + command = "%s %s >> %s" % (addrand_bin, template, log_file) + tools.osrun(command, log_file) + + if exists(sorted_file): + os.remove(sorted_file) + + # The following will replace the existing det-hist file with the new including randoms + + if rebin: + det_file = join(sim_dir, "det.rec") + + with open(det_file, "r") as f: + lines = f.readlines() + with open(det_file, "w") as f: + for line in lines: + line = line.replace("det_hf.hist", "randoms.hist") + f.write(line) + + binfile = join(sim_dir, "phg.rec") + phgbin = join(simset_dir, "bin", "bin -d") + + command = command = "%s %s >> %s" % (phgbin, binfile, log_file) + tools.osrun(command, log_file) + + +def combine_history_files(simset_dir, history_files, output, log_file): + combinehist = join(simset_dir, "bin", "combinehist") + + rcommand = "%s %s %s" % (combinehist, history_files, output) + + proc = sp.Popen( + rcommand, + universal_newlines=True, + shell=True, + stdin=sp.PIPE, + stdout=sp.PIPE, + stderr=sp.PIPE, + ).communicate("Yes\n") + + # tools.osrun(rcommand, log_file) + + +def simset_calcattenuation( + simset_dir, sim_dir, output, hdr_to_copy, nrays=1, timeout=36000 +): + calcattenuation = join(simset_dir, "bin", "calcattenuation") + + current_dir = os.getcwd() + os.chdir(sim_dir) + + child = pexpect.spawn(calcattenuation, timeout=timeout) + # child.logfile = sys.stdout.buffer #I comment it because it failed when I run the code on the spyder console + child.expect("Enter name of param file: ") + child.sendline("phg.rec") + child.expect("Enter name of output file: ") + child.sendline(output) + child.expect("Enter the number of sub-samples*") + child.sendline(str(nrays)) + child.wait() + + + #RESTABLISH IF FAILS: + #CHUNK_SIZE = os.path.getsize(hdr_to_copy[0:-3] + "img") + #print(CHUNK_SIZE) + #with open(output, "rb") as f: + # chunk = f.read(CHUNK_SIZE) + #with open(output + ".img", "wb") as chunk_file: + # chunk_file.write(chunk) + # chunk_file.close() + + #shutil.copy(hdr_to_copy, output + ".hdr") + + + nib.save(nib.load(hdr_to_copy), hdr_to_copy[0:-3] + "hdr") #TRUES AUXILIAR IMAGE + #nib.save(nib.load(hdr_to_copy), hdr_to_copy[0:-6] + "hdr") #TRUES AUXILIAR IMAGE + + CHUNK_SIZE = os.path.getsize(hdr_to_copy[0:-3] + "img") + #CHUNK_SIZE = os.path.getsize(hdr_to_copy[0:-6] + "img") + with open(output, "rb") as f: + chunk = f.read(CHUNK_SIZE) + with open(output + ".img", "wb") as chunk_file: + chunk_file.write(chunk) + chunk_file.close() + + + #shutil.copy(hdr_to_copy[0:-6] + "hdr", output + ".hdr") + shutil.copy(hdr_to_copy[0:-3] + "hdr", output + ".hdr") + + #nib.save(nib.load(output + ".hdr"), output + ".nii.gz") + nib.save(nib.load(output + ".hdr"), output + ".nii") + + #Remove unnecessary files: + #os.remove(hdr_to_copy[0:-6] + "hdr") + #os.remove(hdr_to_copy[0:-6] + "img") + os.remove(hdr_to_copy[0:-3] + "hdr") + os.remove(hdr_to_copy[0:-3] + "img") + os.remove(output + ".hdr") + os.remove(output + ".img") + + + + + #NOT WORKING: + """ + #CHUNK_SIZE = os.path.getsize(hdr_to_copy[0:-3] + "nii") + CHUNK_SIZE = os.path.getsize(hdr_to_copy[0:-6] + "nii.gz") + #print(CHUNK_SIZE) + with open(output, "rb") as f: + chunk = f.read(CHUNK_SIZE) + #with open(output + ".nii", "wb") as chunk_file: + with open(output + ".nii.gz", "wb") as chunk_file: + chunk_file.write(chunk) + chunk_file.close() + + #shutil.copy(hdr_to_copy, output + ".nii") + shutil.copy(hdr_to_copy, output + ".nii.gz") + """ + + os.chdir(current_dir) From 64dd9f3d26c1fc4e1e2cf42762ffb58b5feb6a88 Mon Sep 17 00:00:00 2001 From: arnaufme8 <113554412+arnaufme8@users.noreply.github.com> Date: Tue, 17 Mar 2026 23:12:20 +0100 Subject: [PATCH 05/23] Adding file for Total-Body: stir src --- src/stir/stir_tools.py | 192 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 188 insertions(+), 4 deletions(-) diff --git a/src/stir/stir_tools.py b/src/stir/stir_tools.py index ee8ffe94..496f43f4 100644 --- a/src/stir/stir_tools.py +++ b/src/stir/stir_tools.py @@ -8,6 +8,8 @@ from utils import tools from utils import resources as rsc +import stir + def create_stir_hs_from_detparams(scannerParams, output_file, output_format="SimSET"): num_rings = scannerParams.get("num_rings") @@ -155,6 +157,154 @@ def create_stir_hs_from_detparams(scannerParams, output_file, output_format="Sim ) +def create_reduced_stir_hs_from_detparams(scannerParams, output_file, output_format="SimSET"): + num_rings = scannerParams.get("num_rings") + max_z = scannerParams.get("axial_fov") / 2 + min_z = -scannerParams.get("axial_fov") / 2 + z_crystal_size = scannerParams.get("z_crystal_size") + gap_size = (max_z - min_z - z_crystal_size * num_rings) / (num_rings - 1) + ring_spacing = z_crystal_size + gap_size + max_segment = scannerParams.get("max_segment") + + min_td = -scannerParams.get("scanner_radius") + max_td = scannerParams.get("scanner_radius") + td_bins = scannerParams.get("num_td_bins") + bin_size = (max_td - min_td) / float(td_bins) + matrix_size, ring_difference = generate_segments_lists_stir( + num_rings, max_segment #- 1 + ) + transaxial_crystal_distance = scannerParams.get("transaxial_crystal_size") + + scanner_name = scannerParams.get("scanner_name") + + if output_format == "SimSET": + views_coordinate = 2 + axial_coordinate = 3 + else: + views_coordinate = 3 + axial_coordinate = 2 + + new_file = open(output_file, "w") + + new_file.write( + "!INTERFILE :=\n" + + "!imaging modality := PT\n" + + "name of data file := " + + output_file[0:-2] + + "s" + + "\n" + + "originating system := " + + scanner_name + + "\n" + + "!version of keys := STIR3.0\n" + + "!GENERAL DATA :=\n" + + "!GENERAL IMAGE DATA :=\n" + + "!type of data := PET\n" + + "imagedata byte order := LITTLEENDIAN\n" + + "!PET STUDY (General) :=\n" + + "!PET data type := Emission\n" + + "applied corrections := {arc correction}\n" + + "!number format := float\n" + + "!number of bytes per pixel := 4\n" + + "number of dimensions := 4\n" + + "matrix axis label [4] := segment\n" + + "!matrix size [4] := " + + str(2 * max_segment + 1) #THIS CHANGED TO MAX_SEGMENT + + "\n" + + "matrix axis label [" + + str(views_coordinate) + + "] := view\n" + + "!matrix size [" + + str(views_coordinate) + + "] := " + + str(scannerParams.get("num_aa_bins")) + + "\n" + + "matrix axis label [" + + str(axial_coordinate) + + "] := axial coordinate\n" + + "!matrix size [" + + str(axial_coordinate) + + "] := " + + matrix_size #THIS NEEDS TO CHANGE + + "\n" + + "matrix axis label [1] := tangential coordinate\n" + + "!matrix size [1] := " + + str(td_bins) + + "\n" + "minimum ring difference per segment := " + + ring_difference #THIS NEEDS TO CHANGE + + "\n" + + "maximum ring difference per segment := " + + ring_difference #THIS NEEDS TO CHANGE + + "\n" + + "Scanner parameters:= \n" + + "Scanner type := " + + scanner_name + + "\n" + + "Number of rings := " + + str(num_rings) #THIS NEEDS TO CHANGE??? (or maybe not) + + "\n" + + "Number of detectors per ring := " + + str(scannerParams.get("num_aa_bins") * 2) #THIS NEEDS TO CHANGE??? (or maybe not) + + "\n" + "Inner ring diameter (cm) := " + + str(scannerParams.get("scanner_radius") * 2) + + "\n" + + "Average depth of interaction (cm) := " + + str(scannerParams.get("average_doi")) + + "\n" + + "Distance between rings (cm) := " + + str(ring_spacing) + + "\n" + + "Default bin size (cm) := " + + str(bin_size) + + "\n" + + "View offset (degrees) := 0\n" + + "Maximum number of non-arc-corrected bins := " + + str(td_bins) + + "\n" + + "Default number of arc-corrected bins := " + + str(td_bins) + + "\n" + + "Energy_resolution := " + + str(scannerParams.get("energy_resolution") / 100) + + "\n" + + "Reference energy (in keV) := 511\n" + + "Number of blocks per bucket in transaxial direction := 1\n" + + "Number of blocks per bucket in axial direction := 1\n" + + "Number of crystals per block in axial direction := 1\n" + + "Number of crystals per block in transaxial direction := 1\n" + + "Number of crystals per singles unit in axial direction := 1\n" + + "Number of crystals per singles unit in transaxial direction := 1\n" + + "Scanner geometry := Cylindrical" + + "\n" + + "Distance between crystals in axial direction (cm) := " + + str(ring_spacing) + + "\n" + + "Distance between crystals in transaxial direction (cm) := " + + str(transaxial_crystal_distance) + + "\n" + + "Distance between blocks in transaxial direction (cm) := " + + str(transaxial_crystal_distance) + + "\n" + + "Distance between crystals in axial direction (cm) := " + + str(ring_spacing) + + "\n" + + "Distance between blocks in axial direction (cm) := " + + str(ring_spacing) + + "\n" + + "end scanner parameters:=\n" + + "effective central bin size (cm) := " + + str(bin_size) + + "\n" + + "number of time frames := 1\n" + + "start vertical bed position (mm) := 0\n" + + "start horizontal bed position (mm) := 0\n" + + "!END OF INTERFILE :=\n" + ) + + + def generate_segments_lists_stir(nrings, max_segment): last_segment_sinograms = nrings - max_segment my_matrix_size = " " @@ -558,7 +708,8 @@ def OSEM3D_recons( numberOfSubsets = scannerParams.get("numberOfSubsets") numberOfIterations = scannerParams.get("numberOfIterations") savingInterval = scannerParams.get("savingInterval") - + + """ #RESTABLISH IF FAILS if scannerParams.get("stir_recons_att_corr") == 1: att_corr_str = ( "Bin Normalisation type := From ProjData \n" @@ -570,7 +721,29 @@ def OSEM3D_recons( ) else: att_corr_str = "" - + """ + + if scannerParams.get("stir_norm_from_att_map") == 1: + att_corr_str = ( + "Bin Normalisation From Attenuation Image:= \n" + + "attenuation_image_filename := " + + join(output_dir, 'mu_map.hv') + + "\n" + + "End Bin Normalisation From Attenuation Image := \n" + ) + elif scannerParams.get("stir_recons_att_corr") == 1: + att_corr_str = ( + "Bin Normalisation type := From ProjData \n" + + "Bin Normalisation From ProjData := \n" + + "normalisation projdata filename:= " + + att_stir + + "\n" + + "End Bin Normalisation From ProjData:= \n" + ) + else: + att_corr_str = "" + + if ( scannerParams.get("stir_scatt_corr_smoothing") == 1 ): # Will use smoothed SimSET scatter as additive_sinogram. @@ -672,12 +845,23 @@ def OSEM3D_recons( ) new_file.close() - + + #RESTABLISH IF FAILS!!!! command = "%s %s >> %s" % (recons, paramsFile, log_file) tools.osrun(command, log_file) + + #recon=stir.OSMAPOSLReconstruction3DFloat(paramsFile) + + #print("ATTRIBUTES:", dir(recon)) + + #target=recon.get_initial_data() + #target.fill(1) + #s=recon.set_up(target) + #recon.reconstruct(target) output = recFileName + "_" + str(scannerParams.get("numberOfIterations")) + ".hv" - output = tools.anything_to_hdr_convert(output, log_file) + #output = tools.anything_to_hdr_convert(output, log_file) + output = tools.convert_hv_to_nii(output, log_file) return output From a653712c24bf80854c43e0d5e8dc34367b33c505 Mon Sep 17 00:00:00 2001 From: arnaufme8 <113554412+arnaufme8@users.noreply.github.com> Date: Tue, 17 Mar 2026 23:13:10 +0100 Subject: [PATCH 06/23] Adding templates adapted for Total-Body --- configs/params/scanner/discovery_st.yaml | 2 ++ configs/params/scanner/siemens_quadra.yaml | 42 ++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 configs/params/scanner/siemens_quadra.yaml diff --git a/configs/params/scanner/discovery_st.yaml b/configs/params/scanner/discovery_st.yaml index e7f02dfd..3b2c8b57 100644 --- a/configs/params/scanner/discovery_st.yaml +++ b/configs/params/scanner/discovery_st.yaml @@ -36,3 +36,5 @@ zoomFactor: 1.55 xyOutputSize: 128 zOutputSize: 47 zOutputVoxelSize: 3.27 + +stir_norm_from_att_map: 0 #1 for correcting with STIR, 0 for correcting with SimSET diff --git a/configs/params/scanner/siemens_quadra.yaml b/configs/params/scanner/siemens_quadra.yaml new file mode 100644 index 00000000..d525a889 --- /dev/null +++ b/configs/params/scanner/siemens_quadra.yaml @@ -0,0 +1,42 @@ +scanner_name: "Siemens Quadra" +simset_material: 18 #29 = LYSO? 18 = LSO? +average_doi: 0.84 #MISSING +scanner_radius: 41.0 +num_rings: 320 #320 +axial_fov: 106 #106 #15.7 +z_crystal_size: 0.32 +transaxial_crystal_size: 0.32 +crystal_thickness: 2 +energy_resolution: 16 #CHANGE TO 9!!!!!!!! +num_aa_bins: 252 #440 #? 210, KEEP DISCOVERY #798/2 = 399 +num_td_bins: 344 #440 #? 249, KEEP DISCOVERY +min_energy_window: 435 +max_energy_window: 585 +coincidence_window: 4.7 #ns + +numberOfSubsets: 7 +numberOfIterations: 5 #32 +savingInterval: 5 #8 +analytical_att_correction: 0 +stir_recons_att_corr: 1 +analytic_scatt_corr_factor: 0.15 +stir_scatt_corr_smoothing: 0 +stir_scatt_simulation: 0 +analytic_randoms_corr_factor: 0.2 +stir_randoms_corr_smoothing: 0 +recons_type: "OSEM3D" +inter_iteration_filter: 0 +subiteration_interval: 4 +x_dir_filter_FWHM: 1.5 +y_dir_filter_FWHM: 1.5 +z_dir_filter_FWHM: 3 +psf_value: 0 +add_noise: 0 + +max_segment: 85 #319 #85 MAX: 40 +zoomFactor: 0.25 #1 +xyOutputSize: 110 #440 #220 #CHANGE IT TO DOUBLE OR BY FACTOR OR 3 +zOutputSize: 645 #645 #325 #CHANGE IT TO DOUBLE OR BY FACTOR OR 3 +zOutputVoxelSize: 6.6 #1.65 #3.3 + +stir_norm_from_att_map: 1 From 732c1b9223d6e4b64cf53c2dd06e8970c1e38843 Mon Sep 17 00:00:00 2001 From: arnaufme8 Date: Tue, 17 Mar 2026 23:24:14 +0100 Subject: [PATCH 07/23] Added simset submodule --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index b432fb08..1727b476 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,4 +3,4 @@ url = https://github.com/UCL/STIR [submodule "submodules/simset_simpet"] path = submodules/simset_simpet - url = https://github.com/txusser/simset_simpet.git + url = https://github.com/arnaufme8/simset_simpet.git From 766db8cb7553a8780423f85cd5711437f6930d5d Mon Sep 17 00:00:00 2001 From: arnaufme8 Date: Tue, 17 Mar 2026 23:30:00 +0100 Subject: [PATCH 08/23] Updated submodule simset_simpet for Total-Body --- submodules/simset_simpet | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/simset_simpet b/submodules/simset_simpet index f036934c..54840ac3 160000 --- a/submodules/simset_simpet +++ b/submodules/simset_simpet @@ -1 +1 @@ -Subproject commit f036934ce199fe0b39201a419223b938bf9540cd +Subproject commit 54840ac3db52621aaafb953ff3c3f02ece9ac1be From a62fe27fa4faa1c2db41a9bd16097b3a6444a981 Mon Sep 17 00:00:00 2001 From: arnaufme8 <113554412+arnaufme8@users.noreply.github.com> Date: Sat, 20 Jun 2026 17:03:39 +0200 Subject: [PATCH 09/23] Add files via upload Allows two options: installing with sudo (workstations) and without sudo (public clusters). --- makefile-sudo | 256 +++++++++++++++++++++++++++++++++++++++++++ makefile-withoutsudo | 231 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 487 insertions(+) create mode 100644 makefile-sudo create mode 100644 makefile-withoutsudo diff --git a/makefile-sudo b/makefile-sudo new file mode 100644 index 00000000..8080b1c9 --- /dev/null +++ b/makefile-sudo @@ -0,0 +1,256 @@ +SHELL := /bin/bash +TMPDIR := /tmp +ROOT_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) +ASSETS_DIR := ${ROOT_DIR}assets +INCLUDE_DIR := ${ROOT_DIR}include +SUBMODULES_DIR := ${ROOT_DIR}submodules + +.PHONY: deps install-simset check-simset clean-simset +.PHONY: install-stir check-stir clean-stir +.PHONY: install-resources check-resources clean-resources +.PHONY: config-git clean-git smudge-filer config-paths clean-paths +.PHONY: dummy-data install clean help + +deps: + sudo apt-get -y -q update ;\ + sudo apt-get install -y -q \ + software-properties-common \ + git[all] \ + git-lfs \ + wget \ + unzip \ + sshpass \ + libboost-dev \ + libboost-all-dev \ + libpcre3 \ + libpcre3-dev \ + libncurses-dev \ + cmake \ + g++ \ + swig ;\ + sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys CC86BB64 ;\ + sudo add-apt-repository -y ppa:rmescandon/yq ;\ + sudo apt-get -y -q update ;\ + sudo apt-get install -y -q yq + +SIMSET_SUBMODULE_DIR = ${ROOT_DIR}/submodules/simset_simpet +SIMSET_DEST_DIR = ${INCLUDE_DIR}/SimSET +SIMSET_PATH = ${SIMSET_DEST_DIR}/2.9.2 +SIMSET_BIN = ${SIMSET_PATH}/bin +SIMSET_LIB = ${SIMSET_PATH}/lib +SIMSET_MKALL = ${SIMSET_PATH}/make_all.sh +SIMSET_MKFILE = ${SIMSET_PATH}/make.files/simset.make + +install-simset: + if [ ! -d ${SIMSET_DEST_DIR} ]; then \ + mkdir -p ${SIMSET_PATH} ;\ + cp -r ${SIMSET_SUBMODULE_DIR}/* ${SIMSET_PATH} ;\ + sed -i 's/^\(SIMSET_PATH = \).*$$/\1$(subst /,\/,${SIMSET_PATH})/' ${SIMSET_MKFILE} ;\ + cd ${SIMSET_PATH} && mkdir -p ${SIMSET_LIB} && bash ${SIMSET_MKALL} ;\ + else \ + echo "${SIMSET_DEST_DIR} already exists, run clean-simset if you really want to remove it (you will have to intall SimSET again.)" ;\ + fi + +SIMSET_TAR = ${ASSETS_DIR}/phg.2.9.2.tar.Z +SIMSET_STIR_PATCH = ${ASSETS_DIR}/simset_for_stir.patch + +install-canonic-simset: ${SIMSET_TAR} + if [ ! -d ${SIMSET_DEST_DIR} ]; then \ + mkdir -p ${SIMSET_DEST_DIR} && tar -xvf "${SIMSET_TAR}" --directory=${SIMSET_DEST_DIR} ;\ + cd ${SIMSET_DEST_DIR} && patch -s -p0 < ${SIMSET_STIR_PATCH} ;\ + sed -i 's/^\(SIMSET_PATH = \).*$$/\1$(subst /,\/,${SIMSET_PATH})/' ${SIMSET_MKFILE} ;\ + cd ${SIMSET_PATH} && mkdir -p ${SIMSET_LIB} && bash ${SIMSET_MKALL} ;\ + else \ + echo "${SIMSET_DEST_DIR} already exists, run clean-simset if you really want to remove it (you will have to intall SimSET again.)" ;\ + fi + +check-simset: + declare -a simset_files=(addrandoms bin calcattenuation combinehist makeindexfile phg timesort) ;\ + for file in "$${simset_files[@]}"; do \ + if [ ! -f "${SIMSET_BIN}/$${file}" ]; then \ + echo "${SIMSET_BIN}/$${file} does not exists, check your installation." ;\ + else \ + echo "${SIMSET_BIN}/$${file} exists." ;\ + fi ;\ + done + +clean-simset: + rm -rf ${SIMSET_DEST_DIR} + +SIMSET_SRC = ${SIMSET_PATH}/src +SIMSET_LIBSIMSET = ${SIMSET_LIB}/libsimset.so +STIR_DIR = ${SUBMODULES_DIR}/STIR/STIR +STIR_BUILD_DIR = ${SUBMODULES_DIR}/STIR/build +STIR_INSTALL_DIR = ${SUBMODULES_DIR}/STIR/install +STIR_INSTALL_BIN = ${STIR_INSTALL_DIR}/bin +STIR_MKFILE = ${STIR_BUILD_DIR}/CMakeCache.txt +STIR_FINAL_DEST_DIR = ${INCLUDE_DIR}/STIR +NPROC = $(shell nproc) + +install-stir: install-simset ${STIR_DIR} + if [ ! -d ${STIR_FINAL_DEST_DIR} ]; then \ + mkdir -p ${STIR_BUILD_DIR} ${STIR_INSTALL_DIR} ${STIR_FINAL_DEST_DIR} ;\ + cd ${STIR_BUILD_DIR} && cmake ${STIR_DIR} ;\ + sed -i \ + -e 's/^\(BUILD_SWIG_PYTHON\).*$$/\1:BOOL=OFF/' \ + -e 's/^\(CMAKE_INSTALL_PREFIX\).*$$/\1:PATH=$(subst /,\/,${STIR_INSTALL_DIR})/' \ + -e 's/^\(SIMSET_INCLUDE_DIRS\).*$$/\1:PATH=$(subst /,\/,${SIMSET_SRC})/' \ + -e 's/^\(SIMSET_LIBRARY\).*$$/\1:FILEPATH=$(subst /,\/,${SIMSET_LIBSIMSET})/' \ + -e 's/^\(STIR_OPENMP\).*$$/\1:BOOL=ON/' \ + ${STIR_MKFILE} ;\ + cmake ${STIR_DIR} ;\ + make -s -j${NPROC} ;\ + sudo make install ;\ + mv ${STIR_BUILD_DIR} ${STIR_INSTALL_DIR} ${STIR_FINAL_DEST_DIR} ;\ + else \ + echo "${STIR_FINAL_DEST_DIR} already exists, run clean-stir if you really want to remove it (you will have to intall STIR again)." ;\ + fi + +check-stir: + declare -a stir_files=(FBP2D FBP3DRP forward_project lm_to_projdata OSMAPOSL zoom_image) ;\ + for file in "$${stir_files[@]}"; do \ + if [ ! -f "${STIR_FINAL_DEST_DIR}/install/bin/$${file}" ]; then \ + echo "${STIR_FINAL_DEST_DIR}/install/bin/$${file} does not exists, check your installation." ;\ + else \ + echo "${STIR_FINAL_DEST_DIR}/install/bin/$${file} exists." ;\ + fi ;\ + done + +clean-stir: + rm -rf ${STIR_FINAL_DEST_DIR} + +RESOURCES_ZIP = ${ASSETS_DIR}/fruitcake.zip +RESOURCES_TMP = ${TMPDIR}/resources + +install-resources: ${RESOURCES_ZIP} + mkdir -p ${RESOURCES_TMP} ${INCLUDE_DIR} && unzip -o ${RESOURCES_ZIP} -d ${RESOURCES_TMP} ;\ + declare -a resources=(fruitcake format_converters) ;\ + for rce in "$${resources[@]}"; do \ + include_path="${INCLUDE_DIR}/$${rce}" ;\ + tmp_path="${RESOURCES_TMP}/$${rce}" ;\ + if [ ! -d $${include_path} ]; then \ + mv $${tmp_path} ${INCLUDE_DIR} ;\ + chmod -R +x $${include_path} ;\ + else \ + echo "$${include_path} already exists, run make clean-resources in order to clean resources installation (fruitcake will be removed as well)." ;\ + fi ;\ + done ;\ + rm -rf ${RESOURCES_TMP} + +check-resources: + declare -a resources_paths=(${FRUITCAKE_PATH} ${FORMAT_CONVERTERS_PATH}) ;\ + for path in "$${resources_paths[@]}"; do \ + if [ ! -d "$${path}" ]; then \ + echo "$${path} does not exists, clean fruitcake and format_converters with clean-resources and run install-resources." ;\ + else \ + echo "$${path} exists." ;\ + fi ;\ + done + +clean-resources: + rm -rf ${INCLUDE_DIR}/fruitcake ${INCLUDE_DIR}/format_converters + +config-git: + git config --local filter.config-test.smudge ${ROOT_DIR}scripts/smudge-config_test.sh + git config --local filter.config-test.clean ${ROOT_DIR}scripts/clean-config_test.sh + chmod +x -R ${ROOT_DIR}scripts + +clean-git: + git config --local --unset filter.config-test.smudge + git config --local --unset filter.config-test.clean + +smudge-filter: + rm -f ${ROOT_DIR}configs/config.yaml 2> /dev/null + git checkout HEAD -- ${ROOT_DIR}configs/config.yaml + +FRUITCAKE_PATH = ${INCLUDE_DIR}/fruitcake +FRUITCAKE_BIN = ${FRUITCAKE_PATH}/bin +FRUITCAKE_LIB = ${FRUITCAKE_PATH}/book/lib +FORMAT_CONVERTERS_PATH = ${INCLUDE_DIR}/format_converters + +config-paths: + touch "$${HOME}"/.bashrc ;\ + declare -a simpet_paths=( \ + 'export PATH=${FRUITCAKE_BIN}:$$PATH' \ + 'export LD_LIBRARY_PATH=${FRUITCAKE_LIB}:$$LD_LIBRARY_PATH' \ + 'export PATH=${FORMAT_CONVERTERS_PATH}:$$PATH' \ + ) ;\ + for path in "$${simpet_paths[@]}"; do \ + grep -qxF "$${path}" "$${HOME}"/.bashrc || echo "$${path}" >> "$${HOME}"/.bashrc ;\ + done + +clean-paths: + touch "$${HOME}/.bashrc" + sed -i \ + -e '/export PATH=$(subst /,\/,${FRUITCAKE_BIN}):$$PATH/d' \ + -e '/export LD_LIBRARY_PATH=$(subst /,\/,${FRUITCAKE_LIB}):$$LD_LIBRARY_PATH/d' \ + -e '/export PATH=$(subst /,\/,${FORMAT_CONVERTERS_PATH}):$$PATH/d' \ + "$${HOME}/.bashrc" + +DATA_DIR = ${ROOT_DIR}Data +DATA_ZIP = ${ASSETS_DIR}/Data.zip + +dummy-data: ${DATA_ZIP} + if [ ! -d "${DATA_DIR}" ]; then \ + mkdir -p ${DATA_DIR} && unzip -o ${DATA_ZIP} -d ${ROOT_DIR} ;\ + else \ + echo "${DATA_DIR} already exists, remove it manually." ;\ + fi + +install: + ${MAKE} \ + deps \ + install-simset \ + check-simset \ + install-stir \ + check-stir \ + install-resources \ + check-resources \ + config-paths \ + +clean: + ${MAKE} \ + clean-simset \ + clean-stir \ + clean-resources + +help: + @echo "Help:" + @echo " - deps: Install the dependencies of the projects via apt." + @echo "" + @echo " - install-simset: Install tweaked version of SimSET. + @echo "" + @echo " - install-canonic-simset: Install non-tweaked version of SimSET with STIR patch at directory ${SIMSET_DEST_DIR}." + @echo "" + @echo " - check-simset: Check that SimSET binaries exist." + @echo "" + @echo " - clean-simset: Clean SimSET installation (removes ${SIMSET_DEST_DIR} directory)." + @echo "" + @echo " - install-stir: Install STIR at directory $(shell dirname ${STIR_DIR}), install-simset is a prerequisite. Set NPROC=n to use n CPU cores in compilation." + @echo "" + @echo " - check-stir: Check that STIR binaries exist." + @echo "" + @echo " - clean-stir: Clean STIR installation removing ${STIR_INSTALL_DIR} and ${STIR_BUILD_DIR} directories." + @echo "" + @echo " - install-resources: Decompress ${RESOURCES_ZIP} and move fruitcake and format_converters to ${INCLUDE_DIR}" + @echo "" + @echo " - check-resources: Checks that fruitcake and format_converters are in ${INCLUDE_DIR}." + @echo "" + @echo " - clean-resources: Remove fruitcake and format_converters from ${INCLUDE_DIR}." + @echo "" + @echo " - config-git: Add project filter drivers to local git configuration and make them executable." + @echo "" + @echo " - clean-git: Remove project filter drivers from local git configuration." + @echo "" + @echo " - smudge-filer: Apply smudge filter to config file (updates paths)." + @echo "" + @echo " - config-paths: Iff not present in .bashrc, the paths of the projects will be appended to the file. If ~/.bashrc does not exists it will be created." + @echo "" + @echo " - clean-paths: If the paths of the project are present in .bashrc file, they will be deleted. If ~/.bashrc does not exists it will be created." + @echo "" + @echo " - dummy-data: Uncompress ${DATA_ZIP} at ${DATA_DIR} if ${DATA_DIR} does not exists." + @echo "" + @echo " - install: Run all install, config and check recipes." + @echo "" + @echo " - clean: Run all clean recipes." + diff --git a/makefile-withoutsudo b/makefile-withoutsudo new file mode 100644 index 00000000..7a7dba7a --- /dev/null +++ b/makefile-withoutsudo @@ -0,0 +1,231 @@ +SHELL := /bin/bash +TMPDIR := /tmp +ROOT_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) +ASSETS_DIR := ${ROOT_DIR}assets +INCLUDE_DIR := ${ROOT_DIR}include +SUBMODULES_DIR := ${ROOT_DIR}submodules + +.PHONY: install-simset check-simset clean-simset +.PHONY: install-stir check-stir clean-stir +.PHONY: install-resources check-resources clean-resources +.PHONY: config-git clean-git smudge-filer config-paths clean-paths +.PHONY: dummy-data install clean help + +SIMSET_SUBMODULE_DIR = ${ROOT_DIR}/submodules/simset_simpet +SIMSET_DEST_DIR = ${INCLUDE_DIR}/SimSET +SIMSET_PATH = ${SIMSET_DEST_DIR}/2.9.2 +SIMSET_BIN = ${SIMSET_PATH}/bin +SIMSET_LIB = ${SIMSET_PATH}/lib +SIMSET_MKALL = ${SIMSET_PATH}/make_all.sh +SIMSET_MKFILE = ${SIMSET_PATH}/make.files/simset.make + +install-simset: + if [ ! -d ${SIMSET_DEST_DIR} ]; then \ + mkdir -p ${SIMSET_PATH} ;\ + cp -r ${SIMSET_SUBMODULE_DIR}/* ${SIMSET_PATH} ;\ + sed -i 's/^\(SIMSET_PATH = \).*$$/\1$(subst /,\/,${SIMSET_PATH})/' ${SIMSET_MKFILE} ;\ + cd ${SIMSET_PATH} && mkdir -p ${SIMSET_LIB} && bash ${SIMSET_MKALL} ;\ + else \ + echo "${SIMSET_DEST_DIR} already exists, run clean-simset if you really want to remove it (you will have to intall SimSET again.)" ;\ + fi + +SIMSET_TAR = ${ASSETS_DIR}/phg.2.9.2.tar.Z +SIMSET_STIR_PATCH = ${ASSETS_DIR}/simset_for_stir.patch + +install-canonic-simset: ${SIMSET_TAR} + if [ ! -d ${SIMSET_DEST_DIR} ]; then \ + mkdir -p ${SIMSET_DEST_DIR} && tar -xvf "${SIMSET_TAR}" --directory=${SIMSET_DEST_DIR} ;\ + cd ${SIMSET_DEST_DIR} && patch -s -p0 < ${SIMSET_STIR_PATCH} ;\ + sed -i 's/^\(SIMSET_PATH = \).*$$/\1$(subst /,\/,${SIMSET_PATH})/' ${SIMSET_MKFILE} ;\ + cd ${SIMSET_PATH} && mkdir -p ${SIMSET_LIB} && bash ${SIMSET_MKALL} ;\ + else \ + echo "${SIMSET_DEST_DIR} already exists, run clean-simset if you really want to remove it (you will have to intall SimSET again.)" ;\ + fi + +check-simset: + declare -a simset_files=(addrandoms bin calcattenuation combinehist makeindexfile phg timesort) ;\ + for file in "$${simset_files[@]}"; do \ + if [ ! -f "${SIMSET_BIN}/$${file}" ]; then \ + echo "${SIMSET_BIN}/$${file} does not exists, check your installation." ;\ + else \ + echo "${SIMSET_BIN}/$${file} exists." ;\ + fi ;\ + done + +clean-simset: + rm -rf ${SIMSET_DEST_DIR} + +SIMSET_SRC = ${SIMSET_PATH}/src +SIMSET_LIBSIMSET = ${SIMSET_LIB}/libsimset.so +STIR_DIR = ${SUBMODULES_DIR}/STIR/STIR +STIR_BUILD_DIR = ${SUBMODULES_DIR}/STIR/build +STIR_INSTALL_DIR = ${SUBMODULES_DIR}/STIR/install +STIR_INSTALL_BIN = ${STIR_INSTALL_DIR}/bin +STIR_MKFILE = ${STIR_BUILD_DIR}/CMakeCache.txt +STIR_FINAL_DEST_DIR = ${INCLUDE_DIR}/STIR +NPROC = $(shell nproc) + +install-stir: install-simset ${STIR_DIR} + if [ ! -d ${STIR_FINAL_DEST_DIR} ]; then \ + mkdir -p ${STIR_BUILD_DIR} ${STIR_INSTALL_DIR} ${STIR_FINAL_DEST_DIR} ;\ + cd ${STIR_BUILD_DIR} && cmake ${STIR_DIR} ;\ + sed -i \ + -e 's/^\(BUILD_SWIG_PYTHON\).*$$/\1:BOOL=OFF/' \ + -e 's/^\(CMAKE_INSTALL_PREFIX\).*$$/\1:PATH=$(subst /,\/,${STIR_INSTALL_DIR})/' \ + -e 's/^\(SIMSET_INCLUDE_DIRS\).*$$/\1:PATH=$(subst /,\/,${SIMSET_SRC})/' \ + -e 's/^\(SIMSET_LIBRARY\).*$$/\1:FILEPATH=$(subst /,\/,${SIMSET_LIBSIMSET})/' \ + -e 's/^\(STIR_OPENMP\).*$$/\1:BOOL=ON/' \ + ${STIR_MKFILE} ;\ + cmake ${STIR_DIR} ;\ + make -s -j${NPROC} ;\ + make install ;\ + mv ${STIR_BUILD_DIR} ${STIR_INSTALL_DIR} ${STIR_FINAL_DEST_DIR} ;\ + else \ + echo "${STIR_FINAL_DEST_DIR} already exists, run clean-stir if you really want to remove it (you will have to intall STIR again)." ;\ + fi + +check-stir: + declare -a stir_files=(FBP2D FBP3DRP forward_project lm_to_projdata OSMAPOSL zoom_image) ;\ + for file in "$${stir_files[@]}"; do \ + if [ ! -f "${STIR_FINAL_DEST_DIR}/install/bin/$${file}" ]; then \ + echo "${STIR_FINAL_DEST_DIR}/install/bin/$${file} does not exists, check your installation." ;\ + else \ + echo "${STIR_FINAL_DEST_DIR}/install/bin/$${file} exists." ;\ + fi ;\ + done + +clean-stir: + rm -rf ${STIR_FINAL_DEST_DIR} + +RESOURCES_ZIP = ${ASSETS_DIR}/fruitcake.zip +RESOURCES_TMP = ${TMPDIR}/resources + +install-resources: ${RESOURCES_ZIP} + mkdir -p ${RESOURCES_TMP} ${INCLUDE_DIR} && unzip -o ${RESOURCES_ZIP} -d ${RESOURCES_TMP} ;\ + declare -a resources=(fruitcake format_converters) ;\ + for rce in "$${resources[@]}"; do \ + include_path="${INCLUDE_DIR}/$${rce}" ;\ + tmp_path="${RESOURCES_TMP}/$${rce}" ;\ + if [ ! -d $${include_path} ]; then \ + mv $${tmp_path} ${INCLUDE_DIR} ;\ + chmod -R +x $${include_path} ;\ + else \ + echo "$${include_path} already exists, run make clean-resources in order to clean resources installation (fruitcake will be removed as well)." ;\ + fi ;\ + done ;\ + rm -rf ${RESOURCES_TMP} + +check-resources: + declare -a resources_paths=(${FRUITCAKE_PATH} ${FORMAT_CONVERTERS_PATH}) ;\ + for path in "$${resources_paths[@]}"; do \ + if [ ! -d "$${path}" ]; then \ + echo "$${path} does not exists, clean fruitcake and format_converters with clean-resources and run install-resources." ;\ + else \ + echo "$${path} exists." ;\ + fi ;\ + done + +clean-resources: + rm -rf ${INCLUDE_DIR}/fruitcake ${INCLUDE_DIR}/format_converters + +config-git: + git config --local filter.config-test.smudge ${ROOT_DIR}scripts/smudge-config_test.sh + git config --local filter.config-test.clean ${ROOT_DIR}scripts/clean-config_test.sh + chmod +x -R ${ROOT_DIR}scripts + +clean-git: + git config --local --unset filter.config-test.smudge + git config --local --unset filter.config-test.clean + +smudge-filter: + rm -f ${ROOT_DIR}configs/config.yaml 2> /dev/null + git checkout HEAD -- ${ROOT_DIR}configs/config.yaml + +FRUITCAKE_PATH = ${INCLUDE_DIR}/fruitcake +FRUITCAKE_BIN = ${FRUITCAKE_PATH}/bin +FRUITCAKE_LIB = ${FRUITCAKE_PATH}/book/lib +FORMAT_CONVERTERS_PATH = ${INCLUDE_DIR}/format_converters + +config-paths: + touch "$${HOME}"/.bashrc ;\ + declare -a simpet_paths=( \ + 'export PATH=${FRUITCAKE_BIN}:$$PATH' \ + 'export LD_LIBRARY_PATH=${FRUITCAKE_LIB}:$$LD_LIBRARY_PATH' \ + 'export PATH=${FORMAT_CONVERTERS_PATH}:$$PATH' \ + ) ;\ + for path in "$${simpet_paths[@]}"; do \ + grep -qxF "$${path}" "$${HOME}"/.bashrc || echo "$${path}" >> "$${HOME}"/.bashrc ;\ + done + +clean-paths: + touch "$${HOME}/.bashrc" + sed -i \ + -e '/export PATH=$(subst /,\/,${FRUITCAKE_BIN}):$$PATH/d' \ + -e '/export LD_LIBRARY_PATH=$(subst /,\/,${FRUITCAKE_LIB}):$$LD_LIBRARY_PATH/d' \ + -e '/export PATH=$(subst /,\/,${FORMAT_CONVERTERS_PATH}):$$PATH/d' \ + "$${HOME}/.bashrc" + +DATA_DIR = ${ROOT_DIR}Data +DATA_ZIP = ${ASSETS_DIR}/Data.zip + +dummy-data: ${DATA_ZIP} + if [ ! -d "${DATA_DIR}" ]; then \ + mkdir -p ${DATA_DIR} && unzip -o ${DATA_ZIP} -d ${ROOT_DIR} ;\ + else \ + echo "${DATA_DIR} already exists, remove it manually." ;\ + fi + +install: + ${MAKE} \ + install-simset \ + check-simset \ + install-stir \ + check-stir \ + install-resources \ + check-resources \ + config-paths \ + +clean: + ${MAKE} \ + clean-simset \ + clean-stir \ + clean-resources + +help: + @echo "Help:" + @echo " - install-simset: Install tweaked version of SimSET. + @echo "" + @echo " - install-canonic-simset: Install non-tweaked version of SimSET with STIR patch at directory ${SIMSET_DEST_DIR}." + @echo "" + @echo " - check-simset: Check that SimSET binaries exist." + @echo "" + @echo " - clean-simset: Clean SimSET installation (removes ${SIMSET_DEST_DIR} directory)." + @echo "" + @echo " - install-stir: Install STIR at directory $(shell dirname ${STIR_DIR}), install-simset is a prerequisite. Set NPROC=n to use n CPU cores in compilation." + @echo "" + @echo " - check-stir: Check that STIR binaries exist." + @echo "" + @echo " - clean-stir: Clean STIR installation removing ${STIR_INSTALL_DIR} and ${STIR_BUILD_DIR} directories." + @echo "" + @echo " - install-resources: Decompress ${RESOURCES_ZIP} and move fruitcake and format_converters to ${INCLUDE_DIR}" + @echo "" + @echo " - check-resources: Checks that fruitcake and format_converters are in ${INCLUDE_DIR}." + @echo "" + @echo " - clean-resources: Remove fruitcake and format_converters from ${INCLUDE_DIR}." + @echo "" + @echo " - config-git: Add project filter drivers to local git configuration and make them executable." + @echo "" + @echo " - clean-git: Remove project filter drivers from local git configuration." + @echo "" + @echo " - smudge-filer: Apply smudge filter to config file (updates paths)." + @echo "" + @echo " - config-paths: Iff not present in .bashrc, the paths of the projects will be appended to the file. If ~/.bashrc does not exists it will be created." + @echo "" + @echo " - clean-paths: If the paths of the project are present in .bashrc file, they will be deleted. If ~/.bashrc does not exists it will be created." + @echo "" + @echo " - dummy-data: Uncompress ${DATA_ZIP} at ${DATA_DIR} if ${DATA_DIR} does not exists." + @echo "" + @echo " - install: Run all install, config and check recipes." + @echo "" + @echo " - clean: Run all clean recipes." + From 1d55322249e88017ac3eed87f0705432d03e81e4 Mon Sep 17 00:00:00 2001 From: arnaufme8 <113554412+arnaufme8@users.noreply.github.com> Date: Fri, 17 Jul 2026 21:50:04 +0200 Subject: [PATCH 10/23] Update README.md Typos correction --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c465a7d5..7ae28793 100644 --- a/README.md +++ b/README.md @@ -316,11 +316,13 @@ See [SimSET](https://depts.washington.edu/simset/html/simset_main.html) document - Jesús Silva-Rodríguez - Pablo Aguiar -- Aida Ninyerola-Baizan +- Aida Niñerola-Baizán - Jeremiah Poveda - Francisco Javier López-González - Nikos Efthimiou -- Arnau Farre +- Arnau Farré-Melero +- Claudia Dominguez-Borrero + # References From cb7cab5e2e05299c77ace695d47612f0a54dad98 Mon Sep 17 00:00:00 2001 From: arnaufme8 <113554412+arnaufme8@users.noreply.github.com> Date: Fri, 17 Jul 2026 22:06:13 +0200 Subject: [PATCH 11/23] Add files via upload --- configs/params/scanner/discovery_st.yaml | 15 ++++---- configs/params/scanner/siemens_quadra.yaml | 41 +++++++++++----------- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/configs/params/scanner/discovery_st.yaml b/configs/params/scanner/discovery_st.yaml index 3b2c8b57..04e94a90 100644 --- a/configs/params/scanner/discovery_st.yaml +++ b/configs/params/scanner/discovery_st.yaml @@ -14,8 +14,8 @@ min_energy_window: 375 max_energy_window: 675 coincidence_window: 11.7 numberOfSubsets: 7 -numberOfIterations: 32 -savingInterval: 8 +numberOfIterations: 8 #32 +savingInterval: 8 #32 #8 analytical_att_correction: 0 stir_recons_att_corr: 1 analytic_scatt_corr_factor: 0.15 @@ -23,18 +23,19 @@ stir_scatt_corr_smoothing: 0 stir_scatt_simulation: 0 analytic_randoms_corr_factor: 0.2 stir_randoms_corr_smoothing: 0 -recons_type: "OSEM3D" +recons_type: "OSEM3D" #"FBP3D" #"OSEM3D" inter_iteration_filter: 0 subiteration_interval: 4 x_dir_filter_FWHM: 1.5 y_dir_filter_FWHM: 1.5 z_dir_filter_FWHM: 3 -psf_value: 0 +psf_value: 0 #REMEMBER TO CHANGE TO 0!!!! add_noise: 0 -max_segment: 23 -zoomFactor: 1.55 +max_segment: 23 #2 #23 +zoomFactor: 1 #1.55 xyOutputSize: 128 zOutputSize: 47 zOutputVoxelSize: 3.27 -stir_norm_from_att_map: 0 #1 for correcting with STIR, 0 for correcting with SimSET +#stir_norm_from_att_map: 1 #1 for correcting with STIR, 0 for correcting with SimSET. WARNING: Should both be unified? +attenuation_mode: 2 #0: no attenuation, 1: attenuation with SimSET, 2: attenuation with STIR diff --git a/configs/params/scanner/siemens_quadra.yaml b/configs/params/scanner/siemens_quadra.yaml index d525a889..87aa0953 100644 --- a/configs/params/scanner/siemens_quadra.yaml +++ b/configs/params/scanner/siemens_quadra.yaml @@ -1,42 +1,43 @@ scanner_name: "Siemens Quadra" -simset_material: 18 #29 = LYSO? 18 = LSO? -average_doi: 0.84 #MISSING +simset_material: 18 #29 = LYSO? 18 = LSO? NOTE: Consider adding param for gap material! +average_doi: 0.7 #ORIGINAL: 0.84 DOI FROM DISCOVERY, tried 0.1. scanner_radius: 41.0 -num_rings: 320 #320 -axial_fov: 106 #106 #15.7 +num_rings: 320 #real: 320, papers: 323 +axial_fov: 106 #NoGaps: 102.41 #REAL: 106 # WARNING: MUST BE CHANGED, ONLY FOR TESTING z_crystal_size: 0.32 transaxial_crystal_size: 0.32 -crystal_thickness: 2 -energy_resolution: 16 #CHANGE TO 9!!!!!!!! -num_aa_bins: 252 #440 #? 210, KEEP DISCOVERY #798/2 = 399 -num_td_bins: 344 #440 #? 249, KEEP DISCOVERY +crystal_thickness: 2 #2 # WARNING: CHANGE AGAIN TO 2 (REAL VALUE!). Tried 0.2 cm. +energy_resolution: 9 #OLD: 16 +num_aa_bins: 399 #380 #REAL: 399 (400 if FBP3D) #USED FOR LOWBIN: 252 #CORRECT: 399 #OLD: 252 #440 #? 210, KEEP DISCOVERY #798/2 = 399 +num_td_bins: 440 #440 #380 #REAL: 520 #used in paper: 440 #USED FOR LOWBIN: 344 #CORRECT: 440 #OLD: 344 #440 #? 249, KEEP DISCOVERY nsize = radius*2/nrad min_energy_window: 435 max_energy_window: 585 coincidence_window: 4.7 #ns -numberOfSubsets: 7 -numberOfIterations: 5 #32 -savingInterval: 5 #8 +numberOfSubsets: 7 #7 +numberOfIterations: 14 #25 #28 #28, 56 #14 #last: 102 #BEFORE: 8 #OLD: 5 #32 +savingInterval: 7 #5 #7 #RECOVER: 8 #OLD: 5 #8 #NOTE: MAKE TO CONVERT IT TO NII ASWELL! analytical_att_correction: 0 -stir_recons_att_corr: 1 -analytic_scatt_corr_factor: 0.15 +stir_recons_att_corr: 0 # WARNING: ORIGINALLY TO 1 TO DO ATT CORRECTION. +analytic_scatt_corr_factor: 0.15 # WARNING: ORIGINAL WAS 0.15. RECOVER. 0 for no scatter stir_scatt_corr_smoothing: 0 stir_scatt_simulation: 0 analytic_randoms_corr_factor: 0.2 stir_randoms_corr_smoothing: 0 -recons_type: "OSEM3D" +recons_type: "OSEM3D" #"FBP3D" #"OSEM3D" #try FBP2D... inter_iteration_filter: 0 subiteration_interval: 4 x_dir_filter_FWHM: 1.5 y_dir_filter_FWHM: 1.5 z_dir_filter_FWHM: 3 -psf_value: 0 +psf_value: 0 #WARNING: RETURN TO 0. IMPORTANT. add_noise: 0 -max_segment: 85 #319 #85 MAX: 40 -zoomFactor: 0.25 #1 -xyOutputSize: 110 #440 #220 #CHANGE IT TO DOUBLE OR BY FACTOR OR 3 +max_segment: 85 #last try: 40 #RETURN TO: 85 #OLD: 85 #FULL: 319 #FBP3D: 1, #oldFBP: 10 #85 #319 #85 MAX: 40 +zoomFactor: 1 #0.25 #1 +xyOutputSize: 220 #FOR SR: 300 #NORMAL: 220 #OLD: 440 #110 #440 #220 #CHANGE IT TO DOUBLE OR BY FACTOR OR 3 zOutputSize: 645 #645 #325 #CHANGE IT TO DOUBLE OR BY FACTOR OR 3 -zOutputVoxelSize: 6.6 #1.65 #3.3 +zOutputVoxelSize: 1.65 #6.6 #1.65 #3.3 -stir_norm_from_att_map: 1 +#stir_norm_from_att_map: 1 #1 for correcting with STIR, 0 for correcting with SimSET. WARNING: Should both be unified? +attenuation_mode: 2 #0: no attenuation, 1: attenuation with SimSET, 2: attenuation with STIR From 41597767f7e599f5d5dc96d01fed578f2990259a Mon Sep 17 00:00:00 2001 From: arnaufme8 <113554412+arnaufme8@users.noreply.github.com> Date: Fri, 17 Jul 2026 22:06:45 +0200 Subject: [PATCH 12/23] Add files via upload --- configs/params/test.yaml | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/configs/params/test.yaml b/configs/params/test.yaml index f47b6f29..fcf2fa72 100644 --- a/configs/params/test.yaml +++ b/configs/params/test.yaml @@ -1,22 +1,24 @@ defaults: - - scanner: discovery_st + - scanner: discovery_st #siemens_quadra #discovery_st simulation_environment: 0 sim_type: "SimSET" -do_simulation: 1 +do_simulation: 0 #IMPORTANT: REMEMBER TO 0 IF ONLY RECONS do_reconstruction: 1 -divisions: 2 +divisions: 28 #5 #1 model_type: "cylindrical" -patient_dirname: "test_image" -act_map: "act.hdr" -att_map: "att.hdr" -output_dir: "test_image" -center_slice: 7 -total_dose: 0.1 -simulation_time: 30 -sampling_photons: 0 +patient_dirname: "NEMA_Resolution_Phantoms_Discovery_3sources" #"NEMA_IEC_Body_Discovery" #"NEMA_Resolution_Phantoms_Discovery_3sources" #"NEMA_Resolution_Phantoms_Quadra_3sources" #"NEMA_IEC_Body_Quadra" #"test_brain_resampled" #"NEMA_IEC_Body_Quadra" #"NEMA_Resolution_Phantoms_Quadra_3sources" #"NEMA_Resolution_Phantoms_QuadraSTIR" #"NEMA_IEC_Body_QuadraSTIR" +act_map: "act.nii.gz" +att_map: "att.nii.gz" +output_dir: "NEMA_Resolution_Phantoms_Discovery_3sComparison" #"NEMA_IEC_Body_Discovery_sinos" #"NEMA_Resolution_Phantoms_Discovery_3sComparison" #"NEMA_Resolution_Phantoms_Quadra_3sComparison" #"NEMA_IEC_Body_Quadra_NoGap" #"test_brain_listmode7" #"test_brain4" #"NEMA_IEC_Body_Quadra_thincrystal" #"NEMA_Resolution_Phantoms_Quadra_3sources" #"NEMA_Resolution_Phantoms_QuadraSTIR_aluminium" #"NEMA_IEC_Body_QuadraSTIR" +center_slice: 0 +total_dose: 1.5 #1.5 #0.1 #0.75 #0.03 #0.01 #1 #1.5 #Sens: 0.1 #SR: 0.06 #0.3 #1 #0.1 #0.5 #1 #0.5 #1.5 #0.01 #1.5 #1.5 #0.1 #1.5 +simulation_time: 120 #120 #60 #90 #120 #20 #150 #600 #IQ long: 600 #Sens: 120 #SR: 60 #60 #30 #60 #20 #60 #1 #60 #60 #1 #60 #30 +sampling_photons: 2000000 #2000000 #100000 #1000 #2000000 #2000000 #1000000 #2000000 #2000000 #2000000 #1000000 photons: 0 -add_randoms: 1 -phglistmode: 0 -detlistmode: 1 +add_randoms: 0 #0 #1 + +listmode: True #ADDED. PROPOSAL: to unify both listmodes to only the necessary. +phglistmode: 1 #0 #ACTIVATE IF listmode. +detlistmode: 0 maximumIteration: 1 From a8d439a4f765c4c483de2f6b625a630e2c12e995 Mon Sep 17 00:00:00 2001 From: arnaufme8 <113554412+arnaufme8@users.noreply.github.com> Date: Fri, 17 Jul 2026 22:07:14 +0200 Subject: [PATCH 13/23] Add files via upload --- src/simset/simset_sim.py | 599 +++++++++++++++++-------------------- src/simset/simset_tools.py | 77 +++-- 2 files changed, 327 insertions(+), 349 deletions(-) diff --git a/src/simset/simset_sim.py b/src/simset/simset_sim.py index 39ddac19..d6210461 100644 --- a/src/simset/simset_sim.py +++ b/src/simset/simset_sim.py @@ -81,6 +81,8 @@ def __init__( self.detlistmode = params.get("detlistmode") self.phglistmode = params.get("phglistmode") self.add_randoms = params.get("add_randoms") + + self.list_mode = params.get("listmode") def run(self): processes = [] @@ -194,6 +196,7 @@ def run_simset_simulation(self, sim_dir): rec_weight = join(sim_dir, "rec.weight") det_hf = join(sim_dir, "det_hf.hist") phg_hf = join(sim_dir, "phg_hf.hist") + if self.s_photons != 0 and self.params.get("add_randoms") != 1: @@ -201,7 +204,8 @@ def run_simset_simulation(self, sim_dir): if self.photons == 0: # Removes counts for preparing for the next simulation - os.remove(rec_weight) + if exists(rec_weight): + os.remove(rec_weight) if exists(det_hf): os.remove(det_hf) if exists(phg_hf): @@ -224,6 +228,7 @@ def run_simset_simulation(self, sim_dir): command = "%s/bin/phg %s > %s" % (self.simset_dir, my_phg, my_log) tools.osrun(command, log_file) + w_quotient = read_ws_from_simset_log(my_log) sim_photons = int(self.s_photons * w_quotient) @@ -233,7 +238,8 @@ def run_simset_simulation(self, sim_dir): sim_photons = self.photons # Removes counts for preparing for the next simulation - os.remove(rec_weight) + if exists(rec_weight): + os.remove(rec_weight) if exists(det_hf): os.remove(det_hf) if exists(phg_hf): @@ -249,10 +255,12 @@ def run_simset_simulation(self, sim_dir): command = "%s/bin/phg %s > %s" % (self.simset_dir, my_phg, my_log) tools.osrun(command, log_file) + if self.add_randoms == 1: coincidence_window = self.scanner.get("coincidence_window") - + + #WARNING: TESTING IF THIS IS NOT NEEDED. DE-COMMENT IF FAILS: simset_tools.add_randoms( sim_dir, self.simset_dir, @@ -260,10 +268,12 @@ def run_simset_simulation(self, sim_dir): rebin=True, log_file=log_file, ) - - simset_tools.process_weights( - rec_weight, sim_dir, self.scanner, self.add_randoms - ) + + if not self.list_mode: + + simset_tools.process_weights( + rec_weight, sim_dir, self.scanner, self.add_randoms + ) print("Finished simulation for %s" % os.path.basename(sim_dir)) @@ -275,6 +285,7 @@ def prepare_simset_files( model_type = self.params.get("model_type") scanner_radius = self.scanner.get("scanner_radius") scanner_axial_fov = self.scanner.get("axial_fov") + list_mode = self.params.get("listmode") # We activate det_listmode if demanded by user or if add_randoms is on if self.add_randoms == 1: @@ -329,6 +340,7 @@ def prepare_simset_files( self.scanner, add_randoms, log_file=log_file, + list_mode=list_mode ) simset_tools.make_index_file(sim_dir, self.simset_dir, log_file=log_file) @@ -338,41 +350,94 @@ def prepare_simset_files( def simulation_postprocessing(self): print("Postprocessing simulation...") print(" ") - # All the parallel simulations are combined in division 0 + + + # All the parallel simulations are combined in division 0. NOTE: CHANGE TO simulation folder at some point. log_file = join(self.output_dir, "postprocessing.log") division_zero = join(self.output_dir, "division_0") - - for image in ["trues", "scatter", "randoms"]: - #zero_image = join(division_zero, image + ".hdr") - zero_image = join(division_zero, image + ".nii") - #zero_image = join(division_zero, image + ".nii.gz") - - if exists(zero_image): - print("Adding sinograms for %s" % image) + + #ATTENTION: We need to think a better place to do this. At the moment, we keep it here. + #This is generating rec.weight files when listmoding. NOTE: WE WILL USE phg_hf.hist at the moment. Ignoring det.hist. + + if self.list_mode: + + zero_hist = join(division_zero, "phg_hf.hist") + full_hist = join(division_zero, "full_phg_hf.hist") + rec_weight = join(division_zero, "rec.weight") + my_phg = join(division_zero, "phg.rec") + file_list = zero_hist + + print("Adding History Files for PHG") + + message = "Adding History Files for PHG" + tools.log_message(log_file, message) + + for division in range(1, self.divisions): - #This is to create the postlog file in case there is only one simulation, to avoid double attenuation computing! - message = "Adding sinograms for %s" % image - tools.log_message(log_file, message) + division_dir = join(self.output_dir, "division_" + str(division)) + division_hist = join(division_dir, "phg_hf.hist") + #file_list = zero_hist + " " + division_hist + file_list = file_list + " " + division_hist + + if self.divisions > 1: + simset_tools.combine_history_files( + self.simset_dir, file_list, full_hist, log_file + ) + #shutil.move(output, zero_hist) - for division in range(1, self.divisions): - division_dir = join(self.output_dir, "division_" + str(division)) - #division_image = join(division_dir, image + ".hdr") - division_image = join(division_dir, image + ".nii") - #division_image = join(division_dir, image + ".nii.gz") - message = "Adding %s from simulation %s" % (image, division) + os.remove(zero_hist) + os.rename(full_hist, zero_hist) + + #ATTENTION: THIS IS ADDED ONLY FOR LIST MODE. MAY BECOME UNUSED OR PROBLEMATIC. + + with open(join(division_zero, "bin.rec"), "a") as f: + f.write('STR weight_image_path = "' + rec_weight + '"\n') + f.close() + + command = "%s/bin/bin -p %s" % (self.simset_dir, my_phg) + tools.osrun(command, log_file) + + + + simset_tools.process_weights( + rec_weight, division_zero, self.scanner, self.add_randoms + ) + + + + else: + + for image in ["trues", "scatter", "randoms"]: #TODO: randoms needed here? + #zero_image = join(division_zero, image + ".hdr") + zero_image = join(division_zero, image + ".nii") + #zero_image = join(division_zero, image + ".nii.gz") + + if exists(zero_image): + print("Adding sinograms for %s" % image) + + #This is to create the postlog file in case there is only one simulation, to avoid double attenuation computing! + message = "Adding sinograms for %s" % image tools.log_message(log_file, message) - #tools.operate_images_nii( #RESTABLISH IF FAILS [NOT COMPUTING NANS!] - tools.operate_sinograms_nii( - zero_image, division_image, zero_image, "sum" - ) - #os.remove(division_image) - #os.remove(division_image[0:-3] + "img") - os.remove(division_image[0:-3] + "nii") - #os.remove(division_image[0:-6] + "nii.gz") - + for division in range(1, self.divisions): + division_dir = join(self.output_dir, "division_" + str(division)) + division_image = join(division_dir, image + ".nii") + message = "Adding %s from simulation %s" % (image, division) + tools.log_message(log_file, message) + + tools.operate_sinograms_nii( + #tools.operate_images_nii( + zero_image, division_image, zero_image, "sum" + ) + + #os.remove(division_image[0:-3] + "nii") #NOTE: Not needed as full division folder will be removed + + #TODO: ADDED TO OPTIMIZE: + #histtypes_list = [] + # if detlistmode == 1... if phglistmode == 1... and remove divisions apart. + """ #This has become obsolete. for hist in ["phg_hf.hist", "det_hf.hist"]: zero_hist = join(division_zero, hist) @@ -387,45 +452,58 @@ def simulation_postprocessing(self): division_dir = join(self.output_dir, "division_" + str(division)) division_hist = join(division_dir, hist) file_list = zero_hist + " " + division_hist + + #WARNING: AT THE MOMENT THIS IS NOT NEEDED. RECOVER WHEN DETLISTMODE IS USED. #simset_tools.combine_history_files( # self.simset_dir, file_list, output, log_file - #) #IMPORTANT! RESTABLISH IF FAILS! - # shutil.move(output, zero_hist) - # Once everything is combined in division_0, remove the other division - shutil.rmtree(division_dir) - - os.remove(join(division_zero, "det_hf.hist")) #IMPORTANT! REMOVE IF FAILS. + #) + #shutil.move(output, zero_hist) + #shutil.rmtree(division_dir) # Once everything is combined in division_0, remove the other division + + #os.remove(join(division_zero, "det_hf.hist")) #IMPORTANT! REMOVE IF FAILS. + """ + + #Remove unused divisions... + for division in range(1, self.divisions): + division_dir = join(self.output_dir, "division_" + str(division)) + shutil.rmtree(division_dir) # Once everything is combined in division_0, remove the other division + - if self.add_randoms == 1: + if self.add_randoms == 1: #TODO: THIS HAS TO BE TESTED IN NEW SETUP. # To have randoms in the final history file, we need to add randoms to the final det_hf.hist print("Adding randoms to the history file...") coincidence_window = self.scanner.get("coincidence_window") - - simset_tools.add_randoms( - division_zero, - self.simset_dir, - coincidence_window, - rebin=False, - log_file=log_file, - ) + + #NOTE: I THINK THIS PROCESS SHOULD BE DONE BEFORE REMOVING EACH DIVISION. THAT IS: EXTRACTING AND COMBINING FILES FOR EACH DIVISION. CHECK IF WORKS! + #WARNING: AT THE MOMENT THIS IS NOT NEEDED. RECOVER WHEN DETLISTMODE IS USED. + #simset_tools.add_randoms( + # division_zero, + # self.simset_dir, + # coincidence_window, + # rebin=False, + # log_file=log_file, + #) # os.remove(join(division_zero, "sorted_det_hf.hist")) + + #WARNING: AT THE MOMENT THIS IS NOT NEEDED. RECOVER WHEN DETLISTMODE IS USED. + #det_hist = join(division_zero, "det_hf.hist") + #randoms_hist = join(division_zero, "randoms.hist") + #output = join(division_zero, "full_det_hf.hist") - det_hist = join(division_zero, "det_hf.hist") - randoms_hist = join(division_zero, "randoms.hist") - output = join(division_zero, "full_det_hf.hist") - - file_list = det_hist + " " + randoms_hist + #file_list = det_hist + " " + randoms_hist - simset_tools.combine_history_files( - self.simset_dir, file_list, output, log_file - ) - - self.stir_norm_from_att_map = self.scanner.get("stir_norm_from_att_map") + #simset_tools.combine_history_files( + # self.simset_dir, file_list, output, log_file + #) + + + #self.stir_norm_from_att_map = self.scanner.get("stir_norm_from_att_map") + self.attenuation_mode = self.scanner.get("attenuation_mode") - if self.stir_norm_from_att_map != 1: + if self.attenuation_mode == 1: #self.stir_norm_from_att_map != 1: print("Calculating attenuation map...") print(" ") @@ -440,8 +518,26 @@ def simulation_postprocessing(self): self.simset_dir, division_zero, output_atten, hdr_to_copy, nrays=1, timeout=None ) - #To remove unnecessary files once endeded the sim. Add condition: If exists. - #Also: Change directory name to "simuulation". IMPORTANT! REMOVE IF FAILS: + #TODO: To remove unnecessary files once endeded the sim. Add condition: If exists. + #TODO: Change directory name to "simuulation". IMPORTANT! REMOVE IF FAILS: + + """ + if exists(join(division_zero, "rec.weight")): + os.remove(join(division_zero, "rec.weight")) + + if exists(join(division_zero, "rec.act_indexes")): + os.remove(join(division_zero, "rec.act_indexes")) + + if exists(join(division_zero, "rec.att_indexes")): + os.remove(join(division_zero, "rec.att_indexes")) + + if exists(join(division_zero, "rec.activity_image")): + os.remove(join(division_zero, "rec.activity_image")) + + if exists(join(division_zero, "rec.attenuation_image")): + os.remove(join(division_zero, "rec.attenuation_image")) + """ + #os.remove(join(division_zero, "rec.weight")) #os.remove(join(division_zero, "rec.act_indexes")) #os.remove(join(division_zero, "rec.activity_image")) @@ -497,44 +593,29 @@ def prepare_recons(self): print("Preparing files for reconstruction") - self.stir_norm_from_att_map = self.scanner.get("stir_norm_from_att_map") + self.simset_dir = self.config.get("dir_simset") + self.stir_dir = self.config.get("dir_stir") + num_rings = self.scanner.get("num_rings") + max_segment = self.scanner.get("max_segment") - #RESTABLISH IF FAILS: - #trues_sino = join(self.input_dir, "trues.hdr") - #scatter_sino = join(self.input_dir, "scatter.hdr") - #randoms_sino = join(self.input_dir, "randoms.hdr") - - #corr_scatter_sino = join(self.input_dir, "corr_scatter.hdr") - #corr_randoms_sino = join(self.input_dir, "corr_randoms.hdr") - #my_simset_sino = join(self.input_dir, "my_sinogram.hdr") - #additive_sinogram = join(self.input_dir, "additive_sinogram.hdr") - #att_sino = join(self.input_dir, "attenuationsino.hdr") + #self.stir_norm_from_att_map = self.scanner.get("stir_norm_from_att_map") + self.attenuation_mode = self.scanner.get("attenuation_mode") - #trues_sino = join(self.input_dir, "trues.nii") - #scatter_sino = join(self.input_dir, "scatter.nii") - #randoms_sino = join(self.input_dir, "randoms.nii") - - #corr_scatter_sino = join(self.input_dir, "corr_scatter.nii") - #corr_randoms_sino = join(self.input_dir, "corr_randoms.nii") - #my_simset_sino = join(self.input_dir, "my_sinogram.nii") - #additive_sinogram = join(self.input_dir, "additive_sinogram.nii") - #att_sino = join(self.input_dir, "attenuationsino.nii") + #if ((self.stir_norm_from_att_map != 1) and (not exists(join(self.input_dir, "attenuationsino.nii")))): + if ((self.attenuation_mode == 1) and (not exists(join(self.input_dir, "attenuationsino.nii")))): - """ - trues_sino = join(self.input_dir, "trues.nii.gz") - scatter_sino = join(self.input_dir, "scatter.nii.gz") - randoms_sino = join(self.input_dir, "randoms.nii.gz") - - corr_scatter_sino = join(self.input_dir, "corr_scatter.nii.gz") - corr_randoms_sino = join(self.input_dir, "corr_randoms.nii.gz") - my_simset_sino = join(self.input_dir, "my_sinogram.nii.gz") - additive_sinogram = join(self.input_dir, "additive_sinogram.nii.gz") - att_sino = join(self.input_dir, "attenuationsino.nii.gz") - """ + print("Attenuation map was not computed: Calculating attenuation map...") + print(" ") + + output_atten = "attenuationsino" + hdr_to_copy = join("trues.nii") + + simset_tools.simset_calcattenuation( + self.simset_dir, self.input_dir, output_atten, hdr_to_copy, nrays=1, timeout=None + ) - #Delete: trues_sino = join(self.input_dir, "trues.nii") scatter_sino = join(self.input_dir, "scatter.nii") randoms_sino = join(self.input_dir, "randoms.nii") @@ -545,9 +626,6 @@ def prepare_recons(self): additive_sinogram = join(self.input_dir, "additive_sinogram.nii") att_sino = join(self.input_dir, "attenuationsino.nii") - #RESTABLISH IF FAILS: - #tools.operate_single_image( - print("MULTIPLYING CORR_SCATTER_SINO") if not exists(corr_scatter_sino): @@ -562,142 +640,45 @@ def prepare_recons(self): print("SUMMING TRUES AND CORR SCATTER") - #RESTABLISH IF FAILS: - #tools.operate_images_analyze( if not exists(my_simset_sino): tools.operate_images_nii( trues_sino, corr_scatter_sino, my_simset_sino, operation="sum", check_nans = False, ) if self.add_randoms == 1: - tools.operate_single_image( + #tools.operate_single_image( + tools.operate_single_image_nii( randoms_sino, "mult", self.random_corr_factor, corr_randoms_sino, self.log_file, + #check_nans = False ) - tools.operate_images_analyze( - my_simset_sino, corr_randoms_sino, my_simset_sino, operation="sum" + #tools.operate_images_analyze( + tools.operate_sinograms_nii( + my_simset_sino, corr_randoms_sino, my_simset_sino, operation="sum", #check_nans = False, ) if self.scanner.get("stir_randoms_corr_smoothing") == 1: - tools.operate_images_analyze( - scatter_sino, randoms_sino, additive_sinogram, operation="sum" + #tools.operate_images_analyze( + tools.operate_sinograms_nii( + scatter_sino, randoms_sino, additive_sinogram, operation="sum", #check_nans = False, ) else: - #RESTABLISH IF FAILS - #tools.copy_analyze(scatter_sino, additive_sinogram) tools.copy_nifti(scatter_sino, additive_sinogram) else: - #RESTABLISH IF FAILS - #tools.copy_analyze(scatter_sino, additive_sinogram) - - print("COPYING ADDITIVE SINO") - if not exists(additive_sinogram): - tools.copy_nifti(scatter_sino, additive_sinogram) - print("SMOOTHING ADDITIVE SINO") - tools.smooth_nifti(additive_sinogram, 10, additive_sinogram) - - #RESTABLISH IF FAILS - #tools.smooth_analyze(additive_sinogram, 10, additive_sinogram) - - #if not exists(additive_sinogram): - # tools.smooth_nifti(additive_sinogram, 10, additive_sinogram) - - - #RESTABLISH IF FAILS: - """ - sinogram_stir = join(self.output_dir, "stir_sinogram.hdr") - tools.convert_simset_sino_to_stir(my_simset_sino, sinogram_stir) - shutil.copy(sinogram_stir[0:-3] + "bin", sinogram_stir[0:-3] + "s") #PREVIOUS: img - stir_tools.create_stir_hs_from_detparams( - self.scanner, sinogram_stir[0:-3] + "hs" - ) - - additive_sino_stir = join(self.output_dir, "stir_additivesino.hdr") - tools.convert_simset_sino_to_stir(additive_sinogram, additive_sino_stir) - shutil.copy(additive_sino_stir[0:-3] + "img", additive_sino_stir[0:-3] + "s") #PREVIOUS: img - stir_tools.create_stir_hs_from_detparams( - self.scanner, additive_sino_stir[0:-3] + "hs" - ) - - #att_sino = join(self.input_dir, "attenuationsino.hdr") - #att_sino = join(self.input_dir, "attenuationsino.nii") - att_stir = join(self.output_dir, "stir_att.hdr") - tools.convert_simset_sino_to_stir(att_sino, att_stir) - shutil.copy(att_stir[0:-3] + "bin", att_stir[0:-3] + "s") #PREVIOUS: img - stir_tools.create_stir_hs_from_detparams(self.scanner, att_stir[0:-3] + "hs") - """ - - ##### THIS IS FOR .nii: - #sinogram_stir = join(self.output_dir, "stir_sinogram.nii") - #tools.convert_simset_sino_to_stir_nii(my_simset_sino, sinogram_stir) - #shutil.copy(sinogram_stir[0:-3] + "img", sinogram_stir[0:-3] + "s") - #stir_tools.create_stir_hs_from_detparams( - # self.scanner, sinogram_stir[0:-3] + "hs" - #) - #os.remove(sinogram_stir[0:-3] + "img") - #os.remove(sinogram_stir[0:-3] + "hdr") - - #additive_sino_stir = join(self.output_dir, "stir_additivesino.nii") - #tools.convert_simset_sino_to_stir_nii(additive_sinogram, additive_sino_stir) - #shutil.copy(additive_sino_stir[0:-3] + "img", additive_sino_stir[0:-3] + "s") - #stir_tools.create_stir_hs_from_detparams( - # self.scanner, additive_sino_stir[0:-3] + "hs" - #) - #os.remove(additive_sino_stir[0:-3] + "img") - #os.remove(additive_sino_stir[0:-3] + "hdr") - - #att_sino = join(self.input_dir, "attenuationsino.nii") - #att_stir = join(self.output_dir, "stir_att.nii") - #tools.convert_simset_sino_to_stir_nii(att_sino, att_stir) - #shutil.copy(att_stir[0:-3] + "img", att_stir[0:-3] + "s") - #stir_tools.create_stir_hs_from_detparams( - # self.scanner, att_stir[0:-3] + "hs" - #) - #os.remove(att_stir[0:-3] + "img") - #os.remove(att_stir[0:-3] + "hdr") - - - ##### THIS IS FOR .nii.gz: - #sinogram_stir = join(self.output_dir, "stir_sinogram.nii.gz") - #tools.convert_simset_sino_to_stir_nii(my_simset_sino, sinogram_stir) - #shutil.copy(sinogram_stir[0:-6] + "img", sinogram_stir[0:-6] + "s") - #stir_tools.create_stir_hs_from_detparams( - # self.scanner, sinogram_stir[0:-6] + "hs" - #) - #os.remove(sinogram_stir[0:-6] + "img") - #os.remove(sinogram_stir[0:-6] + "hdr") - - #additive_sino_stir = join(self.output_dir, "stir_additivesino.nii.gz") - #tools.convert_simset_sino_to_stir_nii(additive_sinogram, additive_sino_stir) - #shutil.copy(additive_sino_stir[0:-6] + "img", additive_sino_stir[0:-6] + "s") - #stir_tools.create_stir_hs_from_detparams( - # self.scanner, additive_sino_stir[0:-6] + "hs" - #) - #os.remove(additive_sino_stir[0:-6] + "img") - #os.remove(additive_sino_stir[0:-6] + "hdr") - - #att_stir = join(self.output_dir, "stir_att.nii.gz") - #tools.convert_simset_sino_to_stir_nii(att_sino, att_stir) - #shutil.copy(att_stir[0:-6] + "img", att_stir[0:-6] + "s") - #stir_tools.create_stir_hs_from_detparams( - # self.scanner, att_stir[0:-6] + "hs" - #) - #os.remove(att_stir[0:-6] + "img") - #os.remove(att_stir[0:-6] + "hdr") - - - ##### THIS IS FOR .nii.gz [NEW VERSION (FILE REOIRGANIZATION): - - num_rings = self.scanner.get("num_rings") - max_segment = self.scanner.get("max_segment") + if self.scanner.get("stir_scatt_corr_smoothing") == 1: + print("COPYING ADDITIVE SINO") #TODO: CHECK IF NECESSARY IN FUNCTION OF stir_scatt_corr_smoothing: + if not exists(additive_sinogram): + tools.copy_nifti(scatter_sino, additive_sinogram) + print("SMOOTHING ADDITIVE SINO") + tools.smooth_nifti(additive_sinogram, 10, additive_sinogram) + print("GENERATING STIR SINOGRAM") - #sinogram_stir_nii = join(self.input_dir, "stir_sinogram.nii.gz") sinogram_stir_nii = join(self.input_dir, "stir_sinogram.nii") sinogram_stir_s = join(self.output_dir, "stir_sinogram.s") sinogram_stir_hs = join(self.output_dir, "stir_sinogram.hs") @@ -705,139 +686,120 @@ def prepare_recons(self): if not exists(sinogram_stir_nii): tools.convert_simset_sino_to_stir_nii(my_simset_sino, sinogram_stir_nii) - tools.copy_sinogram_stir_to_output(sinogram_stir_nii, sinogram_stir_s) + tools.copy_sinogram_stir_to_output(sinogram_stir_nii, sinogram_stir_s) #WARNING! GET BACK IF NEEDED. #tools.copy_reduced_sinogram_stir_to_output(sinogram_stir_nii, sinogram_stir_s, num_rings, max_segment) stir_tools.create_stir_hs_from_detparams( - #stir_tools.create_reduced_stir_hs_from_detparams( self.scanner, sinogram_stir_hs ) - print("GENERATING STIR ADDITIVE") - #additivesino_stir_nii = join(self.input_dir, "stir_additivesino.nii.gz") - additivesino_stir_nii = join(self.input_dir, "stir_additivesino.nii") - additivesino_stir_s = join(self.output_dir, "stir_additivesino.s") - additivesino_stir_hs = join(self.output_dir, "stir_additivesino.hs") + if self.scanner.get("stir_scatt_corr_smoothing") == 1: + + print("GENERATING STIR ADDITIVE") #TODO: CHECK IF NECESSARY IN FUNCTION OF stir_scatt_corr_smoothing: - if not exists(additivesino_stir_nii): - tools.convert_simset_sino_to_stir_nii(additive_sinogram, additivesino_stir_nii) + additivesino_stir_nii = join(self.input_dir, "stir_additivesino.nii") + additivesino_stir_s = join(self.output_dir, "stir_additivesino.s") + additivesino_stir_hs = join(self.output_dir, "stir_additivesino.hs") + + if not exists(additivesino_stir_nii): + tools.convert_simset_sino_to_stir_nii(additive_sinogram, additivesino_stir_nii) - tools.copy_sinogram_stir_to_output(additivesino_stir_nii, additivesino_stir_s) - #tools.copy_reduced_sinogram_stir_to_output(additivesino_stir_nii, additivesino_stir_s, num_rings, max_segment) + tools.copy_sinogram_stir_to_output(additivesino_stir_nii, additivesino_stir_s) #WARNING! GET BACK IF NEEDED. + #tools.copy_reduced_sinogram_stir_to_output(additivesino_stir_nii, additivesino_stir_s, num_rings, max_segment) - stir_tools.create_stir_hs_from_detparams( - #stir_tools.create_reduced_stir_hs_from_detparams( - self.scanner, additivesino_stir_hs - ) + stir_tools.create_stir_hs_from_detparams( + self.scanner, additivesino_stir_hs + ) print("GENERATING STIR ATT") + att_stir_nii = join(self.input_dir, "stir_att.nii") + att_stir_s = join(self.output_dir, "stir_att.s") + att_stir_hs = join(self.output_dir, "stir_att.hs") - if self.stir_norm_from_att_map != 1: - - #att_stir_nii = join(self.input_dir, "stir_att.nii.gz") - att_stir_nii = join(self.input_dir, "stir_att.nii") - att_stir_s = join(self.output_dir, "stir_att.s") - att_stir_hs = join(self.output_dir, "stir_att.hs") - + #if self.stir_norm_from_att_map != 1: #TODO: REFINE THIS. + + if self.attenuation_mode == 1: if not exists(att_stir_nii): tools.convert_simset_sino_to_stir_nii(att_sino, att_stir_nii) - tools.copy_sinogram_stir_to_output(att_stir_nii, att_stir_s) + tools.copy_sinogram_stir_to_output(att_stir_nii, att_stir_s) #WARNING! GET BACK IF NEEDED. #tools.copy_reduced_sinogram_stir_to_output(att_stir_nii, att_stir_s, num_rings, max_segment) stir_tools.create_stir_hs_from_detparams( - #stir_tools.create_reduced_stir_hs_from_detparams( self.scanner, att_stir_hs ) - """ #RESTABLISH IF FAILS: - if self.stir_norm_from_att_map == 1: - attmap = nib.load(join('Data', self.params.get("patient_dirname"), self.params.get("act_map"))) - - attmap_dim = np.shape(attmap) - attmap_pixsize = attmap.header['pixdim'][1:4] + #OBTAIN MUMAP DIRECTLY FROM ATTMAP: + #if self.stir_norm_from_att_map == 1: + if self.attenuation_mode == 2: + #if exists(att_stir_nii): #inactive at the moment but this is very time consuming... - filedims = [attmap_dim[2], attmap_dim[0], attmap_dim[1]] #[618, 440, 440] - dtype = np.uint32 #np.float32 # or np.uint8, np.float32, etc. - - with open(join(self.input_dir, "rec.att_indexes"), "rb") as f: - data = np.frombuffer(f.read(), dtype=dtype) - - att_indexes = data.reshape(filedims) + if not exists(att_stir_nii): #Added to save time... Remove if does not work. - mu_map = np.zeros(np.shape(att_indexes)) + nib.save(nib.load(join(self.input_dir, 'stir_sinogram.nii')), join(self.input_dir, 'stir_sinogram.hdr')) #DELETE, this is only for testing... + + attmap = nib.load(join('Data', self.params.get("patient_dirname"), self.params.get("att_map"))) + att_indexes = np.uint8(attmap.get_fdata()) + + attmap_dim = np.shape(attmap) + attmap_pixsize = attmap.header['pixdim'][1:4] + + mu_map = np.zeros(np.shape(att_indexes), dtype=np.float32) - for attidx in np.unique(att_indexes): - mu_map[att_indexes == attidx] = tools.mu_coef_511keV(attidx) + for attidx in np.unique(att_indexes): + mu_map[att_indexes == attidx] = tools.mu_coef_511keV(attidx) - - mu_map_img = nib.Nifti2Image(mu_map, np.eye(4)) - nib.save(mu_map_img, join(self.input_dir, 'mu_map.hdr')) - - shutil.copy(join(self.input_dir, 'mu_map.img'), join(self.output_dir, 'mu_map.v')) - tools.write_interfile_header(join(self.output_dir, 'mu_map.hv'), attmap_dim[2], attmap_pixsize[2], - attmap_dim[0], attmap_pixsize[0], - attmap_dim[1], attmap_pixsize[1]) - - """ - - - if self.stir_norm_from_att_map == 1: - - attmap = nib.load(join('Data', self.params.get("patient_dirname"), self.params.get("att_map"))) - - attmap_data = (attmap.get_fdata()).astype(np.uint8) + mu_map_img = nib.Nifti1Image(mu_map, attmap.affine) + nib.save(mu_map_img, join(self.input_dir, 'mu_map.hdr')) + nib.save(mu_map_img, join(self.input_dir, 'mu_map.nii')) + + shutil.copy(join(self.input_dir, 'mu_map.img'), join(self.output_dir, 'mu_map.v')) + tools.write_interfile_header_mu(join(self.output_dir, 'mu_map.hv'), attmap_dim[0], attmap_pixsize[0], + attmap_dim[1], attmap_pixsize[1], + attmap_dim[2], attmap_pixsize[2]) #offset_z = 0) #offset_z = None) #offset_z = -0.936025*128) + + + tools.write_fwdproj_parfile(join(self.output_dir, "fwdproj_par.par")) + + #This works but removed for logging. + #ACF_command = "%s --ACF %s %s %s %s" % (join(self.stir_dir, "bin", "calculate_attenuation_coefficients"), join(self.output_dir, "stir_att.hs"), join(self.output_dir, 'mu_map.hv'), sinogram_stir_hs, join(self.output_dir, "fwdproj_par.par")) + #TODO: ADD LOGGING FOR ERROR MESSAGES AS WELL AS PRINTING TO TERMINAL, JUST LIKE HERE: + ACF_command = "%s --ACF %s %s %s %s 2>&1 | tee %s" % (join(self.stir_dir, "bin", "calculate_attenuation_coefficients"), join(self.output_dir, "stir_att.hs"), join(self.output_dir, 'mu_map.hv'), sinogram_stir_hs, join(self.output_dir, "fwdproj_par.par"), join(self.output_dir, "att_logging.log")) + + #TODO: Still have to think about this... Way to save the nifti to the simulation folder. + os.system(ACF_command) + + #Added to save time and keep the stir_att... Remove if does not work. + shutil.copyfile(join(self.output_dir, "stir_att.s"), join(self.input_dir, "stir_att.img")) + + os.rename(join(self.input_dir, 'stir_sinogram.hdr'), join(self.input_dir, 'stir_att.hdr')) + + stiratt_img = nib.load(join(self.input_dir, "stir_att.img")) + stiratt_data = stiratt_img.dataobj + + stiratt_nifti2 = nib.Nifti2Image(stiratt_data, stiratt_img.affine, stiratt_img.header) + nib.save(stiratt_nifti2, join(self.input_dir, "stir_att.nii")) + + #Remove not needed files anymore... + os.remove(join(self.input_dir, 'mu_map.hdr')) + os.remove(join(self.input_dir, 'mu_map.img')) + os.remove(join(self.input_dir, 'stir_att.hdr')) + os.remove(join(self.input_dir, 'stir_att.img')) + os.remove(join(self.input_dir, 'stir_sinogram.img')) + + else: + + tools.copy_sinogram_stir_to_output(att_stir_nii, att_stir_s) #WARNING! GET BACK IF NEEDED. - attmap_dim = np.shape(attmap) - attmap_pixsize = attmap.header['pixdim'][1:4] - - mu_map = np.zeros(attmap_dim) - - for attidx in np.unique(attmap_data): - mu_map[attmap_data == attidx] = tools.mu_coef_511keV(attidx) - - mu_map_img = nib.Nifti1Image(mu_map, np.eye(4)) - nib.save(mu_map_img, join(self.input_dir, 'mu_map.hdr')) - - shutil.copy(join(self.input_dir, 'mu_map.img'), join(self.output_dir, 'mu_map.v')) - tools.write_interfile_header(join(self.output_dir, 'mu_map.hv'), attmap_dim[0], attmap_pixsize[0], - attmap_dim[1], attmap_pixsize[1], - attmap_dim[2], attmap_pixsize[2]) - - - + stir_tools.create_stir_hs_from_detparams( + self.scanner, att_stir_hs, output_format = "STIR" + ) + + - ######THIS WAS FOR .nii.gz [NOT WORKING AT THE MOMENT]: - """ - sinogram_stir = join(self.output_dir, "stir_sinogram.nii.gz") - tools.convert_simset_sino_to_stir_nii(my_simset_sino, sinogram_stir) - nib.save(nib.load(sinogram_stir), join(self.output_dir, "stir_sinogram.nii")) - shutil.copy(sinogram_stir[0:-6] + "nii", sinogram_stir[0:-6] + "s") - stir_tools.create_stir_hs_from_detparams( - self.scanner, sinogram_stir[0:-6] + "hs" - ) - os.remove(join(self.output_dir, "stir_sinogram.nii")) - - additive_sino_stir = join(self.output_dir, "stir_additivesino.nii.gz") - tools.convert_simset_sino_to_stir_nii(additive_sinogram, additive_sino_stir) - nib.save(nib.load(additive_sino_stir), join(self.output_dir, "stir_additivesino.nii")) - shutil.copy(additive_sino_stir[0:-6] + "nii", additive_sino_stir[0:-6] + "s") - stir_tools.create_stir_hs_from_detparams( - self.scanner, additive_sino_stir[0:-6] + "hs" - ) - os.remove(join(self.output_dir, "stir_additivesino.nii")) - - att_sino = join(self.input_dir, "attenuationsino.nii.gz") - att_stir = join(self.output_dir, "stir_att.nii.gz") - tools.convert_simset_sino_to_stir_nii(att_sino, att_stir) - nib.save(nib.load(att_stir), join(self.output_dir, "stir_att.nii")) - shutil.copy(att_stir[0:-6] + "nii", att_stir[0:-6] + "s") - stir_tools.create_stir_hs_from_detparams(self.scanner, att_stir[0:-6] + "hs") - os.remove(join(self.output_dir, "stir_att.nii")) - """ #EDIT IN FUTURE: @@ -855,7 +817,9 @@ def prepare_recons(self): shutil.copy(catt_add_sino[0:-3] + "img", additive_sino_stir[0:-3] + "s") if self.scanner.get("psf_value") != 0: - stir_tools.apply_psf(self.scanner, sinogram_stir, self.log_file) + #stir_tools.apply_psf(self.scanner, sinogram_stir, self.log_file) #RECOVER IF NOT WORKING. + stir_tools.apply_psf(self.scanner, join(self.output_dir, "stir_sinogram.nii"), self.log_file) + if self.scanner.get("add_noise") != 0: stir_tools.add_noise( @@ -874,10 +838,11 @@ def run_recons(self): additive_sino_stir = join(self.output_dir, "stir_additivesino.hs") att_stir = join(self.output_dir, "stir_att.hs") - if self.scanner.get("stir_norm_from_att_map") != 1: + #if self.scanner.get("stir_norm_from_att_map") != 1: + if self.scanner.get("attenuation_mode") != 0: if any( - exists(i) == False for i in [sinogram_stir, additive_sino_stir, att_stir] + exists(i) == False for i in [sinogram_stir, att_stir] #[sinogram_stir, additive_sino_stir, att_stir] #NOTE: Integrate in the future... ): print("Something is not ready for the reconstruction") else: @@ -887,7 +852,7 @@ def run_recons(self): else: if any( - exists(i) == False for i in [sinogram_stir, additive_sino_stir] + exists(i) == False for i in [sinogram_stir] #[sinogram_stir, additive_sino_stir] #NOTE: Integrate in the future... ): print("Something is not ready for the reconstruction") else: diff --git a/src/simset/simset_tools.py b/src/simset/simset_tools.py index 6b139f47..c575a228 100644 --- a/src/simset/simset_tools.py +++ b/src/simset/simset_tools.py @@ -285,7 +285,7 @@ def make_simset_phg( def make_simset_bin( - config, output_file, simulation_dir, scanner, add_randoms=False, log_file=False + config, output_file, simulation_dir, scanner, add_randoms=False, log_file=False, list_mode=False ): stir_identifier = "# Hello, I am a SimSET BIN file!\n" @@ -335,11 +335,14 @@ def make_simset_bin( f.write("INT num_e2_bins = 1\n") f.write(real + "min_e = " + min_e + "\n") f.write(real + "max_e = " + max_e + "\n") - f.write("INT weight_image_type = 2\n") #BEFORE: 3 + f.write("INT weight_image_type = 2\n") #BEFORE: 3. WARNING: WORKS WITH 2. CHANGED TO 1 JUST TO TRY WHAT HAPPENS. #f.write("INT weight_image_type = 2\n") #CAREFUL. CHANGED FOR TOTAL-BODY!! - f.write("INT count_image_type = 2\n") + f.write("INT count_image_type = 2\n") #WARNING! WORKS WITH 2. CHANGED TO 1 JUST TO TRY WHAT HAPPENS. f.write("BOOL add_to_existing_img = false\n") - f.write(string + 'weight_image_path = "' + rec_weight_file + '"\n') + f.write("BOOL sum_according_to_type = true\n") #WARNING: ADDED FOR TOTAL-BODY. DELETE IF DETECTING STRANGE BEHAVIOUR. + + if list_mode == False: #Uses the param if no listmode is used. Otherwise does not write weights (RAM saving). + f.write(string + 'weight_image_path = "' + rec_weight_file + '"\n') #WARNING: REMOVED JUST FOR TESTING. HAS TO BE THERE. f.close() @@ -372,7 +375,7 @@ def make_simset_simp_det(scanner_params, output, sim_dir, det_hf=0, log_file=Fal tools.log_message(log_file, message, "info") -def make_simset_cyl_detOLD(scanner_params, output, sim_dir, det_hf=0, log_file=False): +def make_simset_cyl_det(scanner_params, output, sim_dir, det_hf=0, log_file=False): num_rings = scanner_params.get("num_rings") z_crystal_size = scanner_params.get("z_crystal_size") axial_fov = scanner_params.get("axial_fov") @@ -422,7 +425,7 @@ def make_simset_cyl_detOLD(scanner_params, output, sim_dir, det_hf=0, log_file=F + "INT cyln_num_layers = 1 \n" + "LIST cyln_layer_info_list = 4 \n" + "BOOL cyln_layer_is_active = FALSE \n" - + "INT cyln_layer_material = 0 \n" + + "INT cyln_layer_material = 0 \n" #WARNING: WE TRIED ALUMINIUM HERE (20) AND LEAD (8), did not work. RETURN TO AIR (0). + "REAL cyln_layer_inner_radius = %s \n" % cyln_inner_radius + "REAL cyln_layer_outer_radius = %s \n" % cyln_outer_radius + "REAL cyln_min_z = %s \n" % ring_zmax @@ -454,7 +457,7 @@ def make_simset_cyl_detOLD(scanner_params, output, sim_dir, det_hf=0, log_file=F tools.log_message(log_file, message, "info") - +""" #maintain just in case... def make_simset_cyl_det(scanner_params, output, sim_dir, det_hf=0, log_file=False): num_rings = scanner_params.get("num_rings") z_crystal_size = scanner_params.get("z_crystal_size") @@ -537,7 +540,7 @@ def make_simset_cyl_det(scanner_params, output, sim_dir, det_hf=0, log_file=Fals ) tools.log_message(log_file, message, "info") - +""" def make_index_file(simulation_dir, simset_dir, log_file=False): output = join(simulation_dir, "index_file.log") @@ -592,20 +595,22 @@ def process_weights(weights_file, output_dir, scanner, add_randoms=0): trues_start = Simset_offset trues_end = Simset_offset + block_size trues_file = join(output_dir, "w1") - + + #WARNING: THIS IS THE OLD CODE (READS FULL FILE): + #with open(weights_file, "rb") as in_file: + # with open(trues_file, "wb") as out_file: + # out_file.write(in_file.read()[trues_start:trues_end]) + + #TODO: CHECK IF THIS WORKS (READS ONLY PER CHUNKS, USEFUL FOR TOTAL-BODY): with open(weights_file, "rb") as in_file: with open(trues_file, "wb") as out_file: - out_file.write(in_file.read()[trues_start:trues_end]) + in_file.seek(trues_start) + out_file.write(in_file.read(block_size)) - ############ RESTABLISH IF FAILS: - #output = join(output_dir, "trues.hdr") - #tools.create_analyze_from_imgdata( - # trues_file, output, nbins, nangles, nslices, 1, 1, 1, "fl" - #) - #os.remove(trues_file) output = join(output_dir, "trues.nii") #output = join(output_dir, "trues.nii.gz") + tools.create_nifti_from_imgdata( trues_file, output, nbins, nangles, nslices, 1, 1, 1, "fl" ) @@ -615,17 +620,17 @@ def process_weights(weights_file, output_dir, scanner, add_randoms=0): scatter_start = trues_end scatter_end = trues_end + block_size scatter_file = join(output_dir, "w2") - + + #WARNING: THIS IS THE OLD CODE (READS FULL FILE): + #with open(weights_file, "rb") as in_file: + # with open(scatter_file, "wb") as out_file: + # out_file.write(in_file.read()[scatter_start:scatter_end]) + + #TODO: CHECK IF THIS WORKS (READS ONLY PER CHUNKS, USEFUL FOR TOTAL-BODY): with open(weights_file, "rb") as in_file: with open(scatter_file, "wb") as out_file: - out_file.write(in_file.read()[scatter_start:scatter_end]) - - ############ RESTABLISH IF FAILS: - #output = join(output_dir, "scatter.hdr") - #tools.create_analyze_from_imgdata( - # scatter_file, output, nbins, nangles, nslices, 1, 1, 1, "fl" - #) - #os.remove(scatter_file) + in_file.seek(scatter_start) + out_file.write(in_file.read(block_size)) output = join(output_dir, "scatter.nii") #output = join(output_dir, "scatter.nii.gz") @@ -634,23 +639,30 @@ def process_weights(weights_file, output_dir, scanner, add_randoms=0): ) os.remove(scatter_file) - #EDIT IN FUTURE: + #SHOULD CHECK IF THIS WORKS: if add_randoms == 1: randoms_start = scatter_end randoms_end = scatter_end + block_size randoms_file = join(output_dir, "w3") + #WARNING: THIS IS THE OLD CODE (READS FULL FILE): + #with open(weights_file, "rb") as in_file: + # with open(randoms_file, "wb") as out_file: + # out_file.write(in_file.read()[randoms_start:randoms_end]) + + #TODO: CHECK IF THIS WORKS (READS ONLY PER CHUNKS, USEFUL FOR TOTAL-BODY): with open(weights_file, "rb") as in_file: with open(randoms_file, "wb") as out_file: - out_file.write(in_file.read()[randoms_start:randoms_end]) - - output = join(output_dir, "randoms.hdr") - tools.create_analyze_from_imgdata( + in_file.seek(randoms_start) + out_file.write(in_file.read(block_size)) + + output = join(output_dir, "randoms.nii") + #output = join(output_dir, "randoms.nii.gz") + tools.create_nifti_from_imgdata( randoms_file, output, nbins, nangles, nslices, 1, 1, 1, "fl" ) os.remove(randoms_file) - def add_randoms(sim_dir, simset_dir, coincidence_window, rebin=True, log_file=False): string = "STR " integer = "INT " @@ -725,7 +737,8 @@ def combine_history_files(simset_dir, history_files, output, log_file): stdin=sp.PIPE, stdout=sp.PIPE, stderr=sp.PIPE, - ).communicate("Yes\n") + ).communicate("No\n") #No: Do not delete inputs. + #).communicate("Yes\n") #old... # tools.osrun(rcommand, log_file) From 6b07b38d6790f73ee97d5f187e96b9a8f0f0d761 Mon Sep 17 00:00:00 2001 From: arnaufme8 <113554412+arnaufme8@users.noreply.github.com> Date: Fri, 17 Jul 2026 22:07:39 +0200 Subject: [PATCH 14/23] Add files via upload --- src/stir/stir_tools.py | 251 +++++++++++++---------------------------- 1 file changed, 80 insertions(+), 171 deletions(-) diff --git a/src/stir/stir_tools.py b/src/stir/stir_tools.py index 496f43f4..65f9bac3 100644 --- a/src/stir/stir_tools.py +++ b/src/stir/stir_tools.py @@ -8,9 +8,7 @@ from utils import tools from utils import resources as rsc -import stir - - +#OLD VERSION. MUST BE UPDATED FOR STIR 6.0... def create_stir_hs_from_detparams(scannerParams, output_file, output_format="SimSET"): num_rings = scannerParams.get("num_rings") max_z = scannerParams.get("axial_fov") / 2 @@ -18,14 +16,19 @@ def create_stir_hs_from_detparams(scannerParams, output_file, output_format="Sim z_crystal_size = scannerParams.get("z_crystal_size") gap_size = (max_z - min_z - z_crystal_size * num_rings) / (num_rings - 1) ring_spacing = z_crystal_size + gap_size - + + max_segment = scannerParams.get("max_segment") + min_td = -scannerParams.get("scanner_radius") max_td = scannerParams.get("scanner_radius") td_bins = scannerParams.get("num_td_bins") bin_size = (max_td - min_td) / float(td_bins) - matrix_size, ring_difference = generate_segments_lists_stir( + matrix_size, ring_difference = generate_segments_lists_stir( #WARNING! RECOVER THIS IF STOPS WORKING. num_rings, num_rings - 1 ) + #matrix_size, ring_difference = generate_segments_lists_stir( #WARNING! THIS IS NEEDED FOR FBP3D + # num_rings, max_segment + #) transaxial_crystal_distance = scannerParams.get("transaxial_crystal_size") scanner_name = scannerParams.get("scanner_name") @@ -62,7 +65,7 @@ def create_stir_hs_from_detparams(scannerParams, output_file, output_format="Sim + "number of dimensions := 4\n" + "matrix axis label [4] := segment\n" + "!matrix size [4] := " - + str(2 * (num_rings - 1) + 1) + + str(2 * (num_rings - 1) + 1) #WARNING! RECOVER THIS: str(2 * (num_rings - 1) + 1) #WARNING! THIS IS NEEDED FOR FBP3D: str(2 * (max_segment) + 1) + "\n" + "matrix axis label [" + str(views_coordinate) @@ -157,171 +160,49 @@ def create_stir_hs_from_detparams(scannerParams, output_file, output_format="Sim ) -def create_reduced_stir_hs_from_detparams(scannerParams, output_file, output_format="SimSET"): - num_rings = scannerParams.get("num_rings") - max_z = scannerParams.get("axial_fov") / 2 - min_z = -scannerParams.get("axial_fov") / 2 - z_crystal_size = scannerParams.get("z_crystal_size") - gap_size = (max_z - min_z - z_crystal_size * num_rings) / (num_rings - 1) - ring_spacing = z_crystal_size + gap_size - max_segment = scannerParams.get("max_segment") - - min_td = -scannerParams.get("scanner_radius") - max_td = scannerParams.get("scanner_radius") - td_bins = scannerParams.get("num_td_bins") - bin_size = (max_td - min_td) / float(td_bins) - matrix_size, ring_difference = generate_segments_lists_stir( - num_rings, max_segment #- 1 - ) - transaxial_crystal_distance = scannerParams.get("transaxial_crystal_size") - - scanner_name = scannerParams.get("scanner_name") - - if output_format == "SimSET": - views_coordinate = 2 - axial_coordinate = 3 - else: - views_coordinate = 3 - axial_coordinate = 2 - - new_file = open(output_file, "w") +def generate_segments_lists_stir(nrings, max_segment): + last_segment_sinograms = nrings - max_segment + my_matrix_size = " " + my_matrix_ring_difference = " " + for i in range(last_segment_sinograms, nrings): + my_matrix_size = my_matrix_size + str(i) + "," + my_matrix_ring_difference = my_matrix_ring_difference + str(i - nrings) + "," #WARNING! RECOVER IF FAILS. + #my_matrix_ring_difference = my_matrix_ring_difference + str(nrings - i) + "," #NOTE: suspect rings are inverted. + for i in range(nrings, last_segment_sinograms - 1, -1): + my_matrix_size = my_matrix_size + str(i) + "," + my_matrix_ring_difference = my_matrix_ring_difference + str(nrings - i) + "," #WARNING! RECOVER IF FAILS. + #my_matrix_ring_difference = my_matrix_ring_difference + str(i - nrings) + "," #NOTE: suspect rings are inverted. - new_file.write( - "!INTERFILE :=\n" - + "!imaging modality := PT\n" - + "name of data file := " - + output_file[0:-2] - + "s" - + "\n" - + "originating system := " - + scanner_name - + "\n" - + "!version of keys := STIR3.0\n" - + "!GENERAL DATA :=\n" - + "!GENERAL IMAGE DATA :=\n" - + "!type of data := PET\n" - + "imagedata byte order := LITTLEENDIAN\n" - + "!PET STUDY (General) :=\n" - + "!PET data type := Emission\n" - + "applied corrections := {arc correction}\n" - + "!number format := float\n" - + "!number of bytes per pixel := 4\n" - + "number of dimensions := 4\n" - + "matrix axis label [4] := segment\n" - + "!matrix size [4] := " - + str(2 * max_segment + 1) #THIS CHANGED TO MAX_SEGMENT - + "\n" - + "matrix axis label [" - + str(views_coordinate) - + "] := view\n" - + "!matrix size [" - + str(views_coordinate) - + "] := " - + str(scannerParams.get("num_aa_bins")) - + "\n" - + "matrix axis label [" - + str(axial_coordinate) - + "] := axial coordinate\n" - + "!matrix size [" - + str(axial_coordinate) - + "] := " - + matrix_size #THIS NEEDS TO CHANGE - + "\n" - + "matrix axis label [1] := tangential coordinate\n" - + "!matrix size [1] := " - + str(td_bins) - + "\n" - "minimum ring difference per segment := " - + ring_difference #THIS NEEDS TO CHANGE - + "\n" - + "maximum ring difference per segment := " - + ring_difference #THIS NEEDS TO CHANGE - + "\n" - + "Scanner parameters:= \n" - + "Scanner type := " - + scanner_name - + "\n" - + "Number of rings := " - + str(num_rings) #THIS NEEDS TO CHANGE??? (or maybe not) - + "\n" - + "Number of detectors per ring := " - + str(scannerParams.get("num_aa_bins") * 2) #THIS NEEDS TO CHANGE??? (or maybe not) - + "\n" - "Inner ring diameter (cm) := " - + str(scannerParams.get("scanner_radius") * 2) - + "\n" - + "Average depth of interaction (cm) := " - + str(scannerParams.get("average_doi")) - + "\n" - + "Distance between rings (cm) := " - + str(ring_spacing) - + "\n" - + "Default bin size (cm) := " - + str(bin_size) - + "\n" - + "View offset (degrees) := 0\n" - + "Maximum number of non-arc-corrected bins := " - + str(td_bins) - + "\n" - + "Default number of arc-corrected bins := " - + str(td_bins) - + "\n" - + "Energy_resolution := " - + str(scannerParams.get("energy_resolution") / 100) - + "\n" - + "Reference energy (in keV) := 511\n" - + "Number of blocks per bucket in transaxial direction := 1\n" - + "Number of blocks per bucket in axial direction := 1\n" - + "Number of crystals per block in axial direction := 1\n" - + "Number of crystals per block in transaxial direction := 1\n" - + "Number of crystals per singles unit in axial direction := 1\n" - + "Number of crystals per singles unit in transaxial direction := 1\n" - + "Scanner geometry := Cylindrical" - + "\n" - + "Distance between crystals in axial direction (cm) := " - + str(ring_spacing) - + "\n" - + "Distance between crystals in transaxial direction (cm) := " - + str(transaxial_crystal_distance) - + "\n" - + "Distance between blocks in transaxial direction (cm) := " - + str(transaxial_crystal_distance) - + "\n" - + "Distance between crystals in axial direction (cm) := " - + str(ring_spacing) - + "\n" - + "Distance between blocks in axial direction (cm) := " - + str(ring_spacing) - + "\n" - + "end scanner parameters:=\n" - + "effective central bin size (cm) := " - + str(bin_size) - + "\n" - + "number of time frames := 1\n" - + "start vertical bed position (mm) := 0\n" - + "start horizontal bed position (mm) := 0\n" - + "!END OF INTERFILE :=\n" - ) + my_matrix_size = "{" + my_matrix_size[0:-1] + "}" + my_matrix_ring_difference = "{" + my_matrix_ring_difference[0:-1] + "}" + return my_matrix_size, my_matrix_ring_difference -def generate_segments_lists_stir(nrings, max_segment): - last_segment_sinograms = nrings - max_segment +def generate_segments_lists_stir_v2(nrings, max_segment): my_matrix_size = " " + for i in range(1, nrings): + my_matrix_size = my_matrix_size + str(i) + "," + for i in range(nrings, 0, -1): + my_matrix_size = my_matrix_size + str(i) + "," + + last_segment_sinograms = nrings - max_segment my_matrix_ring_difference = " " for i in range(last_segment_sinograms, nrings): - my_matrix_size = my_matrix_size + str(i) + "," my_matrix_ring_difference = my_matrix_ring_difference + str(i - nrings) + "," for i in range(nrings, last_segment_sinograms - 1, -1): - my_matrix_size = my_matrix_size + str(i) + "," my_matrix_ring_difference = my_matrix_ring_difference + str(nrings - i) + "," my_matrix_size = "{" + my_matrix_size[0:-1] + "}" my_matrix_ring_difference = "{" + my_matrix_ring_difference[0:-1] + "}" + + print("MY MATRIX SIZE:", my_matrix_size) + print("MY MATRIX RING DIFFERENCE:", my_matrix_ring_difference) return my_matrix_size, my_matrix_ring_difference + def apply_psf(scannerParams, sinogram_stir, log_file): num_z_bins = scannerParams.get("num_rings") num_aa_bins = scannerParams.get("num_aa_bins") @@ -441,7 +322,10 @@ def FBP2D_recons(config, scannerParams, sinograms_stir, output_dir, log_file): tools.osrun(command, log_file) output = recFileName + ".hv" - output = tools.anything_to_hdr_convert(output, log_file) + #output = tools.anything_to_hdr_convert(output, log_file) + + output = tools.convert_hv_to_nii(output, log_file) + tools.flip_rec_nifti(recFileName + ".nii", join(output_dir, "reconstruction.nii")) #WARNING! REMOVE IF THIS FAILS. THIS FLIPS THE FINAL REC IN 0 AXIS return output @@ -452,6 +336,7 @@ def FBP3D_recons(config, scannerParams, sinograms_stir, output_dir, log_file): zoom = scannerParams.get("zoomFactor") xyOutputSize = scannerParams.get("xyOutputSize") + max_segment = scannerParams.get("max_segment") recFileName = join(output_dir, "rec_FBP3D") @@ -471,20 +356,23 @@ def FBP3D_recons(config, scannerParams, sinograms_stir, output_dir, log_file): + "xy output image size (in pixels) := " + str(xyOutputSize) + "\n\n" - "maximum absolute segment number to process := 2 \n" + #"maximum absolute segment number to process := 2 \n" + #+ "maximum absolute segment number to process := -1 \n" + + "maximum absolute segment number to process := " + str(max_segment) + "\n" + "num segments to combine with ssrb := -1 \n\n" + #+ "num segments to combine with ssrb := 3 \n\n" + "alpha parameter for ramp filter := 1 \n" + "cut-off for ramp filter (in cycles) := 0.5 \n\n" - + "alpha parameter for colsher filter in axial direction := 1 \n" - + "cut-off for colsher filter in axial direction (in cycles) := 0.5 \n" - + "alpha parameter for colsher filter in planar direction := 1 \n" - + "cut-off for colsher filter in planar direction (in cycles) := 0.5 \n\n" - + "stretch factor for colsher filter definition in axial direction := 2 \n" - + "stretch factor for colsher filter definition in planar direction := 2 \n\n" - + "transaxial extension for fft := 1 \n" - + "axial extension for fft := 1 \n\n" - + "save intermediate images := 0 \n" - + "display level := 0 \n\n" + #+ "alpha parameter for colsher filter in axial direction := 1 \n" + #+ "cut-off for colsher filter in axial direction (in cycles) := 0.5 \n" + #+ "alpha parameter for colsher filter in planar direction := 1 \n" + #+ "cut-off for colsher filter in planar direction (in cycles) := 0.5 \n\n" + #+ "stretch factor for colsher filter definition in axial direction := 2 \n" + #+ "stretch factor for colsher filter definition in planar direction := 2 \n\n" + #+ "transaxial extension for fft := 1 \n" + #+ "axial extension for fft := 1 \n\n" + #+ "save intermediate images := 0 \n" + #+ "display level := 0 \n\n" + "end := \n" ) @@ -494,7 +382,10 @@ def FBP3D_recons(config, scannerParams, sinograms_stir, output_dir, log_file): tools.osrun(command, log_file) output = recFileName + ".hv" - output = tools.anything_to_hdr_convert((output)) + #output = tools.anything_to_hdr_convert((output)) + + output = tools.convert_hv_to_nii(output, log_file) + tools.flip_rec_nifti(recFileName + ".nii", join(output_dir, "reconstruction.nii")) #WARNING! REMOVE IF THIS FAILS. THIS FLIPS THE FINAL REC IN 0 AXIS return output @@ -566,7 +457,7 @@ def OSEM2D_recons( zoom_aux = 1 xyOutputSize_aux = round(xyOutputSize / zoom) - if scannerParams.get("analytical_att_correction") == 1: + if scannerParams.get("analytical_att_correction") == 1: #CHECK THIS!!!! att_corr_str = "" elif scannerParams.get("stir_recons_att_corr") == 1: att_corr_str = ( @@ -723,15 +614,32 @@ def OSEM3D_recons( att_corr_str = "" """ + #THIS IS TO CORRECT FROM IMG DIRECTLY, SEEMS TO NOT BE NECESSARY: + """ if scannerParams.get("stir_norm_from_att_map") == 1: att_corr_str = ( - "Bin Normalisation From Attenuation Image:= \n" + #"Bin Normalisation From Attenuation Image:= \n" + "Bin Normalisation type := From Attenuation Image \n" + + "Bin Normalisation From Attenuation Image:= \n" + "attenuation_image_filename := " + join(output_dir, 'mu_map.hv') + "\n" + "End Bin Normalisation From Attenuation Image := \n" ) - elif scannerParams.get("stir_recons_att_corr") == 1: + """ + #WARNING: UNIFY IF STIR_ATT IS USED. + #if scannerParams.get("stir_norm_from_att_map") == 1: + if scannerParams.get("attenuation_mode") == 1: + att_corr_str = ( + "Bin Normalisation type := From ProjData \n" + + "Bin Normalisation From ProjData := \n" + + "normalisation projdata filename:= " + + att_stir + + "\n" + + "End Bin Normalisation From ProjData:= \n" + ) + + elif scannerParams.get("attenuation_mode") == 2: att_corr_str = ( "Bin Normalisation type := From ProjData \n" + "Bin Normalisation From ProjData := \n" @@ -802,7 +710,7 @@ def OSEM3D_recons( + "Projector Pair Using Matrix Parameters := \n" + "Matrix type := Ray Tracing \n" + "Ray tracing matrix parameters := \n" - + "number of rays in tangential direction to trace for each bin:= 5 \n" + + "number of rays in tangential direction to trace for each bin:= 5 \n" #WARNING! ORIGINAL WAS 5 + "End Ray tracing matrix parameters := \n" + "End Projector Pair Using Matrix Parameters := \n\n" + att_corr_str @@ -863,5 +771,6 @@ def OSEM3D_recons( #output = tools.anything_to_hdr_convert(output, log_file) output = tools.convert_hv_to_nii(output, log_file) + tools.flip_rec_nifti(recFileName + "_" + str(scannerParams.get("numberOfIterations")) + ".nii", join(output_dir, "reconstruction.nii")) #WARNING! REMOVE IF THIS FAILS. THIS FLIPS THE FINAL REC IN 0 AXIS return output From 9b3f3cccdcc421fb9bf349610f878403142484ef Mon Sep 17 00:00:00 2001 From: arnaufme8 <113554412+arnaufme8@users.noreply.github.com> Date: Fri, 17 Jul 2026 22:12:03 +0200 Subject: [PATCH 15/23] Add files via upload --- utils/tools.py | 204 +++++++++++++++++++++++++++---------------------- 1 file changed, 114 insertions(+), 90 deletions(-) diff --git a/utils/tools.py b/utils/tools.py index cf22d442..63372c86 100644 --- a/utils/tools.py +++ b/utils/tools.py @@ -289,6 +289,44 @@ def write_interfile_header(header_file,matrix_size_x,pixel_size_x, matrix_size_y fheader_hv.write("!END OF INTERFILE :=\n") fheader_hv.close() + +def write_interfile_header_mu(header_file,matrix_size_x,pixel_size_x, matrix_size_y,pixel_size_y, matrix_size_z,pixel_size_z, offset_z=None): + + image_v = os.path.basename(header_file)[0:-2] + "v" + fheader_hv = open(header_file, "w") + + offset_x = matrix_size_x/2*pixel_size_x + 0.5*pixel_size_x + offset_y = matrix_size_y/2*pixel_size_y + 0.5*pixel_size_y + + fheader_hv.write("!INTERFILE :=\n") + fheader_hv.write("name of data file := %s\n" % image_v) + fheader_hv.write("!GENERAL DATA :=\n") + fheader_hv.write("!GENERAL IMAGE DATA :=\n") + fheader_hv.write("!type of data := PET\n") + fheader_hv.write("imagedata byte order := LITTLEENDIAN\n") + fheader_hv.write("!PET STUDY (General) :=\n") + fheader_hv.write("!PET data type := Image\n") + fheader_hv.write("process status := Reconstructed\n") + fheader_hv.write("!number format := float\n") + fheader_hv.write("!number of bytes per pixel := 4\n") + fheader_hv.write("number of dimensions := 3\n") + fheader_hv.write("matrix axis label [1] := x\n") + fheader_hv.write("!matrix size [1] := %s\n" % matrix_size_x) + fheader_hv.write("scaling factor (mm/pixel) [1] := %s\n" % pixel_size_x) + fheader_hv.write("matrix axis label [2] := y\n") + fheader_hv.write("!matrix size [2] := %s\n" % matrix_size_y) + fheader_hv.write("scaling factor (mm/pixel) [2] := %s\n" % pixel_size_y) + fheader_hv.write("matrix axis label [3] := z\n") + fheader_hv.write("!matrix size [3] := %s\n" % matrix_size_z) + fheader_hv.write("scaling factor (mm/pixel) [3] := %s\n" % pixel_size_z) + + if offset_z != None: + fheader_hv.write("first pixel offset (mm) [3] := %s\n" % offset_z) + + fheader_hv.write("number of time frames := 1\n") + fheader_hv.write("!END OF INTERFILE :=\n") + + fheader_hv.close() def nii_analyze_convert(image, logfile=False, outfile=False): """ @@ -603,9 +641,9 @@ def operate_single_image_nii(input_image, operation, factor, output_image, logfi img = nib.load(input_image) data = img.get_data()[:,:,:] - data = np.nan_to_num(data) #re-added + #data = np.nan_to_num(data) #re-added """ - if check_nans: + if check_nans: #TODO SEE WHAT HAPPENS TO THIS data = np.nan_to_num(data) #data[np.isnan(data)]=0 """ @@ -983,14 +1021,11 @@ def convert_simset_sino_to_stir(input_img, output=False): nib.save(stir_img,output) -#Re-added: WARNING! THIS NEEDS TO BE REVISED. BUT THIS IS THE GOOD ONE (INITIAL) -""" -def convert_simset_sino_to_stir_nii(input_img, output=False): - ## To be continued.... +def convert_simset_sino_to_stir_nii(input_img, output=False): simset_img = nib.load(input_img) - simset_img_data = simset_img.get_fdata() + simset_img_data = simset_img.get_fdata(dtype=np.float32) shape = simset_img_data.shape n_slices = shape[2] @@ -1007,8 +1042,8 @@ def convert_simset_sino_to_stir_nii(input_img, output=False): input_definition.append(slice_def) output_definition = sorted(input_definition, key=itemgetter(3)) - stir_img_data = np.empty(shape, dtype=float, order='C') - stir_img_data_flip_x = np.empty(shape, dtype=float, order='C') + stir_img_data = np.empty(shape, dtype=np.float32, order='C') + #stir_img_data_flip_x = np.empty(shape, dtype=np.float32, order='C') for i in range(n_slices): @@ -1016,92 +1051,36 @@ def convert_simset_sino_to_stir_nii(input_img, output=False): input_slice = simset_img_data[:,:,output_index] stir_img_data[:,:,i] = input_slice - #THIS FLIP IS NOT NECESSARY! [MAYBE IT IS]: - for j in range(n_x): - stir_img_data_flip_x[j,:,:] = stir_img_data[n_x-1-j, :, :] - - #stir_img = nib.AnalyzeImage(stir_img_data_flip_x, simset_img.affine, simset_img.header) - #stir_img = nib.Nifti2Image(stir_img_data, simset_img.affine, simset_img.header) - stir_img = nib.Nifti2Image(stir_img_data_flip_x, simset_img.affine, simset_img.header) - #stir_img = nib.Nifti1Image(stir_img_data_flip_x, simset_img.affine, simset_img.header) - - if not output: - #output = input_img [0:-4] + '_stir.nii' - output = input_img [0:-7] + '_stir.nii.gz' + #for j in range(n_x): + # stir_img_data_flip_x[j,:,:] = stir_img_data[n_x-1-j, :, :] - #nib.save(stir_img,output[0:-4] + '.nii') - #nib.save(stir_img,output[0:-4] + '.hdr') - #nib.save(stir_img,output[0:-7] + '.hdr') - #nib.save(stir_img, output[0:-7] + '.nii.gz') - nib.save(stir_img, output) -""" - -""" -def convert_simset_sino_to_stir_nii(input_img, output=False): - - simset_img = nib.load(input_img) - simset_img_data = simset_img.get_fdata() - shape = simset_img_data.shape - - n_slices = shape[2] - nrings = np.sqrt(n_slices) - n_x = shape[0] - - input_definition = [] - - for i in range(n_slices): - - ring1,ring2 = divmod(i,nrings) - segment = ring1 - ring2 - slice_def = [i, ring1, ring2, segment] - input_definition.append(slice_def) - - indices = np.arange(n_slices).astype(int) - newring1 = indices // nrings - newring2 = indices % nrings - newsegment = newring1 - newring2 - - newinput_definition = np.column_stack([indices, newring1, newring2, newsegment]) - #newoutput_definition = newinput_definition[np.argsort(newinput_definition[:, 3])] - #newoutput_definition = newoutput_definition.tolist() - newoutput_definition = sorted(newinput_definition, key=itemgetter(3)) - - - #print("OLD INPUT DEFINITION:", input_definition) - #print("\n\n\n\n") - #print("NEW INPUT DEFINITION:", newinput_definition) - - output_definition = sorted(input_definition, key=itemgetter(3)) - stir_img_data = np.empty(shape, dtype=float, order='C') - stir_img_data_flip_x = np.empty(shape, dtype=float, order='C') + #WARNING WARNING WARNING: WE COMMENT THAT FOR TESTING, MAY NEED TO COME BACK! + #NOTE: POTENTIALLY, WE NEED TO FLIP IN Y. (if so, merge flips) + #NOTE: POTENTIALLY z IS ALSO FLIPPED RESPECT TO ATT BUT THEN WE NEED TO CHANGE SEGMENT SIGNS AS WELL! - print("OLD INPUT DEFINITION:", output_definition) - print("\n\n\n\n") - print("NEW INPUT DEFINITION:", newoutput_definition) - - - for i in range(n_slices): - - output_index = output_definition[i][0] - input_slice = simset_img_data[:,:,output_index] - stir_img_data[:,:,i] = input_slice - - stir_img_data_flip_x = stir_img_data[::-1, :, :] - - stir_img = nib.Nifti2Image(stir_img_data_flip_x, simset_img.affine, simset_img.header) + #stir_img_data_flip_x = stir_img_data[::-1,:,:] + #stir_img_data_flip_x = stir_img_data_flip_x[:,::-1,:] + stir_img_data_flip = stir_img_data[::-1,::-1,:] #WARNING: THIS IS TEMPORAL + #stir_img_data_flip = stir_img_data[::-1,::-1,::-1] #WARNING: THIS IS TEMPORAL + #stir_img_data_flip_x = stir_img_data + + #stir_img = nib.AnalyzeImage(stir_img_data, simset_img.affine, simset_img.header) + #stir_img = nib.Nifti2Image(stir_img_data_flip_x, simset_img.affine, simset_img.header) + stir_img = nib.Nifti2Image(stir_img_data_flip, simset_img.affine, simset_img.header) if not output: - output = input_img [0:-7] + '_stir.nii.gz' + output = input_img [0:-4] + '_stir.nii' - nib.save(stir_img, output) -""" + nib.save(stir_img,output) + +""" #WARNING: THIS WAS CORRECT. BUT HAS BEEN REMOVED TO ENSURE ORDER IN SINOGRAMS IS CORRECT. def convert_simset_sino_to_stir_nii(input_img, output=False): simset_img = nib.load(input_img) - simset_img_data = simset_img.get_fdata() + simset_img_data = simset_img.get_fdata(dtype=np.float32) #.astype(np.float32) #WARNING! CHANGE DATA TYPE HERE (TO FLOAT32?) shape = simset_img_data.shape n_slices = shape[2] @@ -1135,7 +1114,7 @@ def convert_simset_sino_to_stir_nii(input_img, output=False): output = input_img[:-7] + '_stir.nii.gz' nib.save(stir_img, output) - +""" @@ -1237,7 +1216,8 @@ def copy_sinogram_stir_to_output(input_img, output_img): #os.remove(input_img[0:-6] + "img") #os.remove(input_img[0:-6] + "hdr") - + +#NOTE: THIS FUNCTION MAY BE DELETED IN THE FUTURE... def copy_reduced_sinogram_stir_to_output(input_img, output_img, nrings, max_segment): simset_sino = nib.load(input_img) @@ -1249,11 +1229,11 @@ def copy_reduced_sinogram_stir_to_output(input_img, output_img, nrings, max_segm sino_reduced = sino_dataobj[..., init_idx:final_idx].astype(sino_dataobj.dtype) sino_reduced_nii = nib.Nifti2Image(sino_reduced, simset_sino.affine) - nib.save(sino_reduced_nii, input_img[0:-7] + '.hdr') + nib.save(sino_reduced_nii, input_img[0:-4] + '.hdr') - shutil.copy(input_img[0:-6] + "img", output_img) - os.remove(input_img[0:-6] + "img") - os.remove(input_img[0:-6] + "hdr") + shutil.copy(input_img[0:-3] + "img", output_img) + os.remove(input_img[0:-3] + "img") + os.remove(input_img[0:-3] + "hdr") @@ -1590,3 +1570,47 @@ def mu_coef_511keV(tissue_n): mu = data[510,1] return mu + + +def write_fwdproj_parfile(output_path): #TODO: ADD NRAY ARGUMENT! ORIGINAL WAS 1, POTENTIALLY 5. + content = """Forward Projector parameters:= + type := Matrix + Forward projector Using Matrix Parameters := + Matrix type := Ray Tracing + Ray tracing matrix parameters := + number of rays in tangential direction to trace for each bin := 5 + End Ray tracing matrix parameters := + End Forward Projector Using Matrix Parameters := +End:= +""" + + with open(output_path, "w", encoding="utf-8") as f: + f.write(content) + +def flip_rec_nifti(input_path, output_path=None): + + img = nib.load(input_path) + data = np.asanyarray(img.dataobj) + + # Flip data along axis 0 + flipped_data = np.flip(data, axis=0) + + # Fix affine: NOTE at the moment seems to not be necessary to change affine. + affine = img.affine.copy() + #affine[0, :] *= -1 + #affine[:3, 3] += img.affine[:3, 0] * (data.shape[0] - 1) + + # Create new image + new_img = nib.Nifti1Image(flipped_data, affine, img.header) + + # Save if path provided + if output_path is None: + base, ext = os.path.splitext(input_path) + if ext == ".gz": # handle .nii.gz + base, ext2 = os.path.splitext(base) + ext = ext2 + ext + output_path = base + "_flipped" + ext + + nib.save(new_img, output_path) + + return output_path From d89693d411e49da34fd9808fe0c6dcd264611d78 Mon Sep 17 00:00:00 2001 From: arnaufme8 Date: Fri, 17 Jul 2026 22:47:08 +0200 Subject: [PATCH 16/23] Update submodule to commit 166ba62 --- submodules/STIR/STIR | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/STIR/STIR b/submodules/STIR/STIR index 46da4ef2..166ba624 160000 --- a/submodules/STIR/STIR +++ b/submodules/STIR/STIR @@ -1 +1 @@ -Subproject commit 46da4ef2b8b38487b08f5aa48c6e38b568a584cc +Subproject commit 166ba624e4c00af20612d07c94f1d2f7f04eedd6 From e2f7dbbbfb9aaad59c3213b9ff0ea1d79f678c62 Mon Sep 17 00:00:00 2001 From: arnaufme8 <113554412+arnaufme8@users.noreply.github.com> Date: Tue, 21 Jul 2026 09:49:37 +0200 Subject: [PATCH 17/23] Delete makefile-sudo --- makefile-sudo | 256 -------------------------------------------------- 1 file changed, 256 deletions(-) delete mode 100644 makefile-sudo diff --git a/makefile-sudo b/makefile-sudo deleted file mode 100644 index 8080b1c9..00000000 --- a/makefile-sudo +++ /dev/null @@ -1,256 +0,0 @@ -SHELL := /bin/bash -TMPDIR := /tmp -ROOT_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) -ASSETS_DIR := ${ROOT_DIR}assets -INCLUDE_DIR := ${ROOT_DIR}include -SUBMODULES_DIR := ${ROOT_DIR}submodules - -.PHONY: deps install-simset check-simset clean-simset -.PHONY: install-stir check-stir clean-stir -.PHONY: install-resources check-resources clean-resources -.PHONY: config-git clean-git smudge-filer config-paths clean-paths -.PHONY: dummy-data install clean help - -deps: - sudo apt-get -y -q update ;\ - sudo apt-get install -y -q \ - software-properties-common \ - git[all] \ - git-lfs \ - wget \ - unzip \ - sshpass \ - libboost-dev \ - libboost-all-dev \ - libpcre3 \ - libpcre3-dev \ - libncurses-dev \ - cmake \ - g++ \ - swig ;\ - sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys CC86BB64 ;\ - sudo add-apt-repository -y ppa:rmescandon/yq ;\ - sudo apt-get -y -q update ;\ - sudo apt-get install -y -q yq - -SIMSET_SUBMODULE_DIR = ${ROOT_DIR}/submodules/simset_simpet -SIMSET_DEST_DIR = ${INCLUDE_DIR}/SimSET -SIMSET_PATH = ${SIMSET_DEST_DIR}/2.9.2 -SIMSET_BIN = ${SIMSET_PATH}/bin -SIMSET_LIB = ${SIMSET_PATH}/lib -SIMSET_MKALL = ${SIMSET_PATH}/make_all.sh -SIMSET_MKFILE = ${SIMSET_PATH}/make.files/simset.make - -install-simset: - if [ ! -d ${SIMSET_DEST_DIR} ]; then \ - mkdir -p ${SIMSET_PATH} ;\ - cp -r ${SIMSET_SUBMODULE_DIR}/* ${SIMSET_PATH} ;\ - sed -i 's/^\(SIMSET_PATH = \).*$$/\1$(subst /,\/,${SIMSET_PATH})/' ${SIMSET_MKFILE} ;\ - cd ${SIMSET_PATH} && mkdir -p ${SIMSET_LIB} && bash ${SIMSET_MKALL} ;\ - else \ - echo "${SIMSET_DEST_DIR} already exists, run clean-simset if you really want to remove it (you will have to intall SimSET again.)" ;\ - fi - -SIMSET_TAR = ${ASSETS_DIR}/phg.2.9.2.tar.Z -SIMSET_STIR_PATCH = ${ASSETS_DIR}/simset_for_stir.patch - -install-canonic-simset: ${SIMSET_TAR} - if [ ! -d ${SIMSET_DEST_DIR} ]; then \ - mkdir -p ${SIMSET_DEST_DIR} && tar -xvf "${SIMSET_TAR}" --directory=${SIMSET_DEST_DIR} ;\ - cd ${SIMSET_DEST_DIR} && patch -s -p0 < ${SIMSET_STIR_PATCH} ;\ - sed -i 's/^\(SIMSET_PATH = \).*$$/\1$(subst /,\/,${SIMSET_PATH})/' ${SIMSET_MKFILE} ;\ - cd ${SIMSET_PATH} && mkdir -p ${SIMSET_LIB} && bash ${SIMSET_MKALL} ;\ - else \ - echo "${SIMSET_DEST_DIR} already exists, run clean-simset if you really want to remove it (you will have to intall SimSET again.)" ;\ - fi - -check-simset: - declare -a simset_files=(addrandoms bin calcattenuation combinehist makeindexfile phg timesort) ;\ - for file in "$${simset_files[@]}"; do \ - if [ ! -f "${SIMSET_BIN}/$${file}" ]; then \ - echo "${SIMSET_BIN}/$${file} does not exists, check your installation." ;\ - else \ - echo "${SIMSET_BIN}/$${file} exists." ;\ - fi ;\ - done - -clean-simset: - rm -rf ${SIMSET_DEST_DIR} - -SIMSET_SRC = ${SIMSET_PATH}/src -SIMSET_LIBSIMSET = ${SIMSET_LIB}/libsimset.so -STIR_DIR = ${SUBMODULES_DIR}/STIR/STIR -STIR_BUILD_DIR = ${SUBMODULES_DIR}/STIR/build -STIR_INSTALL_DIR = ${SUBMODULES_DIR}/STIR/install -STIR_INSTALL_BIN = ${STIR_INSTALL_DIR}/bin -STIR_MKFILE = ${STIR_BUILD_DIR}/CMakeCache.txt -STIR_FINAL_DEST_DIR = ${INCLUDE_DIR}/STIR -NPROC = $(shell nproc) - -install-stir: install-simset ${STIR_DIR} - if [ ! -d ${STIR_FINAL_DEST_DIR} ]; then \ - mkdir -p ${STIR_BUILD_DIR} ${STIR_INSTALL_DIR} ${STIR_FINAL_DEST_DIR} ;\ - cd ${STIR_BUILD_DIR} && cmake ${STIR_DIR} ;\ - sed -i \ - -e 's/^\(BUILD_SWIG_PYTHON\).*$$/\1:BOOL=OFF/' \ - -e 's/^\(CMAKE_INSTALL_PREFIX\).*$$/\1:PATH=$(subst /,\/,${STIR_INSTALL_DIR})/' \ - -e 's/^\(SIMSET_INCLUDE_DIRS\).*$$/\1:PATH=$(subst /,\/,${SIMSET_SRC})/' \ - -e 's/^\(SIMSET_LIBRARY\).*$$/\1:FILEPATH=$(subst /,\/,${SIMSET_LIBSIMSET})/' \ - -e 's/^\(STIR_OPENMP\).*$$/\1:BOOL=ON/' \ - ${STIR_MKFILE} ;\ - cmake ${STIR_DIR} ;\ - make -s -j${NPROC} ;\ - sudo make install ;\ - mv ${STIR_BUILD_DIR} ${STIR_INSTALL_DIR} ${STIR_FINAL_DEST_DIR} ;\ - else \ - echo "${STIR_FINAL_DEST_DIR} already exists, run clean-stir if you really want to remove it (you will have to intall STIR again)." ;\ - fi - -check-stir: - declare -a stir_files=(FBP2D FBP3DRP forward_project lm_to_projdata OSMAPOSL zoom_image) ;\ - for file in "$${stir_files[@]}"; do \ - if [ ! -f "${STIR_FINAL_DEST_DIR}/install/bin/$${file}" ]; then \ - echo "${STIR_FINAL_DEST_DIR}/install/bin/$${file} does not exists, check your installation." ;\ - else \ - echo "${STIR_FINAL_DEST_DIR}/install/bin/$${file} exists." ;\ - fi ;\ - done - -clean-stir: - rm -rf ${STIR_FINAL_DEST_DIR} - -RESOURCES_ZIP = ${ASSETS_DIR}/fruitcake.zip -RESOURCES_TMP = ${TMPDIR}/resources - -install-resources: ${RESOURCES_ZIP} - mkdir -p ${RESOURCES_TMP} ${INCLUDE_DIR} && unzip -o ${RESOURCES_ZIP} -d ${RESOURCES_TMP} ;\ - declare -a resources=(fruitcake format_converters) ;\ - for rce in "$${resources[@]}"; do \ - include_path="${INCLUDE_DIR}/$${rce}" ;\ - tmp_path="${RESOURCES_TMP}/$${rce}" ;\ - if [ ! -d $${include_path} ]; then \ - mv $${tmp_path} ${INCLUDE_DIR} ;\ - chmod -R +x $${include_path} ;\ - else \ - echo "$${include_path} already exists, run make clean-resources in order to clean resources installation (fruitcake will be removed as well)." ;\ - fi ;\ - done ;\ - rm -rf ${RESOURCES_TMP} - -check-resources: - declare -a resources_paths=(${FRUITCAKE_PATH} ${FORMAT_CONVERTERS_PATH}) ;\ - for path in "$${resources_paths[@]}"; do \ - if [ ! -d "$${path}" ]; then \ - echo "$${path} does not exists, clean fruitcake and format_converters with clean-resources and run install-resources." ;\ - else \ - echo "$${path} exists." ;\ - fi ;\ - done - -clean-resources: - rm -rf ${INCLUDE_DIR}/fruitcake ${INCLUDE_DIR}/format_converters - -config-git: - git config --local filter.config-test.smudge ${ROOT_DIR}scripts/smudge-config_test.sh - git config --local filter.config-test.clean ${ROOT_DIR}scripts/clean-config_test.sh - chmod +x -R ${ROOT_DIR}scripts - -clean-git: - git config --local --unset filter.config-test.smudge - git config --local --unset filter.config-test.clean - -smudge-filter: - rm -f ${ROOT_DIR}configs/config.yaml 2> /dev/null - git checkout HEAD -- ${ROOT_DIR}configs/config.yaml - -FRUITCAKE_PATH = ${INCLUDE_DIR}/fruitcake -FRUITCAKE_BIN = ${FRUITCAKE_PATH}/bin -FRUITCAKE_LIB = ${FRUITCAKE_PATH}/book/lib -FORMAT_CONVERTERS_PATH = ${INCLUDE_DIR}/format_converters - -config-paths: - touch "$${HOME}"/.bashrc ;\ - declare -a simpet_paths=( \ - 'export PATH=${FRUITCAKE_BIN}:$$PATH' \ - 'export LD_LIBRARY_PATH=${FRUITCAKE_LIB}:$$LD_LIBRARY_PATH' \ - 'export PATH=${FORMAT_CONVERTERS_PATH}:$$PATH' \ - ) ;\ - for path in "$${simpet_paths[@]}"; do \ - grep -qxF "$${path}" "$${HOME}"/.bashrc || echo "$${path}" >> "$${HOME}"/.bashrc ;\ - done - -clean-paths: - touch "$${HOME}/.bashrc" - sed -i \ - -e '/export PATH=$(subst /,\/,${FRUITCAKE_BIN}):$$PATH/d' \ - -e '/export LD_LIBRARY_PATH=$(subst /,\/,${FRUITCAKE_LIB}):$$LD_LIBRARY_PATH/d' \ - -e '/export PATH=$(subst /,\/,${FORMAT_CONVERTERS_PATH}):$$PATH/d' \ - "$${HOME}/.bashrc" - -DATA_DIR = ${ROOT_DIR}Data -DATA_ZIP = ${ASSETS_DIR}/Data.zip - -dummy-data: ${DATA_ZIP} - if [ ! -d "${DATA_DIR}" ]; then \ - mkdir -p ${DATA_DIR} && unzip -o ${DATA_ZIP} -d ${ROOT_DIR} ;\ - else \ - echo "${DATA_DIR} already exists, remove it manually." ;\ - fi - -install: - ${MAKE} \ - deps \ - install-simset \ - check-simset \ - install-stir \ - check-stir \ - install-resources \ - check-resources \ - config-paths \ - -clean: - ${MAKE} \ - clean-simset \ - clean-stir \ - clean-resources - -help: - @echo "Help:" - @echo " - deps: Install the dependencies of the projects via apt." - @echo "" - @echo " - install-simset: Install tweaked version of SimSET. - @echo "" - @echo " - install-canonic-simset: Install non-tweaked version of SimSET with STIR patch at directory ${SIMSET_DEST_DIR}." - @echo "" - @echo " - check-simset: Check that SimSET binaries exist." - @echo "" - @echo " - clean-simset: Clean SimSET installation (removes ${SIMSET_DEST_DIR} directory)." - @echo "" - @echo " - install-stir: Install STIR at directory $(shell dirname ${STIR_DIR}), install-simset is a prerequisite. Set NPROC=n to use n CPU cores in compilation." - @echo "" - @echo " - check-stir: Check that STIR binaries exist." - @echo "" - @echo " - clean-stir: Clean STIR installation removing ${STIR_INSTALL_DIR} and ${STIR_BUILD_DIR} directories." - @echo "" - @echo " - install-resources: Decompress ${RESOURCES_ZIP} and move fruitcake and format_converters to ${INCLUDE_DIR}" - @echo "" - @echo " - check-resources: Checks that fruitcake and format_converters are in ${INCLUDE_DIR}." - @echo "" - @echo " - clean-resources: Remove fruitcake and format_converters from ${INCLUDE_DIR}." - @echo "" - @echo " - config-git: Add project filter drivers to local git configuration and make them executable." - @echo "" - @echo " - clean-git: Remove project filter drivers from local git configuration." - @echo "" - @echo " - smudge-filer: Apply smudge filter to config file (updates paths)." - @echo "" - @echo " - config-paths: Iff not present in .bashrc, the paths of the projects will be appended to the file. If ~/.bashrc does not exists it will be created." - @echo "" - @echo " - clean-paths: If the paths of the project are present in .bashrc file, they will be deleted. If ~/.bashrc does not exists it will be created." - @echo "" - @echo " - dummy-data: Uncompress ${DATA_ZIP} at ${DATA_DIR} if ${DATA_DIR} does not exists." - @echo "" - @echo " - install: Run all install, config and check recipes." - @echo "" - @echo " - clean: Run all clean recipes." - From fd63484c0f3e8a25c7ec43eb6765b75f3537c2ff Mon Sep 17 00:00:00 2001 From: arnaufme8 <113554412+arnaufme8@users.noreply.github.com> Date: Tue, 21 Jul 2026 11:23:03 +0200 Subject: [PATCH 18/23] Add files via upload --- configs/params/test.yaml | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/configs/params/test.yaml b/configs/params/test.yaml index fcf2fa72..42a624a2 100644 --- a/configs/params/test.yaml +++ b/configs/params/test.yaml @@ -1,24 +1,24 @@ defaults: - - scanner: discovery_st #siemens_quadra #discovery_st + - scanner: discovery_st simulation_environment: 0 sim_type: "SimSET" -do_simulation: 0 #IMPORTANT: REMEMBER TO 0 IF ONLY RECONS +do_simulation: 1 do_reconstruction: 1 -divisions: 28 #5 #1 +divisions: 2 model_type: "cylindrical" -patient_dirname: "NEMA_Resolution_Phantoms_Discovery_3sources" #"NEMA_IEC_Body_Discovery" #"NEMA_Resolution_Phantoms_Discovery_3sources" #"NEMA_Resolution_Phantoms_Quadra_3sources" #"NEMA_IEC_Body_Quadra" #"test_brain_resampled" #"NEMA_IEC_Body_Quadra" #"NEMA_Resolution_Phantoms_Quadra_3sources" #"NEMA_Resolution_Phantoms_QuadraSTIR" #"NEMA_IEC_Body_QuadraSTIR" -act_map: "act.nii.gz" -att_map: "att.nii.gz" -output_dir: "NEMA_Resolution_Phantoms_Discovery_3sComparison" #"NEMA_IEC_Body_Discovery_sinos" #"NEMA_Resolution_Phantoms_Discovery_3sComparison" #"NEMA_Resolution_Phantoms_Quadra_3sComparison" #"NEMA_IEC_Body_Quadra_NoGap" #"test_brain_listmode7" #"test_brain4" #"NEMA_IEC_Body_Quadra_thincrystal" #"NEMA_Resolution_Phantoms_Quadra_3sources" #"NEMA_Resolution_Phantoms_QuadraSTIR_aluminium" #"NEMA_IEC_Body_QuadraSTIR" +patient_dirname: "test_image" +act_map: "act.hdr" +att_map: "att.hdr" +output_dir: "test_image" center_slice: 0 -total_dose: 1.5 #1.5 #0.1 #0.75 #0.03 #0.01 #1 #1.5 #Sens: 0.1 #SR: 0.06 #0.3 #1 #0.1 #0.5 #1 #0.5 #1.5 #0.01 #1.5 #1.5 #0.1 #1.5 -simulation_time: 120 #120 #60 #90 #120 #20 #150 #600 #IQ long: 600 #Sens: 120 #SR: 60 #60 #30 #60 #20 #60 #1 #60 #60 #1 #60 #30 -sampling_photons: 2000000 #2000000 #100000 #1000 #2000000 #2000000 #1000000 #2000000 #2000000 #2000000 #1000000 +total_dose: 0.1 +simulation_time: 30 +sampling_photons: 0 photons: 0 -add_randoms: 0 #0 #1 +add_randoms: 1 -listmode: True #ADDED. PROPOSAL: to unify both listmodes to only the necessary. -phglistmode: 1 #0 #ACTIVATE IF listmode. -detlistmode: 0 -maximumIteration: 1 +listmode: True # TODO: to unify both listmodes to only the necessary. +phglistmode: 1 #MUST BE ACTIVATED IF listmode. +detlistmode: 0 #At the moment unused, but may be used in the future. +maximumIteration: 1 #Unused at the moment. From 8174c224d79564ca29ecf739c35704179c490d12 Mon Sep 17 00:00:00 2001 From: arnaufme8 <113554412+arnaufme8@users.noreply.github.com> Date: Tue, 21 Jul 2026 11:23:23 +0200 Subject: [PATCH 19/23] Add files via upload --- configs/params/scanner/discovery_st.yaml | 11 +++-- configs/params/scanner/siemens_quadra.yaml | 47 +++++++++++----------- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/configs/params/scanner/discovery_st.yaml b/configs/params/scanner/discovery_st.yaml index 04e94a90..a586a990 100644 --- a/configs/params/scanner/discovery_st.yaml +++ b/configs/params/scanner/discovery_st.yaml @@ -14,8 +14,8 @@ min_energy_window: 375 max_energy_window: 675 coincidence_window: 11.7 numberOfSubsets: 7 -numberOfIterations: 8 #32 -savingInterval: 8 #32 #8 +numberOfIterations: 32 +savingInterval: 8 analytical_att_correction: 0 stir_recons_att_corr: 1 analytic_scatt_corr_factor: 0.15 @@ -29,13 +29,12 @@ subiteration_interval: 4 x_dir_filter_FWHM: 1.5 y_dir_filter_FWHM: 1.5 z_dir_filter_FWHM: 3 -psf_value: 0 #REMEMBER TO CHANGE TO 0!!!! +psf_value: 0 add_noise: 0 -max_segment: 23 #2 #23 -zoomFactor: 1 #1.55 +max_segment: 23 +zoomFactor: 1 #For brain: 1.55 xyOutputSize: 128 zOutputSize: 47 zOutputVoxelSize: 3.27 -#stir_norm_from_att_map: 1 #1 for correcting with STIR, 0 for correcting with SimSET. WARNING: Should both be unified? attenuation_mode: 2 #0: no attenuation, 1: attenuation with SimSET, 2: attenuation with STIR diff --git a/configs/params/scanner/siemens_quadra.yaml b/configs/params/scanner/siemens_quadra.yaml index 87aa0953..eac432e1 100644 --- a/configs/params/scanner/siemens_quadra.yaml +++ b/configs/params/scanner/siemens_quadra.yaml @@ -1,43 +1,42 @@ scanner_name: "Siemens Quadra" -simset_material: 18 #29 = LYSO? 18 = LSO? NOTE: Consider adding param for gap material! -average_doi: 0.7 #ORIGINAL: 0.84 DOI FROM DISCOVERY, tried 0.1. +simset_material: 18 +average_doi: 0.7 scanner_radius: 41.0 -num_rings: 320 #real: 320, papers: 323 -axial_fov: 106 #NoGaps: 102.41 #REAL: 106 # WARNING: MUST BE CHANGED, ONLY FOR TESTING -z_crystal_size: 0.32 -transaxial_crystal_size: 0.32 -crystal_thickness: 2 #2 # WARNING: CHANGE AGAIN TO 2 (REAL VALUE!). Tried 0.2 cm. -energy_resolution: 9 #OLD: 16 -num_aa_bins: 399 #380 #REAL: 399 (400 if FBP3D) #USED FOR LOWBIN: 252 #CORRECT: 399 #OLD: 252 #440 #? 210, KEEP DISCOVERY #798/2 = 399 -num_td_bins: 440 #440 #380 #REAL: 520 #used in paper: 440 #USED FOR LOWBIN: 344 #CORRECT: 440 #OLD: 344 #440 #? 249, KEEP DISCOVERY nsize = radius*2/nrad +num_rings: 320 #Real: 320, With module gaps: 323 +axial_fov: 106 +z_crystal_size: 0.32 #cm +transaxial_crystal_size: 0.32 #cm +crystal_thickness: 2 #cm +energy_resolution: 9 +num_aa_bins: 399 +num_td_bins: 520 min_energy_window: 435 max_energy_window: 585 coincidence_window: 4.7 #ns -numberOfSubsets: 7 #7 -numberOfIterations: 14 #25 #28 #28, 56 #14 #last: 102 #BEFORE: 8 #OLD: 5 #32 -savingInterval: 7 #5 #7 #RECOVER: 8 #OLD: 5 #8 #NOTE: MAKE TO CONVERT IT TO NII ASWELL! +numberOfSubsets: 7 +numberOfIterations: 14 +savingInterval: 7 analytical_att_correction: 0 -stir_recons_att_corr: 0 # WARNING: ORIGINALLY TO 1 TO DO ATT CORRECTION. -analytic_scatt_corr_factor: 0.15 # WARNING: ORIGINAL WAS 0.15. RECOVER. 0 for no scatter +stir_recons_att_corr: 0 +analytic_scatt_corr_factor: 0.15 stir_scatt_corr_smoothing: 0 stir_scatt_simulation: 0 analytic_randoms_corr_factor: 0.2 stir_randoms_corr_smoothing: 0 -recons_type: "OSEM3D" #"FBP3D" #"OSEM3D" #try FBP2D... +recons_type: "OSEM3D" #Options: "FBP2D", "FBP3D", "OSEM2D", "OSEM3D" inter_iteration_filter: 0 subiteration_interval: 4 x_dir_filter_FWHM: 1.5 y_dir_filter_FWHM: 1.5 z_dir_filter_FWHM: 3 -psf_value: 0 #WARNING: RETURN TO 0. IMPORTANT. +psf_value: 0 add_noise: 0 -max_segment: 85 #last try: 40 #RETURN TO: 85 #OLD: 85 #FULL: 319 #FBP3D: 1, #oldFBP: 10 #85 #319 #85 MAX: 40 -zoomFactor: 1 #0.25 #1 -xyOutputSize: 220 #FOR SR: 300 #NORMAL: 220 #OLD: 440 #110 #440 #220 #CHANGE IT TO DOUBLE OR BY FACTOR OR 3 -zOutputSize: 645 #645 #325 #CHANGE IT TO DOUBLE OR BY FACTOR OR 3 -zOutputVoxelSize: 1.65 #6.6 #1.65 #3.3 +max_segment: 85 #Reduced protocol: 85; Full protocol: 319 or 322 +zoomFactor: 1 +xyOutputSize: 220 +zOutputSize: 645 +zOutputVoxelSize: 1.65 -#stir_norm_from_att_map: 1 #1 for correcting with STIR, 0 for correcting with SimSET. WARNING: Should both be unified? -attenuation_mode: 2 #0: no attenuation, 1: attenuation with SimSET, 2: attenuation with STIR +attenuation_mode: 0 #0: no attenuation, 1: attenuation with SimSET, 2: attenuation with STIR From def128464a36388f3da5cf9db24e02901ce46ea9 Mon Sep 17 00:00:00 2001 From: arnaufme8 <113554412+arnaufme8@users.noreply.github.com> Date: Tue, 21 Jul 2026 11:23:52 +0200 Subject: [PATCH 20/23] Add files via upload --- src/simset/simset_sim.py | 108 +++++------------------- src/simset/simset_tools.py | 166 ++++--------------------------------- 2 files changed, 36 insertions(+), 238 deletions(-) diff --git a/src/simset/simset_sim.py b/src/simset/simset_sim.py index d6210461..67bd5633 100644 --- a/src/simset/simset_sim.py +++ b/src/simset/simset_sim.py @@ -260,7 +260,6 @@ def run_simset_simulation(self, sim_dir): if self.add_randoms == 1: coincidence_window = self.scanner.get("coincidence_window") - #WARNING: TESTING IF THIS IS NOT NEEDED. DE-COMMENT IF FAILS: simset_tools.add_randoms( sim_dir, self.simset_dir, @@ -378,14 +377,12 @@ def simulation_postprocessing(self): division_dir = join(self.output_dir, "division_" + str(division)) division_hist = join(division_dir, "phg_hf.hist") - #file_list = zero_hist + " " + division_hist file_list = file_list + " " + division_hist if self.divisions > 1: simset_tools.combine_history_files( self.simset_dir, file_list, full_hist, log_file ) - #shutil.move(output, zero_hist) os.remove(zero_hist) os.rename(full_hist, zero_hist) @@ -410,9 +407,7 @@ def simulation_postprocessing(self): else: for image in ["trues", "scatter", "randoms"]: #TODO: randoms needed here? - #zero_image = join(division_zero, image + ".hdr") zero_image = join(division_zero, image + ".nii") - #zero_image = join(division_zero, image + ".nii.gz") if exists(zero_image): print("Adding sinograms for %s" % image) @@ -428,41 +423,8 @@ def simulation_postprocessing(self): tools.log_message(log_file, message) tools.operate_sinograms_nii( - #tools.operate_images_nii( zero_image, division_image, zero_image, "sum" ) - - #os.remove(division_image[0:-3] + "nii") #NOTE: Not needed as full division folder will be removed - - #TODO: ADDED TO OPTIMIZE: - #histtypes_list = [] - # if detlistmode == 1... if phglistmode == 1... and remove divisions apart. - """ #This has become obsolete. - for hist in ["phg_hf.hist", "det_hf.hist"]: - zero_hist = join(division_zero, hist) - - if exists(zero_hist): - print(" ") - print("Adding History Files for %s" % hist) - - #PUT IN FUNCTION OF DETLISTMODE (MISSING NOW?) - output = join(division_zero, "tmp_" + hist) - - for division in range(1, self.divisions): - division_dir = join(self.output_dir, "division_" + str(division)) - division_hist = join(division_dir, hist) - file_list = zero_hist + " " + division_hist - - #WARNING: AT THE MOMENT THIS IS NOT NEEDED. RECOVER WHEN DETLISTMODE IS USED. - #simset_tools.combine_history_files( - # self.simset_dir, file_list, output, log_file - #) - #shutil.move(output, zero_hist) - - #shutil.rmtree(division_dir) # Once everything is combined in division_0, remove the other division - - #os.remove(join(division_zero, "det_hf.hist")) #IMPORTANT! REMOVE IF FAILS. - """ #Remove unused divisions... for division in range(1, self.divisions): @@ -476,6 +438,7 @@ def simulation_postprocessing(self): coincidence_window = self.scanner.get("coincidence_window") + #ATTENTION: AT THE MOMENT RANDOMS + LIST MODE IS NOT WELL IMPLEMENTED, NEED TO HAVE A LOOK: #NOTE: I THINK THIS PROCESS SHOULD BE DONE BEFORE REMOVING EACH DIVISION. THAT IS: EXTRACTING AND COMBINING FILES FOR EACH DIVISION. CHECK IF WORKS! #WARNING: AT THE MOMENT THIS IS NOT NEEDED. RECOVER WHEN DETLISTMODE IS USED. #simset_tools.add_randoms( @@ -499,8 +462,6 @@ def simulation_postprocessing(self): # self.simset_dir, file_list, output, log_file #) - - #self.stir_norm_from_att_map = self.scanner.get("stir_norm_from_att_map") self.attenuation_mode = self.scanner.get("attenuation_mode") if self.attenuation_mode == 1: #self.stir_norm_from_att_map != 1: @@ -509,18 +470,13 @@ def simulation_postprocessing(self): print(" ") output_atten = "attenuationsino" - #RESTABLISH IF FAILS: - #hdr_to_copy = join("trues.hdr") hdr_to_copy = join("trues.nii") - #hdr_to_copy = join("trues.nii.gz") simset_tools.simset_calcattenuation( self.simset_dir, division_zero, output_atten, hdr_to_copy, nrays=1, timeout=None ) - #TODO: To remove unnecessary files once endeded the sim. Add condition: If exists. - #TODO: Change directory name to "simuulation". IMPORTANT! REMOVE IF FAILS: - + #TODO: Add a parameter that allows to remove unnecessary files, for saving space... """ if exists(join(division_zero, "rec.weight")): os.remove(join(division_zero, "rec.weight")) @@ -538,14 +494,6 @@ def simulation_postprocessing(self): os.remove(join(division_zero, "rec.attenuation_image")) """ - #os.remove(join(division_zero, "rec.weight")) - #os.remove(join(division_zero, "rec.act_indexes")) - #os.remove(join(division_zero, "rec.activity_image")) - #os.remove(join(division_zero, "rec.att_indexes")) - #os.remove(join(division_zero, "rec.attenuation_image")) - #os.remove(join(division_zero, "sampling_rec")) - #os.remove(join(division_zero, "attenuationsino")) - class SimSET_Reconstruction(object): """This class provides functions to reconstruct a SimSET simulation.""" @@ -583,7 +531,6 @@ def run(self): if not exists(self.output_dir): os.makedirs(self.output_dir) - #if (not exists(join(self.output_dir, "stir_sinogram.nii.gz")) or (not exists(join(self.output_dir, "stir_sinogram.nii.gz")) self.prepare_recons() self.run_recons() @@ -599,11 +546,8 @@ def prepare_recons(self): num_rings = self.scanner.get("num_rings") max_segment = self.scanner.get("max_segment") - - #self.stir_norm_from_att_map = self.scanner.get("stir_norm_from_att_map") self.attenuation_mode = self.scanner.get("attenuation_mode") - #if ((self.stir_norm_from_att_map != 1) and (not exists(join(self.input_dir, "attenuationsino.nii")))): if ((self.attenuation_mode == 1) and (not exists(join(self.input_dir, "attenuationsino.nii")))): print("Attenuation map was not computed: Calculating attenuation map...") @@ -646,7 +590,6 @@ def prepare_recons(self): ) if self.add_randoms == 1: - #tools.operate_single_image( tools.operate_single_image_nii( randoms_sino, "mult", @@ -655,13 +598,13 @@ def prepare_recons(self): self.log_file, #check_nans = False ) - #tools.operate_images_analyze( + tools.operate_sinograms_nii( my_simset_sino, corr_randoms_sino, my_simset_sino, operation="sum", #check_nans = False, ) if self.scanner.get("stir_randoms_corr_smoothing") == 1: - #tools.operate_images_analyze( + tools.operate_sinograms_nii( scatter_sino, randoms_sino, additive_sinogram, operation="sum", #check_nans = False, ) @@ -670,7 +613,7 @@ def prepare_recons(self): else: if self.scanner.get("stir_scatt_corr_smoothing") == 1: - print("COPYING ADDITIVE SINO") #TODO: CHECK IF NECESSARY IN FUNCTION OF stir_scatt_corr_smoothing: + print("COPYING ADDITIVE SINO") if not exists(additive_sinogram): tools.copy_nifti(scatter_sino, additive_sinogram) print("SMOOTHING ADDITIVE SINO") @@ -686,8 +629,8 @@ def prepare_recons(self): if not exists(sinogram_stir_nii): tools.convert_simset_sino_to_stir_nii(my_simset_sino, sinogram_stir_nii) - tools.copy_sinogram_stir_to_output(sinogram_stir_nii, sinogram_stir_s) #WARNING! GET BACK IF NEEDED. - #tools.copy_reduced_sinogram_stir_to_output(sinogram_stir_nii, sinogram_stir_s, num_rings, max_segment) + tools.copy_sinogram_stir_to_output(sinogram_stir_nii, sinogram_stir_s) + #tools.copy_reduced_sinogram_stir_to_output(sinogram_stir_nii, sinogram_stir_s, num_rings, max_segment) #NOTE: Needed for FBP stir_tools.create_stir_hs_from_detparams( self.scanner, sinogram_stir_hs @@ -705,8 +648,8 @@ def prepare_recons(self): if not exists(additivesino_stir_nii): tools.convert_simset_sino_to_stir_nii(additive_sinogram, additivesino_stir_nii) - tools.copy_sinogram_stir_to_output(additivesino_stir_nii, additivesino_stir_s) #WARNING! GET BACK IF NEEDED. - #tools.copy_reduced_sinogram_stir_to_output(additivesino_stir_nii, additivesino_stir_s, num_rings, max_segment) + tools.copy_sinogram_stir_to_output(additivesino_stir_nii, additivesino_stir_s) + #tools.copy_reduced_sinogram_stir_to_output(additivesino_stir_nii, additivesino_stir_s, num_rings, max_segment) #NOTE: Needed for FBP stir_tools.create_stir_hs_from_detparams( self.scanner, additivesino_stir_hs @@ -717,27 +660,24 @@ def prepare_recons(self): att_stir_s = join(self.output_dir, "stir_att.s") att_stir_hs = join(self.output_dir, "stir_att.hs") - #if self.stir_norm_from_att_map != 1: #TODO: REFINE THIS. if self.attenuation_mode == 1: if not exists(att_stir_nii): tools.convert_simset_sino_to_stir_nii(att_sino, att_stir_nii) - tools.copy_sinogram_stir_to_output(att_stir_nii, att_stir_s) #WARNING! GET BACK IF NEEDED. - #tools.copy_reduced_sinogram_stir_to_output(att_stir_nii, att_stir_s, num_rings, max_segment) + tools.copy_sinogram_stir_to_output(att_stir_nii, att_stir_s) + #tools.copy_reduced_sinogram_stir_to_output(att_stir_nii, att_stir_s, num_rings, max_segment) #NOTE: Needed for FBP stir_tools.create_stir_hs_from_detparams( self.scanner, att_stir_hs ) - #OBTAIN MUMAP DIRECTLY FROM ATTMAP: - #if self.stir_norm_from_att_map == 1: + #Obtain Mu-map directly from att map: + if self.attenuation_mode == 2: - #if exists(att_stir_nii): #inactive at the moment but this is very time consuming... - - if not exists(att_stir_nii): #Added to save time... Remove if does not work. + if not exists(att_stir_nii): #Added to save time... Remove if this gives problems. nib.save(nib.load(join(self.input_dir, 'stir_sinogram.nii')), join(self.input_dir, 'stir_sinogram.hdr')) #DELETE, this is only for testing... @@ -759,20 +699,19 @@ def prepare_recons(self): shutil.copy(join(self.input_dir, 'mu_map.img'), join(self.output_dir, 'mu_map.v')) tools.write_interfile_header_mu(join(self.output_dir, 'mu_map.hv'), attmap_dim[0], attmap_pixsize[0], attmap_dim[1], attmap_pixsize[1], - attmap_dim[2], attmap_pixsize[2]) #offset_z = 0) #offset_z = None) #offset_z = -0.936025*128) + attmap_dim[2], attmap_pixsize[2]) tools.write_fwdproj_parfile(join(self.output_dir, "fwdproj_par.par")) - #This works but removed for logging. - #ACF_command = "%s --ACF %s %s %s %s" % (join(self.stir_dir, "bin", "calculate_attenuation_coefficients"), join(self.output_dir, "stir_att.hs"), join(self.output_dir, 'mu_map.hv'), sinogram_stir_hs, join(self.output_dir, "fwdproj_par.par")) - #TODO: ADD LOGGING FOR ERROR MESSAGES AS WELL AS PRINTING TO TERMINAL, JUST LIKE HERE: + + #Command to compute ACF + #NOTE: For total-body this is crashing at some point of the execution... Wait for further versions to be solved. ACF_command = "%s --ACF %s %s %s %s 2>&1 | tee %s" % (join(self.stir_dir, "bin", "calculate_attenuation_coefficients"), join(self.output_dir, "stir_att.hs"), join(self.output_dir, 'mu_map.hv'), sinogram_stir_hs, join(self.output_dir, "fwdproj_par.par"), join(self.output_dir, "att_logging.log")) #TODO: Still have to think about this... Way to save the nifti to the simulation folder. os.system(ACF_command) - #Added to save time and keep the stir_att... Remove if does not work. shutil.copyfile(join(self.output_dir, "stir_att.s"), join(self.input_dir, "stir_att.img")) os.rename(join(self.input_dir, 'stir_sinogram.hdr'), join(self.input_dir, 'stir_att.hdr')) @@ -792,17 +731,14 @@ def prepare_recons(self): else: - tools.copy_sinogram_stir_to_output(att_stir_nii, att_stir_s) #WARNING! GET BACK IF NEEDED. + tools.copy_sinogram_stir_to_output(att_stir_nii, att_stir_s) stir_tools.create_stir_hs_from_detparams( self.scanner, att_stir_hs, output_format = "STIR" ) - - - - #EDIT IN FUTURE: + #WARNING: This has not been revised yet for the new version. if self.scanner.get("analytical_att_correction") == 1: catt_sino = join(self.output_dir, "catt_sinogram.hdr") tools.operate_images_analyze( @@ -817,7 +753,7 @@ def prepare_recons(self): shutil.copy(catt_add_sino[0:-3] + "img", additive_sino_stir[0:-3] + "s") if self.scanner.get("psf_value") != 0: - #stir_tools.apply_psf(self.scanner, sinogram_stir, self.log_file) #RECOVER IF NOT WORKING. + #stir_tools.apply_psf(self.scanner, sinogram_stir, self.log_file) #Original, recover if not working. stir_tools.apply_psf(self.scanner, join(self.output_dir, "stir_sinogram.nii"), self.log_file) @@ -838,7 +774,7 @@ def run_recons(self): additive_sino_stir = join(self.output_dir, "stir_additivesino.hs") att_stir = join(self.output_dir, "stir_att.hs") - #if self.scanner.get("stir_norm_from_att_map") != 1: + if self.scanner.get("attenuation_mode") != 0: if any( diff --git a/src/simset/simset_tools.py b/src/simset/simset_tools.py index c575a228..54f5c588 100644 --- a/src/simset/simset_tools.py +++ b/src/simset/simset_tools.py @@ -335,15 +335,13 @@ def make_simset_bin( f.write("INT num_e2_bins = 1\n") f.write(real + "min_e = " + min_e + "\n") f.write(real + "max_e = " + max_e + "\n") - f.write("INT weight_image_type = 2\n") #BEFORE: 3. WARNING: WORKS WITH 2. CHANGED TO 1 JUST TO TRY WHAT HAPPENS. - #f.write("INT weight_image_type = 2\n") #CAREFUL. CHANGED FOR TOTAL-BODY!! - f.write("INT count_image_type = 2\n") #WARNING! WORKS WITH 2. CHANGED TO 1 JUST TO TRY WHAT HAPPENS. + f.write("INT weight_image_type = 2\n") #Originally was 3. Change to 2 for optimisation. + f.write("INT count_image_type = 2\n") #Originally was 3. Change to 2 for consistence with weight image. f.write("BOOL add_to_existing_img = false\n") - f.write("BOOL sum_according_to_type = true\n") #WARNING: ADDED FOR TOTAL-BODY. DELETE IF DETECTING STRANGE BEHAVIOUR. + f.write("BOOL sum_according_to_type = true\n") #Originally was false. Change to true for optimisation. if list_mode == False: #Uses the param if no listmode is used. Otherwise does not write weights (RAM saving). - f.write(string + 'weight_image_path = "' + rec_weight_file + '"\n') #WARNING: REMOVED JUST FOR TESTING. HAS TO BE THERE. - + f.write(string + 'weight_image_path = "' + rec_weight_file + '"\n') f.close() if log_file: @@ -425,7 +423,7 @@ def make_simset_cyl_det(scanner_params, output, sim_dir, det_hf=0, log_file=Fals + "INT cyln_num_layers = 1 \n" + "LIST cyln_layer_info_list = 4 \n" + "BOOL cyln_layer_is_active = FALSE \n" - + "INT cyln_layer_material = 0 \n" #WARNING: WE TRIED ALUMINIUM HERE (20) AND LEAD (8), did not work. RETURN TO AIR (0). + + "INT cyln_layer_material = 0 \n" #NOTE: if needed to change the gap material, change it here: e.g., Lead (8), Aluminium (20). This has to be to 0 by default (air). + "REAL cyln_layer_inner_radius = %s \n" % cyln_inner_radius + "REAL cyln_layer_outer_radius = %s \n" % cyln_outer_radius + "REAL cyln_min_z = %s \n" % ring_zmax @@ -457,90 +455,6 @@ def make_simset_cyl_det(scanner_params, output, sim_dir, det_hf=0, log_file=Fals tools.log_message(log_file, message, "info") -""" #maintain just in case... -def make_simset_cyl_det(scanner_params, output, sim_dir, det_hf=0, log_file=False): - num_rings = scanner_params.get("num_rings") - z_crystal_size = scanner_params.get("z_crystal_size") - axial_fov = scanner_params.get("axial_fov") - max_z = axial_fov / 2 - min_z = -axial_fov / 2 - - - gap_z_size = (max_z - min_z - z_crystal_size * num_rings) - cyln_inner_radius = scanner_params.get("scanner_radius") - cyln_outer_radius = cyln_inner_radius + scanner_params.get("crystal_thickness") - energy_resolution = scanner_params.get("energy_resolution") - timing_resolution = scanner_params.get("timing_resolution") - material = scanner_params.get("simset_material") - - nrings_total = num_rings + 1 - - new_file = open(output, "w") - new_file.write( - "ENUM detector_type = cylindrical \n\n" - + "# This detector example has %s axial rings \n" - % (num_rings) - + "INT cyln_num_rings = %s \n\n" % nrings_total - ) - - for i in range(1, num_rings + 1): - ring_zmin = min_z + (i - 1) * z_crystal_size #+ (i - 1) * gap_z_size - ring_zmax = ring_zmin + z_crystal_size - gap_zmax = ring_zmin + z_crystal_size + gap_z_size - - new_file.write( - "# RING #%s \n" % i - + "# The following defines the ring parameters \n" - + "LIST cyln_ring_info_list = 5 \n" - + "INT cyln_num_layers = 1 \n" - + "LIST cyln_layer_info_list = 4 \n" - + "BOOL cyln_layer_is_active = TRUE \n" - + "INT cyln_layer_material = %s \n" % material - + "REAL cyln_layer_inner_radius = %s \n" % cyln_inner_radius - + "REAL cyln_layer_outer_radius = %s \n" % cyln_outer_radius - + "REAL cyln_min_z = %s \n" % ring_zmin - + "REAL cyln_max_z = %s \n\n" % ring_zmax - ) - - if i == num_rings: - new_file.write( - "# GAP #%s \n" % i - + "# The following defines the gap parameters \n" - + "LIST cyln_ring_info_list = 5 \n" - + "INT cyln_num_layers = 1 \n" - + "LIST cyln_layer_info_list = 4 \n" - + "BOOL cyln_layer_is_active = FALSE \n" - + "INT cyln_layer_material = 0 \n" - + "REAL cyln_layer_inner_radius = %s \n" % cyln_inner_radius - + "REAL cyln_layer_outer_radius = %s \n" % cyln_outer_radius - + "REAL cyln_min_z = %s \n" % ring_zmax - + "REAL cyln_max_z = %s \n\n" % max_z - ) - - new_file.write( - "REAL reference_energy_keV = 511.0 \n" - + "REAL energy_resolution_percentage = %s \n" % energy_resolution - + "REAL photon_time_fwhm_ns = %s \n" % timing_resolution - ) - if det_hf == 1: - new_file.write( - 'STR history_file = "' + join(sim_dir, "det_hf.hist" + '"\n') - ) - - new_file.close() - - if log_file: - message = ( - "Created det_file with:\n" - + "Cristal z: %s cm\n" % z_crystal_size - + "Gap size: %s cm\n" % gap_z_size - + "Ring thickness: %s cm" % (cyln_outer_radius - cyln_inner_radius) - + "Energy resolution: %s" % energy_resolution - + "Timing resolution: %s" % timing_resolution - ) - - tools.log_message(log_file, message, "info") -""" def make_index_file(simulation_dir, simset_dir, log_file=False): output = join(simulation_dir, "index_file.log") @@ -595,13 +509,8 @@ def process_weights(weights_file, output_dir, scanner, add_randoms=0): trues_start = Simset_offset trues_end = Simset_offset + block_size trues_file = join(output_dir, "w1") - - #WARNING: THIS IS THE OLD CODE (READS FULL FILE): - #with open(weights_file, "rb") as in_file: - # with open(trues_file, "wb") as out_file: - # out_file.write(in_file.read()[trues_start:trues_end]) - #TODO: CHECK IF THIS WORKS (READS ONLY PER CHUNKS, USEFUL FOR TOTAL-BODY): + #This is much faster than originally opening and writing a file: with open(weights_file, "rb") as in_file: with open(trues_file, "wb") as out_file: in_file.seek(trues_start) @@ -609,7 +518,6 @@ def process_weights(weights_file, output_dir, scanner, add_randoms=0): output = join(output_dir, "trues.nii") - #output = join(output_dir, "trues.nii.gz") tools.create_nifti_from_imgdata( trues_file, output, nbins, nangles, nslices, 1, 1, 1, "fl" @@ -621,43 +529,34 @@ def process_weights(weights_file, output_dir, scanner, add_randoms=0): scatter_end = trues_end + block_size scatter_file = join(output_dir, "w2") - #WARNING: THIS IS THE OLD CODE (READS FULL FILE): - #with open(weights_file, "rb") as in_file: - # with open(scatter_file, "wb") as out_file: - # out_file.write(in_file.read()[scatter_start:scatter_end]) - - #TODO: CHECK IF THIS WORKS (READS ONLY PER CHUNKS, USEFUL FOR TOTAL-BODY): + #This is much faster than originally opening and writing a file: with open(weights_file, "rb") as in_file: with open(scatter_file, "wb") as out_file: in_file.seek(scatter_start) out_file.write(in_file.read(block_size)) output = join(output_dir, "scatter.nii") - #output = join(output_dir, "scatter.nii.gz") + tools.create_nifti_from_imgdata( scatter_file, output, nbins, nangles, nslices, 1, 1, 1, "fl" ) os.remove(scatter_file) - #SHOULD CHECK IF THIS WORKS: + #TODO: Still few things need to be tested in new version... if add_randoms == 1: randoms_start = scatter_end randoms_end = scatter_end + block_size randoms_file = join(output_dir, "w3") - #WARNING: THIS IS THE OLD CODE (READS FULL FILE): - #with open(weights_file, "rb") as in_file: - # with open(randoms_file, "wb") as out_file: - # out_file.write(in_file.read()[randoms_start:randoms_end]) - #TODO: CHECK IF THIS WORKS (READS ONLY PER CHUNKS, USEFUL FOR TOTAL-BODY): + #This is much faster than originally opening and writing a file: with open(weights_file, "rb") as in_file: with open(randoms_file, "wb") as out_file: in_file.seek(randoms_start) out_file.write(in_file.read(block_size)) output = join(output_dir, "randoms.nii") - #output = join(output_dir, "randoms.nii.gz") + tools.create_nifti_from_imgdata( randoms_file, output, nbins, nangles, nslices, 1, 1, 1, "fl" ) @@ -737,10 +636,7 @@ def combine_history_files(simset_dir, history_files, output, log_file): stdin=sp.PIPE, stdout=sp.PIPE, stderr=sp.PIPE, - ).communicate("No\n") #No: Do not delete inputs. - #).communicate("Yes\n") #old... - - # tools.osrun(rcommand, log_file) + ).communicate("No\n") #This was originally to Yes, set to No (delete inputs?) for optimisation.. def simset_calcattenuation( @@ -762,23 +658,12 @@ def simset_calcattenuation( child.wait() - #RESTABLISH IF FAILS: - #CHUNK_SIZE = os.path.getsize(hdr_to_copy[0:-3] + "img") - #print(CHUNK_SIZE) - #with open(output, "rb") as f: - # chunk = f.read(CHUNK_SIZE) - #with open(output + ".img", "wb") as chunk_file: - # chunk_file.write(chunk) - # chunk_file.close() - - #shutil.copy(hdr_to_copy, output + ".hdr") - nib.save(nib.load(hdr_to_copy), hdr_to_copy[0:-3] + "hdr") #TRUES AUXILIAR IMAGE - #nib.save(nib.load(hdr_to_copy), hdr_to_copy[0:-6] + "hdr") #TRUES AUXILIAR IMAGE + nib.save(nib.load(hdr_to_copy), hdr_to_copy[0:-3] + "hdr") CHUNK_SIZE = os.path.getsize(hdr_to_copy[0:-3] + "img") - #CHUNK_SIZE = os.path.getsize(hdr_to_copy[0:-6] + "img") + with open(output, "rb") as f: chunk = f.read(CHUNK_SIZE) with open(output + ".img", "wb") as chunk_file: @@ -786,37 +671,14 @@ def simset_calcattenuation( chunk_file.close() - #shutil.copy(hdr_to_copy[0:-6] + "hdr", output + ".hdr") shutil.copy(hdr_to_copy[0:-3] + "hdr", output + ".hdr") - #nib.save(nib.load(output + ".hdr"), output + ".nii.gz") nib.save(nib.load(output + ".hdr"), output + ".nii") #Remove unnecessary files: - #os.remove(hdr_to_copy[0:-6] + "hdr") - #os.remove(hdr_to_copy[0:-6] + "img") os.remove(hdr_to_copy[0:-3] + "hdr") os.remove(hdr_to_copy[0:-3] + "img") os.remove(output + ".hdr") os.remove(output + ".img") - - - - - #NOT WORKING: - """ - #CHUNK_SIZE = os.path.getsize(hdr_to_copy[0:-3] + "nii") - CHUNK_SIZE = os.path.getsize(hdr_to_copy[0:-6] + "nii.gz") - #print(CHUNK_SIZE) - with open(output, "rb") as f: - chunk = f.read(CHUNK_SIZE) - #with open(output + ".nii", "wb") as chunk_file: - with open(output + ".nii.gz", "wb") as chunk_file: - chunk_file.write(chunk) - chunk_file.close() - - #shutil.copy(hdr_to_copy, output + ".nii") - shutil.copy(hdr_to_copy, output + ".nii.gz") - """ os.chdir(current_dir) From 69a1c8f6bc873ebc060f533c0d3089afe53c20a9 Mon Sep 17 00:00:00 2001 From: arnaufme8 <113554412+arnaufme8@users.noreply.github.com> Date: Tue, 21 Jul 2026 11:24:10 +0200 Subject: [PATCH 21/23] Add files via upload --- src/stir/stir_tools.py | 63 ++++++++---------------------------------- 1 file changed, 11 insertions(+), 52 deletions(-) diff --git a/src/stir/stir_tools.py b/src/stir/stir_tools.py index 65f9bac3..13b6cab8 100644 --- a/src/stir/stir_tools.py +++ b/src/stir/stir_tools.py @@ -8,7 +8,7 @@ from utils import tools from utils import resources as rsc -#OLD VERSION. MUST BE UPDATED FOR STIR 6.0... +#ATTENTION: Old version, must be updated for STIR >6.0 def create_stir_hs_from_detparams(scannerParams, output_file, output_format="SimSET"): num_rings = scannerParams.get("num_rings") max_z = scannerParams.get("axial_fov") / 2 @@ -23,10 +23,10 @@ def create_stir_hs_from_detparams(scannerParams, output_file, output_format="Sim max_td = scannerParams.get("scanner_radius") td_bins = scannerParams.get("num_td_bins") bin_size = (max_td - min_td) / float(td_bins) - matrix_size, ring_difference = generate_segments_lists_stir( #WARNING! RECOVER THIS IF STOPS WORKING. + matrix_size, ring_difference = generate_segments_lists_stir( num_rings, num_rings - 1 ) - #matrix_size, ring_difference = generate_segments_lists_stir( #WARNING! THIS IS NEEDED FOR FBP3D + #matrix_size, ring_difference = generate_segments_lists_stir( #NOTE: This will be needed for FBP # num_rings, max_segment #) transaxial_crystal_distance = scannerParams.get("transaxial_crystal_size") @@ -65,7 +65,7 @@ def create_stir_hs_from_detparams(scannerParams, output_file, output_format="Sim + "number of dimensions := 4\n" + "matrix axis label [4] := segment\n" + "!matrix size [4] := " - + str(2 * (num_rings - 1) + 1) #WARNING! RECOVER THIS: str(2 * (num_rings - 1) + 1) #WARNING! THIS IS NEEDED FOR FBP3D: str(2 * (max_segment) + 1) + + str(2 * (num_rings - 1) + 1) #WARNING: For FBP we need to do: str(2 * (max_segment) + 1) + "\n" + "matrix axis label [" + str(views_coordinate) @@ -166,12 +166,12 @@ def generate_segments_lists_stir(nrings, max_segment): my_matrix_ring_difference = " " for i in range(last_segment_sinograms, nrings): my_matrix_size = my_matrix_size + str(i) + "," - my_matrix_ring_difference = my_matrix_ring_difference + str(i - nrings) + "," #WARNING! RECOVER IF FAILS. - #my_matrix_ring_difference = my_matrix_ring_difference + str(nrings - i) + "," #NOTE: suspect rings are inverted. + my_matrix_ring_difference = my_matrix_ring_difference + str(i - nrings) + "," + #my_matrix_ring_difference = my_matrix_ring_difference + str(nrings - i) + "," #NOTE: in case rings are invented. for i in range(nrings, last_segment_sinograms - 1, -1): my_matrix_size = my_matrix_size + str(i) + "," - my_matrix_ring_difference = my_matrix_ring_difference + str(nrings - i) + "," #WARNING! RECOVER IF FAILS. - #my_matrix_ring_difference = my_matrix_ring_difference + str(i - nrings) + "," #NOTE: suspect rings are inverted. + my_matrix_ring_difference = my_matrix_ring_difference + str(nrings - i) + "," + #my_matrix_ring_difference = my_matrix_ring_difference + str(i - nrings) + "," #NOTE: in case rings are invented. my_matrix_size = "{" + my_matrix_size[0:-1] + "}" my_matrix_ring_difference = "{" + my_matrix_ring_difference[0:-1] + "}" @@ -322,10 +322,9 @@ def FBP2D_recons(config, scannerParams, sinograms_stir, output_dir, log_file): tools.osrun(command, log_file) output = recFileName + ".hv" - #output = tools.anything_to_hdr_convert(output, log_file) output = tools.convert_hv_to_nii(output, log_file) - tools.flip_rec_nifti(recFileName + ".nii", join(output_dir, "reconstruction.nii")) #WARNING! REMOVE IF THIS FAILS. THIS FLIPS THE FINAL REC IN 0 AXIS + tools.flip_rec_nifti(recFileName + ".nii", join(output_dir, "reconstruction.nii")) #TODO: We need to check the flips done here. Final reconstruction is flipped from maps (due to sinogram flipping). return output @@ -382,7 +381,6 @@ def FBP3D_recons(config, scannerParams, sinograms_stir, output_dir, log_file): tools.osrun(command, log_file) output = recFileName + ".hv" - #output = tools.anything_to_hdr_convert((output)) output = tools.convert_hv_to_nii(output, log_file) tools.flip_rec_nifti(recFileName + ".nii", join(output_dir, "reconstruction.nii")) #WARNING! REMOVE IF THIS FAILS. THIS FLIPS THE FINAL REC IN 0 AXIS @@ -457,7 +455,7 @@ def OSEM2D_recons( zoom_aux = 1 xyOutputSize_aux = round(xyOutputSize / zoom) - if scannerParams.get("analytical_att_correction") == 1: #CHECK THIS!!!! + if scannerParams.get("analytical_att_correction") == 1: att_corr_str = "" elif scannerParams.get("stir_recons_att_corr") == 1: att_corr_str = ( @@ -600,35 +598,7 @@ def OSEM3D_recons( numberOfIterations = scannerParams.get("numberOfIterations") savingInterval = scannerParams.get("savingInterval") - """ #RESTABLISH IF FAILS - if scannerParams.get("stir_recons_att_corr") == 1: - att_corr_str = ( - "Bin Normalisation type := From ProjData \n" - + "Bin Normalisation From ProjData := \n" - + "normalisation projdata filename:= " - + att_stir - + "\n" - + "End Bin Normalisation From ProjData:= \n" - ) - else: - att_corr_str = "" - """ - - #THIS IS TO CORRECT FROM IMG DIRECTLY, SEEMS TO NOT BE NECESSARY: - """ - if scannerParams.get("stir_norm_from_att_map") == 1: - att_corr_str = ( - #"Bin Normalisation From Attenuation Image:= \n" - "Bin Normalisation type := From Attenuation Image \n" - + "Bin Normalisation From Attenuation Image:= \n" - + "attenuation_image_filename := " - + join(output_dir, 'mu_map.hv') - + "\n" - + "End Bin Normalisation From Attenuation Image := \n" - ) - """ - #WARNING: UNIFY IF STIR_ATT IS USED. - #if scannerParams.get("stir_norm_from_att_map") == 1: + #TODO: To unify these blocks if stir_att file with same name is generated with both methods. if scannerParams.get("attenuation_mode") == 1: att_corr_str = ( "Bin Normalisation type := From ProjData \n" @@ -754,22 +724,11 @@ def OSEM3D_recons( new_file.close() - #RESTABLISH IF FAILS!!!! command = "%s %s >> %s" % (recons, paramsFile, log_file) tools.osrun(command, log_file) - #recon=stir.OSMAPOSLReconstruction3DFloat(paramsFile) - - #print("ATTRIBUTES:", dir(recon)) - - #target=recon.get_initial_data() - #target.fill(1) - #s=recon.set_up(target) - #recon.reconstruct(target) - output = recFileName + "_" + str(scannerParams.get("numberOfIterations")) + ".hv" - #output = tools.anything_to_hdr_convert(output, log_file) output = tools.convert_hv_to_nii(output, log_file) tools.flip_rec_nifti(recFileName + "_" + str(scannerParams.get("numberOfIterations")) + ".nii", join(output_dir, "reconstruction.nii")) #WARNING! REMOVE IF THIS FAILS. THIS FLIPS THE FINAL REC IN 0 AXIS From ac8a2d6460756a44ad4d0863852dadc729b4a1f4 Mon Sep 17 00:00:00 2001 From: arnaufme8 <113554412+arnaufme8@users.noreply.github.com> Date: Tue, 21 Jul 2026 11:24:25 +0200 Subject: [PATCH 22/23] Add files via upload --- utils/tools.py | 148 +++++++------------------------------------------ 1 file changed, 21 insertions(+), 127 deletions(-) diff --git a/utils/tools.py b/utils/tools.py index 63372c86..3bb5edf5 100644 --- a/utils/tools.py +++ b/utils/tools.py @@ -53,7 +53,7 @@ def nib_load(image, logfile=False): else: print(message) -def copy_analyze(image1, image2=False, dest_dir=False, logfile=False): +def copy_analyze(image1, image2=False, dest_dir=False, logfile=False): #NOTE: Potentially unused. """ Create a copy of an Analyze format image :param image1: (string) path to the original image @@ -490,7 +490,7 @@ def convert_hv_to_nii(image, logfile=False, outfile=False): -def prepare_input_image(image_hdr, logfile, min_voxel_size=1): +def prepare_input_image(image_hdr, logfile, min_voxel_size=1): #TODO: This may be optimised. """ This method converts input_image to float data type, re-sizes the image to 1mm size voxels if too large to keep a reasonable analysis execution time and @@ -588,7 +588,7 @@ def verify_roi_exists(rois_image, roi_number): else: return True -def operate_single_image(input_image, operation, factor, output_image, logfile): +def operate_single_image(input_image, operation, factor, output_image, logfile): #NOTE: Potentially unused. """ Given an input image, multiply or divide it by a numerical factor saving the result as output_image @@ -673,7 +673,7 @@ def operate_single_image_nii(input_image, operation, factor, output_image, logfi nib.save(nifti_img,output_image) -def operate_images_analyze(image1, image2, out_image, operation='mult'): +def operate_images_analyze(image1, image2, out_image, operation='mult'): #NOTE: Potentially unused. """ Given the input images, calculate the multiplication image or the ratio between them :param image1: string, path to the first image @@ -730,8 +730,7 @@ def operate_images_nii(image1, image2, out_image, operation='mult', check_nans=T #data2 = np.nan_to_num(data2) #re-added """ - if check_nans: - # TODO CHECK IF NEGATIVE VALUES NEED TO BE REMOVED + if check_nans: #TODO: Implement this as may be needed. At the moment no NaNs are checked. # Remove NaN and negative values #data1 = np.nan_to_num(data1) #data2 = np.nan_to_num(data2) @@ -977,7 +976,7 @@ def ncounts(image_hdr): ncounts = np.sum(data) return ncounts -def convert_simset_sino_to_stir(input_img, output=False): +def convert_simset_sino_to_stir(input_img, output=False): #NOTE: Potentially unused. ## To be continued.... @@ -1043,46 +1042,16 @@ def convert_simset_sino_to_stir_nii(input_img, output=False): output_definition = sorted(input_definition, key=itemgetter(3)) stir_img_data = np.empty(shape, dtype=np.float32, order='C') - #stir_img_data_flip_x = np.empty(shape, dtype=np.float32, order='C') for i in range(n_slices): output_index = output_definition[i][0] input_slice = simset_img_data[:,:,output_index] stir_img_data[:,:,i] = input_slice - - #for j in range(n_x): - # stir_img_data_flip_x[j,:,:] = stir_img_data[n_x-1-j, :, :] - - #WARNING WARNING WARNING: WE COMMENT THAT FOR TESTING, MAY NEED TO COME BACK! - #NOTE: POTENTIALLY, WE NEED TO FLIP IN Y. (if so, merge flips) - #NOTE: POTENTIALLY z IS ALSO FLIPPED RESPECT TO ATT BUT THEN WE NEED TO CHANGE SEGMENT SIGNS AS WELL! - - #stir_img_data_flip_x = stir_img_data[::-1,:,:] - #stir_img_data_flip_x = stir_img_data_flip_x[:,::-1,:] - stir_img_data_flip = stir_img_data[::-1,::-1,:] #WARNING: THIS IS TEMPORAL - #stir_img_data_flip = stir_img_data[::-1,::-1,::-1] #WARNING: THIS IS TEMPORAL - #stir_img_data_flip_x = stir_img_data - #stir_img = nib.AnalyzeImage(stir_img_data, simset_img.affine, simset_img.header) - #stir_img = nib.Nifti2Image(stir_img_data_flip_x, simset_img.affine, simset_img.header) - stir_img = nib.Nifti2Image(stir_img_data_flip, simset_img.affine, simset_img.header) - - if not output: - output = input_img [0:-4] + '_stir.nii' - - nib.save(stir_img,output) - - - - -""" #WARNING: THIS WAS CORRECT. BUT HAS BEEN REMOVED TO ENSURE ORDER IN SINOGRAMS IS CORRECT. -def convert_simset_sino_to_stir_nii(input_img, output=False): - - simset_img = nib.load(input_img) - simset_img_data = simset_img.get_fdata(dtype=np.float32) #.astype(np.float32) #WARNING! CHANGE DATA TYPE HERE (TO FLOAT32?) - shape = simset_img_data.shape - + + #TODO: THIS CAN BE SURELY SPEED-UP (FOR TOTAL-BODY), CHECK THIS: + """ n_slices = shape[2] nrings = int(np.sqrt(n_slices)) # assuming perfect square n_x = shape[0] @@ -1103,90 +1072,24 @@ def convert_simset_sino_to_stir_nii(input_img, output=False): # Reorder slices in one vectorized step order = output_definition[:, 0].astype(int) stir_img_data = simset_img_data[:, :, order] - - # Flip X dimension - stir_img_data_flip_x = stir_img_data[::-1, :, :] - - # Save - stir_img = nib.Nifti2Image(stir_img_data_flip_x, simset_img.affine, simset_img.header) - - if not output: - output = input_img[:-7] + '_stir.nii.gz' - - nib.save(stir_img, output) -""" - - - -""" -def convert_simset_sino_to_stir_nii(input_img, output=False): - - simset_img = nib.load(input_img) - simset_img_data = simset_img.get_data() #simset_img.get_fdata() - shape = simset_img_data.shape - - n_slices = shape[2] - nrings = int(np.sqrt(n_slices)) - - indices = np.arange(n_slices) - ring1 = indices // nrings - ring2 = indices % nrings - segment = ring1 - ring2 - - order = np.argsort(segment) - - stir_img_data = simset_img_data[:, :, order] - stir_img_data = stir_img_data[::-1, :, :] #flip_x - - stir_img = nib.Nifti2Image(stir_img_data, simset_img.affine, simset_img.header) - - if not output: - output = input_img[:-7] + '_stir.nii.gz' - - nib.save(stir_img, output) -""" - - -""" -def convert_simset_sino_to_stir_nii(input_img, output): - - simset_img = nib.load(input_img) - data = simset_img.dataobj # memory-mapped + """ - print("Check 1") - shape = simset_img.shape - n_slices = shape[2] - nrings = int(np.sqrt(n_slices)) - - indices = np.arange(n_slices) - ring1 = indices // nrings - ring2 = indices % nrings - segment = ring1 - ring2 - order = np.argsort(segment) + #WARNING: This is still being investigated. At the moment we suspect SimSET sinograms are inverted in x and y respect to STIR. May be also inverted in z. - print("Check 2") - - # Preallocate output (float32 recommended if safe) - out_data = np.empty(shape, dtype=data.dtype) - - for new_z, old_z in enumerate(order): - out_data[:, :, new_z] = data[:, :, old_z] + #stir_img_data_flip = stir_img_data[::-1,:,:] + stir_img_data_flip = stir_img_data[::-1,::-1,:] #WARNING: THIS IS TEMPORAL - print("Check 3") + stir_img = nib.Nifti2Image(stir_img_data_flip, simset_img.affine, simset_img.header) - # Flip in-place - out_data[:] = out_data[::-1, :, :] + if not output: + output = input_img [0:-4] + '_stir.nii' - print("Check 4") + nib.save(stir_img,output) - stir_img = nib.Nifti2Image(out_data, simset_img.affine, simset_img.header) - nib.save(stir_img, output) - print("Check 5") -""" -def copy_sinogram_stir_to_output(input_img, output_img): +def copy_sinogram_stir_to_output(input_img, output_img): simset_sino = nib.load(input_img) @@ -1206,19 +1109,9 @@ def copy_sinogram_stir_to_output(input_img, output_img): else: print("Image is not in the NifTi format!") - - #Re-added: - #shutil.copy(input_img[0:-3] + "nii", output_img) - #os.remove(input_img[0:-3] + "nii") - #os.remove(input_img[0:-3] + "hdr") - - #shutil.copy(input_img[0:-6] + "img", output_img) - #os.remove(input_img[0:-6] + "img") - #os.remove(input_img[0:-6] + "hdr") - #NOTE: THIS FUNCTION MAY BE DELETED IN THE FUTURE... -def copy_reduced_sinogram_stir_to_output(input_img, output_img, nrings, max_segment): +def copy_reduced_sinogram_stir_to_output(input_img, output_img, nrings, max_segment): #NOTE: This is unused for now, but is needed for FBP3D simset_sino = nib.load(input_img) sino_dataobj = simset_sino.dataobj @@ -1236,6 +1129,7 @@ def copy_reduced_sinogram_stir_to_output(input_img, output_img, nrings, max_segm os.remove(input_img[0:-3] + "hdr") +#NOTE: Following functions may be moved to a BrainVISET tools separate file... def resampleXYvoxelSizes(image_hdr, xyVoxelSize, log_file): img = nib.load(image_hdr) @@ -1558,7 +1452,7 @@ def fix_4d_data(data): return data[:, :, :, 0] -def mu_coef_511keV(tissue_n): +def mu_coef_511keV(tissue_n): #NOTE: Check if this can be done with a internal function of SimSET. rows_per_tissue = 1000 start = tissue_n * (rows_per_tissue + 1) From 3ffc70b9d6aa45300d0fadf1f654ea9580fa655f Mon Sep 17 00:00:00 2001 From: arnaufme8 <113554412+arnaufme8@users.noreply.github.com> Date: Thu, 23 Jul 2026 13:45:57 +0200 Subject: [PATCH 23/23] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7ae28793..af1c1063 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ The SIMPET project is intended to allow to setup and launch MC simulation on a s - Apply the BrainViset procedure to obtain realistic Activity and Attenuation maps. - Run Analytic simulations using STIR simulation procedure and MC simulation using SimSET. -# Installtion +# Installation 1. Install [Git LFS](https://git-lfs.com/). 2. Clone the repository by adding the `--recurse-submodules` flag: @@ -52,6 +52,7 @@ Sometimes, even activating the virtual environemnt, the shell will use the wide ``` make install ``` +_NOTE:_ At the moment, to install SimPET you need to have sudo available in your computer. If you don't (e.g., when installing on a HPC), you can make use of the makefile that does not require sudo: ```makefile-withoutsudo```. To use it, simply remove or change the name of ```makefile```, and rename the file ```makefile-withoutsudo``` to ```makefile```. This will allow the installation without root privileges. 7. Decompress dummy data with: ``` @@ -277,8 +278,9 @@ See [SimSET](https://depts.washington.edu/simset/html/simset_main.html) document #### Attenuation correction -- **analytical_att_correction**: performed by SimSET calcattenuation. +- **analytical_att_correction**: performed by SimSET calcattenuation. (_NOTE: obsolete at the moment and will be removed in further updates_). - **stir_recons_att_corr**: performed in STIR by entering the att image as a normalization map. +- **attenuation_mode**: it has three options. 0 (no attenuation), 1 (attenuation by SimSET calcattenuation) or 2 (attenuation by STIR computation of the ACFs). #### Scatter Correction