Source code for toupy.resolution.FSCtools
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
FOURIER SHELL CORRELATION
"""
# standard packages
import time
import concurrent.futures
# third party package
import h5py
from ..utils.plot_utils import plt
import numpy as np
# local packages
from ..utils.FFT_utils import fastfftn
from ..utils.funcutils import checkhostname
from ..utils import isnotebook
from ..tomo import tomo_recons
__all__ = ["compute_2tomograms", "compute_2tomograms_splitted", "split_dataset"]
[docs]
def split_dataset(sinogram, theta):
"""
Split the tomographic dataset in 2 datasets
Parameters
----------
sinogram : ndarray
A 2-dimensional array containing the sinogram
theta : ndarray
A 1-dimensional array of thetas
Returns
-------
sinogram1 : ndarray
A 2-dimensional array containing the 1st sinogram.
sinogram2 : ndarray
A 2-dimensional array containing the 2nd sinogram.
theta1 : ndarray
A 1-dimensional array containing the 1st set of thetas.
theta2 : ndarray
A 1-dimensional array containing the 2nd set of thetas.
"""
sinogram1 = sinogram[:, 0::2]
theta1 = theta[0::2]
sinogram2 = sinogram[:, 1::2]
theta2 = theta[1::2]
return sinogram1, sinogram2, theta1, theta2
[docs]
def compute_2tomograms(sinogram, theta, **params):
"""
Split the tomographic dataset in 2 datasets and
compute 2 tomograms from them.
Parameters
----------
sinogram : ndarray
A 2-dimensional array containing the sinogram
theta : ndarray
A 1-dimensional array of thetas
Returns
-------
recon1 : ndarray
A 2-dimensional array containing the 1st reconstruction.
recon2 : ndarray
A 2-dimensional array containing the 2nd reconstruction.
"""
sino1, sino2, theta1, theta2 = split_dataset(sinogram, theta)
return compute_2tomograms_splitted(sino1, sino2, theta1, theta2, **params)
[docs]
def compute_2tomograms_splitted(sinogram1, sinogram2, theta1, theta2, **params):
"""
Compute 2 tomograms from an already-split tomographic dataset.
The two reconstructions are independent, so they are run concurrently
using a ThreadPoolExecutor. Because tomo_recons ultimately calls
scipy.ndimage / iradon C extensions that release the GIL, two OS threads
can execute true parallel C code with negligible overhead.
If threading fails for any reason (e.g. the backend is not thread-safe),
the function transparently falls back to the original sequential path.
Parameters
----------
sinogram1 : ndarray
A 2-dimensional array containing sinogram 1
sinogram2 : ndarray
A 2-dimensional array containing sinogram 2
theta1 : ndarray
A 1-dimensional array of thetas for sinogram1
theta2 : ndarray
A 1-dimensional array of thetas for sinogram2
Returns
-------
recon1 : ndarray
A 2-dimensional array containing the 1st reconstruction
recon2 : ndarray
A 2-dimensional array containing the 2nd reconstruction
"""
verbose = not isnotebook()
def _recon(label, sinogram, theta):
if verbose:
print(f"Calculating slice {label}...")
t0 = time.time()
result = tomo_recons(sinogram, theta, **params)
if verbose:
print(f"Slice {label} done. Time elapsed: {time.time() - t0:.3f} s")
return result
# --- Parallel path (threads) ---
# Two workers suffice: one per reconstruction.
try:
with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
f1 = executor.submit(_recon, 1, sinogram1, theta1)
f2 = executor.submit(_recon, 2, sinogram2, theta2)
recon1 = f1.result()
recon2 = f2.result()
return recon1, recon2
except Exception as exc:
# Fall back to sequential execution if threading caused any problem.
print(f"Parallel reconstruction failed ({exc}); falling back to sequential.")
recon1 = _recon(1, sinogram1, theta1)
recon2 = _recon(2, sinogram2, theta2)
return recon1, recon2