Skip to content

Commit

Permalink
[pre-commit.ci] auto fixes from pre-commit.com hooks
Browse files Browse the repository at this point in the history
for more information, see https://pre-commit.ci
  • Loading branch information
pre-commit-ci[bot] committed Feb 15, 2024
1 parent 0966ac4 commit 5da2d16
Show file tree
Hide file tree
Showing 53 changed files with 789 additions and 223 deletions.
14 changes: 11 additions & 3 deletions scripts/reduce_CG1D.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
from imars3d.backend import extract_info_from_path
from imars3d.backend import substitute_template
from imars3d.backend.autoredux import logger as logger_autoredux
from imars3d.backend.workflow.engine import WorkflowEngineAuto, WorkflowEngineError, WorkflowEngineExitCodes
from imars3d.backend.workflow.engine import (
WorkflowEngineAuto,
WorkflowEngineError,
WorkflowEngineExitCodes,
)
from imars3d.backend.util.functions import to_time_str

# standard imports
Expand Down Expand Up @@ -49,7 +53,9 @@ def _find_template_config() -> Path:
autored_dir = Path(__file__).parent
path_to_file = autored_dir / template_name
if not path_to_file.exists():
raise FileNotFoundError(f"Template {template_name} not found in directory {autored_dir}")
raise FileNotFoundError(
f"Template {template_name} not found in directory {autored_dir}"
)
return path_to_file


Expand Down Expand Up @@ -130,7 +136,9 @@ def main(inputfile: Union[str, Path], outputdir: Union[str, Path]) -> int:
log_file_handler.flush()
root_logger.removeHandler(log_file_handler)
log_file_handler.close()
shutil.move(log_file_path, radiographs_dir) # move the log file to the radiographs directory
shutil.move(
log_file_path, radiographs_dir
) # move the log file to the radiographs directory
else:
config_file_path = outputdir / f"{config_file_name}_{time_str}.json"
save_config(config_dict, config_file_path)
Expand Down
9 changes: 8 additions & 1 deletion src/imars3d/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
"""iMars3D: a Python package for neutron imaging and tomography reconstruction."""
import logging
from .backend import corrections, diagnostics, dataio, morph, preparation, reconstruction # noqa: F401
from .backend import (
corrections,
diagnostics,
dataio,
morph,
preparation,
reconstruction,
) # noqa: F401

logging.getLogger("imars3d").setLevel(logging.INFO)
try:
Expand Down
24 changes: 18 additions & 6 deletions src/imars3d/backend/autoredux.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ def auto_reduction_ready(data_file: Union[str, Path]) -> bool:
if match:
image_type = match.groups()[0]
if image_type in non_radiograph["dark-field"] + non_radiograph["open-beam"]:
logger.info("The input image is not a radiograph. It's assumed the scan is incomplete.")
logger.info(
"The input image is not a radiograph. It's assumed the scan is incomplete."
)
return False
else:
logger.error("non-canonical data file path")
Expand Down Expand Up @@ -115,7 +117,9 @@ def extract_info_from_path(data_file: Union[str, Path]) -> dict:
raw_position = 4

extracted_data = Path(data_file).parts
assert extracted_data[0] == os.path.sep, f"Path {str(data_file)} is not an absolute path"
assert (
extracted_data[0] == os.path.sep
), f"Path {str(data_file)} is not an absolute path"

# Extract FACILITY, INSTRUMENT, and IPTS
data_dict = {}
Expand All @@ -133,7 +137,9 @@ def extract_info_from_path(data_file: Union[str, Path]) -> dict:
data_dict["radiograph"] = extracted_data[ct_position]

# Extract PREPATH and SUBPATH
data_dict["prepath"] = os.path.sep.join(extracted_data[1 + raw_position : ct_position])
data_dict["prepath"] = os.path.sep.join(
extracted_data[1 + raw_position : ct_position]
)
data_dict["subpath"] = os.path.sep.join(extracted_data[1 + ct_position : -1])

