Skip to content

Commit 32a72e8

Browse files
authored
Merge pull request FABLE-3DXRD#573 from haixing0a/master
a few clean ups and minor modifications for forward_projector etc.
2 parents 4043c84 + 6ebceb3 commit 32a72e8

3 files changed

Lines changed: 75 additions & 29 deletions

File tree

ImageD11/forward_model/forward_projector.py

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
# Jan 2nd, 2025
77
# version 1.0: updated on January 30, 2025
88
# version 1.1: fixed a bug with ray origin on December 10, 2025
9+
# version 1.2: some clean-ups and ensure all print-outs go to .out file instead of .log file, April 12, 2026
910

1011
import numpy as np
1112
from matplotlib import pyplot as plt
@@ -39,7 +40,11 @@
3940
from ImageD11.nbGui.nb_utils import slurm_submit_and_wait
4041

4142
econst = 12.3984
42-
logging.basicConfig(level=logging.INFO, force=True)
43+
logging.basicConfig(
44+
level=logging.INFO,
45+
stream=sys.stdout,
46+
force=True
47+
)
4348
os.environ["JOBLIB_TEMP_FOLDER"] = "/tmp_14_days/slurm/log"
4449

4550
"""
@@ -380,14 +385,14 @@ def set_all(self):
380385
self.check_halfy()
381386

382387
def check_halfy(self):
383-
#quick check to see if halfy is big enough
388+
# quick check to see if halfy is big enough to include all sample voxels to be illuminated
384389
if self.sample_mask is None:
385390
mask_tmp = (self.sample_input.DS['labels'] > -1) & (~np.isnan(self.sample_input.DS['U'][:, :, :, 0, 0]))
386391
mask_tmp = np.transpose(mask_tmp, (2, 1, 0))
387392
else:
388393
mask_tmp = self.sample_mask
389394
center = (mask_tmp.shape[0]/2, mask_tmp.shape[1]/2)
390-
radius = self.args["halfy"]/np.mean(self.sample_input.DS['voxel_size']) #radius covered by halfy, in pixels
395+
radius = self.args["halfy"]/np.mean(self.sample_input.DS['voxel_size']) # radius covered by halfy, [pixel]
391396
x = np.linspace(0, mask_tmp.shape[0], mask_tmp.shape[1])
392397
y = np.linspace(0, mask_tmp.shape[1], mask_tmp.shape[1])
393398
xv, yv = np.meshgrid(x, y)
@@ -396,8 +401,9 @@ def check_halfy(self):
396401
combine_mask = mask_circle*mask_tmp[:,:,0]
397402
if self.verbose >= 1 and np.sum(combine_mask)>=1:
398403
logging.warning('****** WARNING ******')
399-
logging.warning('half_y = {} is too small, you should increase it.'.format(self.args["halfy"]))
400-
404+
logging.info('halfy = {} um is too small, you should increase to ~{:6f} um.'.format(self.args["halfy"],
405+
self.args["halfy"] + 2.5*np.sum(combine_mask)/(2*np.pi*radius)*np.mean(self.sample_input.DS['voxel_size'])))
406+
401407
def set_beam(self):
402408
"""
403409
set the X-ray beam source, including beam energy, FWHM, flux, shape, source-to-sample distance, name etc.
@@ -406,7 +412,7 @@ def set_beam(self):
406412

