From 1e42e377ac6d5731705294cbcf38b9718d49031a Mon Sep 17 00:00:00 2001 From: Alvaro-Exposito-MTZ Date: Fri, 6 Mar 2026 14:34:13 +0000 Subject: [PATCH 1/3] Create README.md --- scripts/hackathon_scripts/synerby26/README.md | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 scripts/hackathon_scripts/synerby26/README.md diff --git a/scripts/hackathon_scripts/synerby26/README.md b/scripts/hackathon_scripts/synerby26/README.md new file mode 100644 index 0000000..da92aec --- /dev/null +++ b/scripts/hackathon_scripts/synerby26/README.md @@ -0,0 +1,20 @@ +This repository contains a complete tutorial for generating synthetic foam data and its subsequent reconstruction and denoising using the Noise2Inverse algorithm with the LION library. + +Tutorial Structure +The workflow is divided into two main steps to ensure version compatibility: + + - Creation of analytical bubble structures (Foam_env). + - Training a neural network for self-supervised denoising (lion_env). + +Create the environment with the specific version of Python for generating the Dataset, activate the enviroment and instal the necessary stuff: +conda create -n foam_env python=3.9 +conda activate foam_env +pip install foam-ct-phantom h5py numpy torch + +For installing LION: +git clone https://github.com/CambridgeCIA/LION.git +cd LION +git submodule update --init --recursive # Legacy: for MSD_pytorch_ +conda env create --file=env_base.yml --name=lion # You can change 'lion' to a different env name +conda activate lion +pip install . From b71831691c787dc6249ba2f3ac7a9fb2727eb1b1 Mon Sep 17 00:00:00 2001 From: Alvaro-Exposito-MTZ Date: Fri, 6 Mar 2026 14:36:46 +0000 Subject: [PATCH 2/3] Create Foam_Like_Dataset_Generator --- .../synerby26/Foam_Like_Dataset_Generator | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 scripts/hackathon_scripts/synerby26/Foam_Like_Dataset_Generator diff --git a/scripts/hackathon_scripts/synerby26/Foam_Like_Dataset_Generator b/scripts/hackathon_scripts/synerby26/Foam_Like_Dataset_Generator new file mode 100644 index 0000000..e1e8b64 --- /dev/null +++ b/scripts/hackathon_scripts/synerby26/Foam_Like_Dataset_Generator @@ -0,0 +1,28 @@ +import os +import numpy as np +import foam_ct_phantom + +#Activate foam_env +OUTPUT_DIR = './Dataset_Hackathon/' +NUM_SAMPLES = 30 +PIXSIZE = 0.004 + +if not os.path.exists(OUTPUT_DIR): + os.makedirs(OUTPUT_DIR) + + phantom_file = 'phantom_hackathon.h5' +if not os.path.exists(phantom_file): + foam_ct_phantom.FoamPhantom.generate(phantom_file, seed=42, n_bubbles=2000, n_crystals=5000, radius=0.1, height=1.0) + +phantom = foam_ct_phantom.FoamPhantom(phantom_file) +angles = np.linspace(0, np.pi, 720, endpoint=False) +for i in range(NUM_SAMPLES): + current_cz = 0.2 + (i * 0.02) + + gt_filename = os.path.join(OUTPUT_DIR, f'gt_{i:04d}.h5') + sino_filename = os.path.join(OUTPUT_DIR, f'sino_{i:04d}.h5') + + geom_vol = foam_ct_phantom.VolumeGeometry(512, 512, 1, float(PIXSIZE), 0.0, 0.0, float(current_cz)) + phantom.generate_volume(gt_filename, geom_vol) + geom_proj = foam_ct_phantom.ParallelGeometry(1, 900, angles, float(PIXSIZE), 0.0, 0.0, float(current_cz)) + phantom.generate_projections(sino_filename, geom_proj) From 8f8b8fe3d08592ed073fa2c0649b52567434fd9c Mon Sep 17 00:00:00 2001 From: Alvaro-Exposito-MTZ Date: Fri, 6 Mar 2026 14:37:42 +0000 Subject: [PATCH 3/3] Create Model_example_scripts --- .../synerby26/Model_example_scripts | 146 ++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 scripts/hackathon_scripts/synerby26/Model_example_scripts diff --git a/scripts/hackathon_scripts/synerby26/Model_example_scripts b/scripts/hackathon_scripts/synerby26/Model_example_scripts new file mode 100644 index 0000000..afd9d9e --- /dev/null +++ b/scripts/hackathon_scripts/synerby26/Model_example_scripts @@ -0,0 +1,146 @@ +import os +import numpy as np +import h5py +import LION.CTtools.ct_utils as ct_utils +import LION.CTtools.ct_geometry as ctgeo +from LION.utils.parameter import LIONParameter +import torch +import glob +from torch.utils.data import Dataset +from torch.utils.data import DataLoader +import torch.utils.data as data_utils +from LION.classical_algorithms.fdk import fdk +from LION.optimizers.Noise2InverseSolver import Noise2InverseSolver +from LION.models.CNNs.UNets.Unet import UNet +from torch.utils.data import DataLoader +from torch.optim.adam import Adam +import torch.nn as nn +import random + +##Activate lion_env + +device = torch.device("cuda" if torch.cuda.is_available() else "cpu") +seed = 42 +random.seed(seed) +torch.manual_seed(seed) +torch.cuda.manual_seed_all(seed) +torch.backends.cudnn.deterministic = True +torch.backends.cudnn.benchmark = False + + +OUTPUT_DIR = './Dataset_Hackathon/' +SAVE_DIR = "./trained_models" +if not os.path.exists(SAVE_DIR): + os.makedirs(SAVE_DIR) + + +class FoamLike(Dataset): + def __init__(self, mode, parameters: LIONParameter): + self.path_to_dataset = parameters.path_to_dataset + self.mode = mode + + self.target_angles = parameters.geometry.angles + self.num_target_angles = len(self.target_angles) + + all_sino = sorted(glob.glob(os.path.join(self.path_to_dataset, 'sino_*.h5'))) + all_gt = sorted(glob.glob(os.path.join(self.path_to_dataset, 'gt_*.h5'))) + + max_samples = getattr(parameters, 'max_samples', len(all_sino)) + all_sino = all_sino[:max_samples] + all_gt = all_gt[:max_samples] + + num = len(all_sino) + if mode == "train": + self.sino_files = all_sino[:int(num * 0.8)] + self.gt_files = all_gt[:int(num * 0.8)] + elif mode == "validation": + self.sino_files = all_sino[int(num * 0.8):int(num * 0.9)] + self.gt_files = all_gt[int(num * 0.8):int(num * 0.9)] + else: + self.sino_files = all_sino + self.gt_files = all_gt + + def __len__(self): + return len(self.sino_files) + + def __getitem__(self, index): + f_sino = h5py.File(self.sino_files[index], 'r') + + total_in_file = 720 + step = total_in_file // self.num_target_angles + + sino = f_sino['projs'][::step, :, 0].astype(np.float32) + sino = sino[:self.num_target_angles, :] + f_sino.close() + + f_gt = h5py.File(self.gt_files[index], 'r') + gt = f_gt['volume'][0, :, :].astype(np.float32) + f_gt.close() + + input_tensor = torch.from_numpy(sino).unsqueeze(0) + target_tensor = torch.from_numpy(gt).unsqueeze(0) + + return input_tensor, target_tensor + +#Number of angles +n_angles = 360 + +#Geometry of the experiment +geometry = ctgeo.Geometry( + image_shape=[1, 512, 512], + image_size=[300 / 512, 300, 300], + detector_shape=[1, 900], + detector_size=[1, 900], + dso=575, dsd=1050, mode="fan", + angles=np.linspace(0, 2 * np.pi, n_angles, endpoint=False), +) + +params = LIONParameter() +params.geometry = geometry +params.path_to_dataset = OUTPUT_DIR +params.max_samples = 30 + +#30 samples +samples = FoamLike(mode="all", parameters=params) + + +#Add noise to the sample sinograms +sigma_paper = 5 +sigma_blurred_paper = 0.8 +ks = int(sigma_blurred_paper * 3) * 2 + 1 + +noisy_list_y = [] +target_list = [] + +for i in range(30): + _, target = samples[i] + + sino_clean, _ = samples[i] + y=ct_utils._sinogram_add_noise(sino_clean, I0=10000, sigma=sigma_paper, sigma_blur=sigma_blurred_paper, ks_value=3) + noisy_list_y.append(y.view(1, 360, 900)) + target_list.append(target.view(1, 512, 512)) + if i == 0: + print(f"Min sino_clean: {sino_clean.min().item():.4f}") + print(f"Max sino_clean: {sino_clean.max().item():.4f}") + +sino_tensor_y = torch.stack(noisy_list_y) +target_tensor = torch.stack(target_list) +train_data = data_utils.TensorDataset(sino_tensor_y, target_tensor) +dataloader = DataLoader(train_data, batch_size=5, shuffle=True) + +#Noise2Inverse +model = UNet().to(device) +optimizer = Adam(model.parameters(), lr=1e-4) +loss_fn = nn.MSELoss() + +n2i_params = Noise2InverseSolver.default_parameters() +n2i_params.splits = 4 +n2i_params.algo = fdk +n2i_params.strategy = Noise2InverseSolver.X_one_strategy(n2i_params.splits) + +solver = Noise2InverseSolver(model, optimizer, loss_fn, n2i_params, geometry, verbose=True, device=device) + +solver.set_training(dataloader) +solver.train(500) +solver.save_final_results("final_model_N2I.pt", SAVE_DIR) +