logger.info("Extract information from data file path.")
Expand Down Expand Up @@ -167,8 +173,14 @@ def substitute_template(config: dict, values: dict) -> dict:
"dcdir": str(pre_path / "df" / values["subpath"]),
}
)
assert {"facility", "instrument", "ipts", "name", "workingdir", "outputdir", "tasks"}.issubset(
set(config.keys())
), "Config template dict is missing keys."
assert {
"facility",
"instrument",
"ipts",
"name",
"workingdir",
"outputdir",
"tasks",
}.issubset(set(config.keys())), "Config template dict is missing keys."
template = Template(json.dumps(config))
return json.loads(template.substitute(**values))
19 changes: 15 additions & 4 deletions src/imars3d/backend/corrections/beam_hardening.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
from multiprocessing.managers import SharedMemoryManager
from functools import partial
from tqdm.contrib.concurrent import process_map
from algotom.prep.correction import beam_hardening_correction as algotom_beam_hardening_correction
from algotom.prep.correction import (
beam_hardening_correction as algotom_beam_hardening_correction,
)

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -74,11 +76,15 @@ def __call__(self, **params):
logger.debug(f"max_worker={self.max_workers}")

if params.arrays.ndim == 2:
return algotom_beam_hardening_correction(params.arrays, params.q, params.n, params.opt)
return algotom_beam_hardening_correction(
params.arrays, params.q, params.n, params.opt
)
elif params.arrays.ndim == 3:
with SharedMemoryManager() as smm:
shm = smm.SharedMemory(params.arrays.nbytes)
shm_arrays = np.ndarray(params.arrays.shape, dtype=params.arrays.dtype, buffer=shm.buf)
shm_arrays = np.ndarray(
params.arrays.shape, dtype=params.arrays.dtype, buffer=shm.buf
)
np.copyto(shm_arrays, params.arrays)
# mp
kwargs = {
Expand All @@ -88,7 +94,12 @@ def __call__(self, **params):
if self.tqdm_class:
kwargs["tqdm_class"] = self.tqdm_class
rst = process_map(
partial(algotom_beam_hardening_correction, q=params.q, n=params.n, opt=params.opt),
partial(
algotom_beam_hardening_correction,
q=params.q,
n=params.n,
opt=params.opt,
),
[shm_arrays[i] for i in range(shm_arrays.shape[0])],
**kwargs,
)
Expand Down
16 changes: 13 additions & 3 deletions src/imars3d/backend/corrections/denoise.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,11 @@ def denoise_by_median(


def denoise_by_bilateral(
arrays: np.ndarray, sigma_color: float = 0.02, sigma_spatial: float = 5.0, max_workers: int = 0, tqdm_class=None
arrays: np.ndarray,
sigma_color: float = 0.02,
sigma_spatial: float = 5.0,
max_workers: int = 0,
tqdm_class=None,
) -> np.ndarray:
"""
Denoise the image stack with the bilateral filter.
Expand Down Expand Up @@ -159,7 +163,11 @@ def denoise_by_bilateral(
kwargs["tqdm_class"] = tqdm_class

rst = process_map(
partial(denoise_by_bilateral_2d, sigma_color=sigma_color, sigma_spatial=sigma_spatial),
partial(
denoise_by_bilateral_2d,
sigma_color=sigma_color,
sigma_spatial=sigma_spatial,
),
[img for img in shm_arrays],
**kwargs,
)
Expand Down Expand Up @@ -196,7 +204,9 @@ def denoise_by_bilateral_2d(
logger.debug(f"denoise_by_bilateral_2d:array2d_max = {array2d_max}")
_sigma_color = sigma_color / array2d_max
array_2d /= array2d_max
array_2d = denoise_bilateral(array_2d, sigma_color=_sigma_color, sigma_spatial=sigma_spatial)
array_2d = denoise_bilateral(
array_2d, sigma_color=_sigma_color, sigma_spatial=sigma_spatial
)
return array_2d * array2d_max


Expand Down
19 changes: 15 additions & 4 deletions src/imars3d/backend/corrections/gamma_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@ class gamma_filter(param.ParameterizedFunction):
corrected 3D array of images, the first dimension is the rotation angle omega
"""

arrays = param.Array(doc="3D array of images, the first dimension is the rotation angle omega", default=None)
arrays = param.Array(
doc="3D array of images, the first dimension is the rotation angle omega",
default=None,
)
threshold = param.Integer(
default=-1,
doc="threshold for saturation, default is -1, which means using the internally defined threshold (see source code)",
Expand Down Expand Up @@ -90,13 +93,21 @@ def __call__(self, **params):
try:
saturation_intensity = np.iinfo(params.arrays.dtype).max
except ValueError:
logger.warning("Switch to use standard uint16 threshold as given arrays are float type.")
logger.warning(
"Switch to use standard uint16 threshold as given arrays are float type."
)
saturation_intensity = 65535
threshold = saturation_intensity - 5 if params.threshold == -1 else params.threshold
threshold = (
saturation_intensity - 5 if params.threshold == -1 else params.threshold
)
logger.debug(f"threshold={threshold}")

# NOTE: use 20% of the total dynamic range as the outlier detection criterion
diff_tomopy = 0.2 * saturation_intensity if params.diff_tomopy < 0.0 else params.diff_tomopy
diff_tomopy = (
0.2 * saturation_intensity
if params.diff_tomopy < 0.0
else params.diff_tomopy
)
logger.debug(f"diff_tomopy={diff_tomopy}")
# median filtering
arrays_filtered = tomopy.remove_outlier(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ class intensity_fluctuation_correction(param.ParameterizedFunction):
The corrected image/radiograph stack.
"""

ct = param.Array(doc="The image/radiograph stack to correct for beam intensity fluctuation.", default=None)
ct = param.Array(
doc="The image/radiograph stack to correct for beam intensity fluctuation.",
default=None,
)
air_pixels = param.Integer(
default=5,
doc="Number of pixels at each boundary to calculate the scaling factor. When a negative number is given, the auto air region detection will be used instead of tomopy.",
Expand Down Expand Up @@ -66,12 +69,18 @@ def __call__(self, **params):
self.max_workers = clamp_max_workers(params.max_workers)
logger.debug(f"max_workers={self.max_workers}")
corrected_array = self._intensity_fluctuation_correction(
params.ct, params.air_pixels, params.sigma, self.max_workers, params.tqdm_class
params.ct,
params.air_pixels,
params.sigma,
self.max_workers,
params.tqdm_class,
)
logger.info("FINISHED Executing Filter: Intensity Fluctuation Correction")
return corrected_array

def _intensity_fluctuation_correction(self, ct, air_pixels, sigma, max_workers, tqdm_class):
def _intensity_fluctuation_correction(
self, ct, air_pixels, sigma, max_workers, tqdm_class
):
"""Correct for intensity fluctuation in the radiograph."""
# validation
if ct.ndim not in (2, 3):
Expand Down Expand Up @@ -254,6 +263,10 @@ def __call__(self, **params):
def _normalize_roi(self, ct, roi, max_workers):
# sanity check
if ct.ndim != 3:
raise ValueError("This correction can only be used for a stack, i.e. a 3D image.")
proj_norm_beam_fluctuation = tomopy.prep.normalize.normalize_roi(ct, roi=roi, ncore=max_workers)
raise ValueError(
"This correction can only be used for a stack, i.e. a 3D image."
)
proj_norm_beam_fluctuation = tomopy.prep.normalize.normalize_roi(
ct, roi=roi, ncore=max_workers
)
return proj_norm_beam_fluctuation
53 changes: 39 additions & 14 deletions src/imars3d/backend/corrections/ring_removal.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ class bm3d_ring_removal(param.ParameterizedFunction):

arrays = param.Array(doc="Input radiograph stack.", default=None)
# parameters passed to bm3dsr.extreme_streak_attenuation
extreme_streak_iterations = param.Integer(default=3, doc="Number of iterations for extreme streak attenuation.")
extreme_streak_iterations = param.Integer(
default=3, doc="Number of iterations for extreme streak attenuation."
)
extreme_detect_lambda = param.Number(
default=4.0,
doc="Consider streaks which are stronger than lambda * local_std as extreme.",
Expand Down Expand Up @@ -117,8 +119,12 @@ class bm3d_ring_removal(param.ParameterizedFunction):
def __call__(self, **params):
"""See class level documentation for help."""
if not bm3dsr:
logger.warning("To use method, make sure to install bm3d_streak_removal package via pip.")
raise RuntimeError("BM3D suite not installed, please install with pip install bm3d_streak_removal")
logger.warning(
"To use method, make sure to install bm3d_streak_removal package via pip."
)
raise RuntimeError(
"BM3D suite not installed, please install with pip install bm3d_streak_removal"
)
else:
logger.info("Executing Filter: Remove Ring Artifact with BM3D")
_ = self.instance(**params)
Expand Down Expand Up @@ -181,15 +187,22 @@ class remove_ring_artifact(param.ParameterizedFunction):

arrays = param.Array(doc="Input radiograph stack.", default=None)
kernel_size = param.Integer(
default=5, doc="The size of the kernel (moving window) during local smoothing with median filter."
default=5,
doc="The size of the kernel (moving window) during local smoothing with median filter.",
)
sub_division = param.Integer(
default=10, doc="Sub-dividing the sinogram into subsections (along rotation angle axis)."
default=10,
doc="Sub-dividing the sinogram into subsections (along rotation angle axis).",
)
correction_range = param.List(
default=[0.9, 1.1], doc="Multiplicative correction factor is capped within given range."
default=[0.9, 1.1],
doc="Multiplicative correction factor is capped within given range.",
)
max_workers = param.Integer(
default=0,
bounds=(0, None),
doc="Number of cores to use for parallel processing.",
)
max_workers = param.Integer(default=0, bounds=(0, None), doc="Number of cores to use for parallel processing.")
tqdm_class = param.ClassSelector(class_=object, doc="Progress bar to render with")

def __call__(self, **params):
Expand Down Expand Up @@ -219,7 +232,9 @@ def _remove_ring_artifact(
) -> np.ndarray:
# sanity check
if arrays.ndim != 3:
raise ValueError("This correction can only be used for a stack, i.e. a 3D image.")
raise ValueError(
"This correction can only be used for a stack, i.e. a 3D image."
)
# NOTE:
# additional work is needed to avoid duplicating arrays in memory
max_workers = clamp_max_workers(max_workers)
Expand Down Expand Up @@ -288,13 +303,16 @@ class remove_ring_artifact_Ketcham(param.ParameterizedFunction):

sinogram = param.Array(doc="Input sinogram.", default=None)
kernel_size = param.Integer(
default=5, doc="The size of the kernel (moving window) during local smoothing via median filter."
default=5,
doc="The size of the kernel (moving window) during local smoothing via median filter.",
)
sub_division = param.Integer(
default=10, doc="Sub-dividing the sinogram into subsections (along rotation angle axis)."
default=10,
doc="Sub-dividing the sinogram into subsections (along rotation angle axis).",
)
correction_range = param.Tuple(
default=(0.9, 1.1), doc="Multiplicative correction factor is capped within given range."
default=(0.9, 1.1),
doc="Multiplicative correction factor is capped within given range.",
)

def __call__(self, **params):
Expand All @@ -303,7 +321,10 @@ def __call__(self, **params):
_ = self.instance(**params)
params = param.ParamOverrides(self, params)
val = _remove_ring_artifact_Ketcham(
params.sinogram, params.kernel_size, params.sub_division, params.correction_range
params.sinogram,
params.kernel_size,
params.sub_division,
params.correction_range,
)
logger.info("FINISHED Executing Filter: Remove Ring Artifact (Ketcham)")
return val
Expand All @@ -317,7 +338,9 @@ def _remove_ring_artifact_Ketcham(
) -> np.ndarray:
# sanity check
if sinogram.ndim != 2:
raise ValueError("This correction can only be used for a sinogram, i.e. a 2D image.")
raise ValueError(
"This correction can only be used for a sinogram, i.e. a 2D image."
)
# sub-divide the sinogram into smaller sections
edges = np.linspace(0, sinogram.shape[0], sub_division + 1).astype(int)
#
Expand All @@ -332,7 +355,9 @@ def _remove_ring_artifact_Ketcham(
# - skip one window/kernel on both end (mostly air anyway)
# - correction ratio is capped within specified range
corr_ratio = np.ones_like(sum_over_angle)
corr_ratio[kernel_size:-kernel_size] = (sum_over_angle_smoothed / sum_over_angle)[kernel_size:-kernel_size]
corr_ratio[kernel_size:-kernel_size] = (
sum_over_angle_smoothed / sum_over_angle
)[kernel_size:-kernel_size]
corr_ratio[corr_ratio < correction_range[0]] = correction_range[0]
corr_ratio[corr_ratio > correction_range[1]] = correction_range[1]
#
Expand Down
Loading

0 comments on commit 5da2d16

Please sign in to comment.