407413
def set_sample(self):
408414
"""
409-
set the sample properties: including the grain map, voxel size, .
415+
set the sample properties: including the grain map, voxel size, density, mass absorption.
410416
"""
411417
self.sample_input = sample(self.sample_filename, min_misori = self.args['min_misori'], crystal_system = self.args['crystal_system'],
412418
remove_small_grains = self.args['remove_small_grains'], min_vol = self.args['min_vol'])
@@ -620,19 +626,23 @@ def generate_slurm_script(self, script_name="fwd_projector_slurm.sh"):
620626
#SBATCH --mem-per-cpu=8000
621627
#SBATCH --time=01:00:00
622628
629+
module load jupyter-slurm/2025.04.5
630+
echo "===== JOB START ====="
623631
date
624-
source /cvmfs/hpc.esrf.fr/software/packages/linux/x86_64/jupyter-slurm/latest/envs/jupyter-slurm/bin/activate
632+
625633
log_path="{log_path}_${{SLURM_JOB_ID}}_${{SLURM_ARRAY_TASK_ID}}.log"
626634
dty_file="{dty_file_list}"
627635
dty_values=$(sed -n "${{SLURM_ARRAY_TASK_ID}}p" $dty_file)
628-
echo "OMP_NUM_THREADS=1 OPENBLAS_NUM_THREADS=1 MKL_NUM_THREADS=1 PYTHONPATH={id11_code_path}" >> $log_path 2>&1
629-
OMP_NUM_THREADS=1 OPENBLAS_NUM_THREADS=1 MKL_NUM_THREADS=1 PYTHONPATH={id11_code_path} \
636+
echo "OMP_NUM_THREADS=1 OPENBLAS_NUM_THREADS=1 MKL_NUM_THREADS=1 PYTHONPATH={id11_code_path}"
637+
OMP_NUM_THREADS=1 OPENBLAS_NUM_THREADS=1 MKL_NUM_THREADS=1 PYTHONPATH={id11_code_path}
638+
export PYTHONPATH={id11_code_path}
630639
python3 -c "from ImageD11.forward_model import forward_projector; \
631640
fp = forward_projector.forward_projector(sample_filename='{sample_filename}', pars_filename='{pars_filename}', \
632641
phase_name='{phase_name}', output_folder='{output_folder}', gids_mask={gids_mask}, \
633642
verbose={verbose}, auto_set={auto_set}, detector_mask={detector_mask}, to_sparse={to_sparse}, \
634643
read_fwd_peaks_from_file={read_fwd_peaks_from_file}, **{args}); \
635-
fp.run_series_dty([float(v) for v in '$dty_values'.split()])" >> $log_path 2>&1
644+
fp.run_series_dty([float(v) for v in '$dty_values'.split()])"
645+
echo "===== JOB END ====="
636646
date""".format(
637647
outfile_path=outfile_path,
638648
errfile_path=errfile_path,
@@ -780,6 +790,7 @@ def write_cf(self):
780790
def plot_cf(self, cf_type = '2d', m=None):
781791
"""
782792
plot the sinogram of cf peaks, i.e. dty vs omega colored by peak intensity
793+
m is a boolen mask
783794
"""
784795
plt.figure(figsize=(10,8))
785796
if cf_type == '2d':
@@ -870,7 +881,7 @@ def forward_peaks_voxels(beam, sample, omega_angles, ucell, pars, dty=0.0,
870881
t0 = time.time()
871882
results = Parallel(n_jobs=-1, backend="loky")(delayed(process_omega)(
872883
omega, mask, DS, beam, sample, pars, ucell, rot_step, ds_max, Lsam2det, args, verbose
873-
) for omega in tqdm(omega_angles, desc="Processing omega angles"))
884+
) for omega in tqdm(omega_angles, desc="Processing omega angles", file=sys.stdout))
874885
# # Regarding multiprocessing.Pool, tried chunk, shared memory etc, it does not help to improve the speed
875886
# set_tmp_dir()
876887
# with multiprocessing.Pool(processes=min(multiprocessing.cpu_count(), 24)) as pool:

ImageD11/forward_model/grainmaps.py

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,13 @@ class grainmap:
5151
example:
5252
filename = ds.grains_file
5353
gm = grainmap(filename)
54-
gm.merge_and_identify_grains(FirstGrainID = 0, dis_tol = np.sqrt(2))
54+
gm.merge_and_identify_grains(FirstGrainID = 0, dis_tol = np.sqrt(3))
5555
DS = gm.DS
5656
'''
57-
def __init__(self, filename = None, outname = None, min_misori = 3, crystal_system = 'cubic', remove_small_grains = True, min_vol = 2):
57+
def __init__(self, filename = None, outname = None, min_misori = 3, crystal_system = 'cubic', remove_small_grains = True, min_vol = 2, verbose = 0):
5858
self.filename = filename
5959
self.outname = outname
60+
self.verbose = verbose
6061
if self.outname is None and self.filename is not None:
6162
self.outname = os.path.join(os.path.split(self.filename)[0], 'DS.h5')
6263
if filename is not None:
@@ -68,19 +69,21 @@ def __init__(self, filename = None, outname = None, min_misori = 3, crystal_syst
6869
self.crystal_system = crystal_system
6970
self.remove_small_grains = remove_small_grains
7071
self.min_vol = min_vol
71-
print('****************************************** Parameters for operating the grain map: ')
72-
print('Output file name: {}'.format(self.outname))
73-
print('min_misori = {}'.format(min_misori))
74-
print('crystal_system: {}'.format(crystal_system))
75-
print('remove_small_grains = {}'.format(remove_small_grains))
76-
print('min_vol = {} voxels'.format(min_vol))
77-
72+
if self.verbose >= 1:
73+
print('****************************************** Parameters for operating the grain map: ')
74+
print('Output file name: {}'.format(self.outname))
75+
print('min_misori = {}'.format(min_misori))
76+
print('crystal_system: {}'.format(crystal_system))
77+
print('remove_small_grains = {}'.format(remove_small_grains))
78+
print('min_vol = {} voxels'.format(min_vol))
79+
7880

7981
def read_tensor_map(self):
8082
if os.path.exists(self.filename):
8183
tensor_map = io.read_h5_file(self.filename)
8284
if isinstance(tensor_map, dict):
83-
io.print_all_keys(tensor_map)
85+
if self.verbose >= 1:
86+
io.print_all_keys(tensor_map)
8487
else:
8588
print("The file did not produce a dictionary.")
8689
return tensor_map
@@ -97,7 +100,8 @@ def convert2DS(self):
97100
tensor_map_flag = False
98101
for key in tensor_map.keys():
99102
if 'TensorMap' in key:
100-
print('Got the key name {}'.format(key))
103+
if self.verbose >= 1:
104+
print('Got the key name {}'.format(key))
101105
keyname = key
102106
tensor_map_flag = True
103107

@@ -108,7 +112,8 @@ def convert2DS(self):
108112
DS = {}
109113
for k in keys_list:
110114
if k in tensor_map[keyname]['maps'].keys():
111-
print('Loading {} with a shape of {} to DS ...'.format(k, tensor_map[keyname]['maps'][k].shape))
115+
if self.verbose >= 1:
116+
print('Loading {} with a shape of {} to DS ...'.format(k, tensor_map[keyname]['maps'][k].shape))
112117
DS[k] = tensor_map[keyname]['maps'][k]
113118
if 'step' in tensor_map[keyname].keys():
114119
DS['voxel_size'] = tensor_map[keyname]['step'] # [um/pixel]
@@ -131,7 +136,7 @@ def convert2DS(self):
131136
print('Found a map (dimensions {}) with {} voxels to be labeled with grain ID'.format(DS['labels'].shape, np.max(DS['labels'])))
132137
print('Please note: current labels are only randomized without any region merging !!!')
133138

134-
# get U from UBI if no 'U' existed
139+
# get U from UBI if no 'U' existed, this is usually the case with tensor map before refinement
135140
if 'U' not in DS.keys() and 'UBI' in DS.keys():
136141
try:
137142
latticepar = np.array(tensor_map[keyname]['phases']['0'].decode('utf-8').split(), dtype = float)
@@ -141,11 +146,15 @@ def convert2DS(self):
141146

142147
DS['U'] = np.empty((DS['UBI'].shape[0], DS['UBI'].shape[1], DS['UBI'].shape[2], 3, 3))
143148
DS['U'].fill(np.nan)
149+
150+
DS['B'] = np.empty((DS['UBI'].shape[0], DS['UBI'].shape[1], DS['UBI'].shape[2], 3, 3))
151+
DS['B'].fill(np.nan)
144152

145153
indices = np.argwhere(~np.isnan(DS['UBI'][:, :, :, 0, 0]))
146154
for (i, j, k) in indices:
147155
DS['U'][i, j, k, :, :] = np.dot(B, DS['UBI'][i, j, k, :, :]).T
148-
print('Done with deriving U matrix !')
156+
DS['B'][i, j, k, :, :] = B
157+
print('Done with deriving U and B matrices !')
149158
except KeyError as e:
150159
print("Key error: {}".format(e))
151160
except Exception as e:
@@ -284,7 +293,7 @@ def DS_remove_small_grains(DS, min_vol = 2):
284293
return DS_out
285294

286295

287-
def DS_merge_and_identify_grains_sub(DS, FirstGrainID = 0, min_misori = 3.0, dis_tol = np.sqrt(2), crystal_system = 'cubic'):
296+
def DS_merge_and_identify_grains_sub(DS, FirstGrainID = 0, min_misori = 3.0, dis_tol = np.sqrt(3), crystal_system = 'cubic'):
288297

289298
"""
290299
sub function:
@@ -296,7 +305,7 @@ def DS_merge_and_identify_grains_sub(DS, FirstGrainID = 0, min_misori = 3.0, dis
296305
DS -- grain map dictionary, gm = grainmap(tensor_map_file); DS = gm.DS
297306
FirstGrainID -- First grain ID to be considered for merging, by default = 0
298307
min_misori -- misorientation for merging regions
299-
dis_tol -- maximum distance around the target voxel for identifying its neigboring grains, np.sqrt(2) corresponds to distance to the diagonal voxel in 2D
308+
dis_tol -- maximum distance around the target voxel for identifying its neigboring grains, np.sqrt(3) ensure the diagonal voxel in 2D included
300309
crystal_system -- crystal system name one of ['cubic', 'hexagonal', 'orthorhombic', 'tetragonal', 'trigonal', 'monoclinic', 'triclinic']
301310
302311
Returns:

ImageD11/forward_model/io.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
# March 20, 2024
1212
# updated on January 30, 2025
1313

14-
import os, shutil
14+
import os, sys, shutil
1515
import glob
1616

1717
import numpy as np
@@ -394,7 +394,7 @@ def write_xdmf(h5name, xdmf_filename = None, ctrl = 'recon_mlem', attributes = [
394394

395395

396396
def camera_names_ID11():
397-
return ['marana3', 'marana1', 'marana2', 'marana', 'frelon1', 'frelon3', 'frelon6', 'eiger', 'basler_eh32', 'frelon16']
397+
return ['marana3', 'marana1', 'marana2', 'marana', 'frelon1', 'frelon3', 'frelon6', 'eiger', 'basler_eh32', 'frelon16', 'pco1']
398398

399399

400400
def guess_camera_name(h5name, scan = '1.1'):
@@ -630,6 +630,32 @@ def read_fsparse(h5_file, group_name = "/entry_0000/ESRF-ID11/eiger/data"):
630630
return fsparse_pks
631631

632632

633+
def deep_getsizeof(obj, seen=None):
634+
"""
635+
Get memory info of a dict, list, tuple or set, it does so recursively so that it can collect memory info of all elements.
636+
Args:
637+
obj: an object, e.g. dict, list, tuple or set
638+
Returns:
639+
size: size of the object in bytes
640+
Example to use:
641+
size_bytes = deep_getsizeof(sparse_cuda_dict)
642+
print(size_bytes / (1024**3), "GB")
643+
"""
644+
if seen is None:
645+
seen = set()
646+
obj_id = id(obj)
647+
if obj_id in seen:
648+
return 0
649+
seen.add(obj_id)
650+
size = sys.getsizeof(obj)
651+
if isinstance(obj, dict):
652+
size += sum(deep_getsizeof(k, seen) + deep_getsizeof(v, seen)
653+
for k, v in obj.items())
654+
elif isinstance(obj, (list, tuple, set)):
655+
size += sum(deep_getsizeof(i, seen) for i in obj)
656+
return size
657+
658+
633659
def copytree_with_progress(src, dst, skip_keys=None, create_subfolder = True, overwrite = False):
634660
"""
635661
Copies a folder with all subfolders and files, displaying progress.

0 commit comments

Comments
 (0)