Skip to content

Commit

Permalink
Merge pull request #252 from carnisj/test_runner
Browse files Browse the repository at this point in the history
Implement large unit tests for runners
  • Loading branch information
carnisj authored Apr 26, 2022
2 parents 89bc6c7 + 3e321a7 commit 587d2ea
Show file tree
Hide file tree
Showing 11 changed files with 175 additions and 37 deletions.
2 changes: 1 addition & 1 deletion bcdi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
# authors:
# Jerome Carnis, carnis_jerome@yahoo.fr
"""The main bcdi package, which contains the whole framework."""
__version__ = "0.2.5"
__version__ = "0.2.6"
2 changes: 1 addition & 1 deletion bcdi/graph/graph_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1413,7 +1413,7 @@ def linecut(
num_points = int(
np.sqrt(sum((val[1] - val[0] + 1) ** 2 for _, val in enumerate(indices)))
)
print("num_points", num_points)

cut = map_coordinates(
input=array,
coordinates=np.vstack(
Expand Down
28 changes: 21 additions & 7 deletions bcdi/utils/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,17 @@ def __init__(self, key, value, allowed):


class ConfigChecker(ABC):
"""Validate and configure parameters."""
"""
Validate and configure parameters.
:param initial_params: the dictionary of parameters to validate and configure
:param default_values: an optional dictionary of default values for keys in
initial_params
:param logger: an optional Logger
:param match_length_params: a tuple of keys from initial_params which should match
a certain length (e.g. the number of scans)
:param required_params: a tuple of keys that have to be present in initial_params
"""

def __init__(
self,
Expand Down Expand Up @@ -286,9 +296,13 @@ def _configure_params(self) -> None:
if self.initial_params["align_q"]:
if self.initial_params["ref_axis_q"] not in {"x", "y", "z"}:
raise ValueError("ref_axis_q should be either 'x', 'y' or 'z'")
self._checked_params[
"comment"
] += f"_align-q-{self.initial_params['ref_axis_q']}"
if (
not self._checked_params["use_rawdata"]
and self._checked_params["interpolation_method"] == "linearization"
):
self._checked_params[
"comment"
] += f"_align-q-{self.initial_params['ref_axis_q']}"

if (
self.initial_params["backend"].lower() == "agg"
Expand Down Expand Up @@ -506,7 +520,7 @@ def valid_param(key: str, value: Any) -> Tuple[Any, bool]:
elif key == "config_file":
valid.valid_container(value, container_types=str, min_length=1, name=key)
if not os.path.isfile(value):
raise ValueError(f"The file {value} does not exist")
raise ValueError(f"The file '{value}' does not exist")
elif key == "correlation_threshold":
valid.valid_item(
value, allowed_types=Real, min_included=0, max_included=1, name=key
Expand Down Expand Up @@ -736,7 +750,7 @@ def valid_param(key: str, value: Any) -> Tuple[Any, bool]:
value = (value,)
valid.valid_container(
value,
container_types=list,
container_types=(tuple, list),
item_types=str,
min_length=1,
allow_none=True,
Expand All @@ -745,7 +759,7 @@ def valid_param(key: str, value: Any) -> Tuple[Any, bool]:
if value is not None:
for val in value:
if not os.path.isfile(val):
raise ValueError(f"The file {val} does not exist")
raise ValueError(f"The file '{val}' does not exist")
elif key in {"ref_axis_q", "ref_axis"}:
allowed = {"x", "y", "z"}
if value not in allowed:
Expand Down
2 changes: 1 addition & 1 deletion bcdi/utils/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ def file_path(self, value):
if pathlib.Path(value).suffix != ".yml":
raise ValueError("Expecting a YAML config file")
if not os.path.isfile(value):
raise ValueError(f"The file {value} does not exist")
raise ValueError(f"The config file '{value}' does not exist")
self._file_path = value

@staticmethod
Expand Down
4 changes: 2 additions & 2 deletions doc/HISTORY.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Future:
-------
Version 0.2.6:
--------------

* Fix deprecation warning with the method `pandas.DataFrame.append`

Expand Down
2 changes: 1 addition & 1 deletion doc/modules/utils/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ API Reference
parameters
^^^^^^^^^^

Function related to the validation of configuration parameters.
Classes and functions related to the validation of configuration parameters.

API Reference
-------------
Expand Down
72 changes: 72 additions & 0 deletions tests/postprocessing/test_postprocessing_runner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# -*- coding: utf-8 -*-

# BCDI: tools for pre(post)-processing Bragg coherent X-ray diffraction imaging data
# (c) 07/2017-06/2019 : CNRS UMR 7344 IM2NP
# (c) 07/2019-05/2021 : DESY PHOTON SCIENCE
# authors:
# Jerome Carnis, carnis_jerome@yahoo.fr

from functools import reduce
import h5py
import numpy as np
from pathlib import Path
import tempfile
from typing import Dict, Optional
import unittest
import yaml

from bcdi.postprocessing.postprocessing_runner import run
from bcdi.utils.parser import ConfigParser
from tests.config import run_tests

here = Path(__file__).parent
THIS_DIR = str(here)
CONFIG = str(here.parents[1] / "bcdi/examples/S11_config_postprocessing.yml")


class TestRun(unittest.TestCase):
"""Large test for the postprocessing script bcdi_strain.py."""

def setUp(self) -> None:
self.args: Optional[Dict] = None
self.command_line_args = {
"backend": "Agg",
"reconstruction_files": str(
here.parents[1]
/ "bcdi/examples/S11_modes_252_420_392_prebinning_1_1_1.h5"
),
}
self.parser = ConfigParser(CONFIG, self.command_line_args)
if not Path(
yaml.load(self.parser.raw_config, Loader=yaml.SafeLoader)["root_folder"]
).is_dir():
self.skipTest(
reason="This test can only run locally with the example dataset"
)

def test_run(self):
expected_q_com = [0, 2.77555, 0]
expected_volume = 23217408
with tempfile.TemporaryDirectory() as tmpdir:
self.args = self.parser.load_arguments()
self.args["save_dir"] = (tmpdir,)
run(self.args)

with h5py.File(
f"{tmpdir}/S11_ampdispstrain_mode_crystalframe.h5",
"r",
) as h5file:
amp = h5file["output/amp"][()]
voxel_sizes = h5file["output/voxel_sizes"][()]
q_com = h5file["output/q_com"][()]
amp = amp / amp.max()
amp[amp < self.args["isosurface_strain"]] = 0
amp[np.nonzero(amp)] = 1
volume = amp.sum() * reduce(lambda x, y: x * y, voxel_sizes)

self.assertEqual(volume, expected_volume)
self.assertTrue(np.allclose(q_com, expected_q_com))


if __name__ == "__main__":
run_tests(TestRun)
22 changes: 0 additions & 22 deletions tests/postprocessing/test_postprocessing_utils.py

This file was deleted.

72 changes: 72 additions & 0 deletions tests/preprocessing/test_preprocessing_runner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# -*- coding: utf-8 -*-

# BCDI: tools for pre(post)-processing Bragg coherent X-ray diffraction imaging data
# (c) 07/2017-06/2019 : CNRS UMR 7344 IM2NP
# (c) 07/2019-05/2021 : DESY PHOTON SCIENCE
# authors:
# Jerome Carnis, carnis_jerome@yahoo.fr

from functools import reduce
import h5py
import numpy as np
from pathlib import Path
import tempfile
from typing import Dict, Optional
import unittest
import yaml

from bcdi.preprocessing.preprocessing_runner import run
from bcdi.utils.parser import ConfigParser
from tests.config import run_tests

here = Path(__file__).parent
THIS_DIR = str(here)
CONFIG = str(here.parents[1] / "bcdi/examples/S11_config_preprocessing.yml")


class TestRun(unittest.TestCase):
"""Large test for the preprocessing script bcdi_preprocessing_BCDI.py."""

def setUp(self) -> None:
self.args: Optional[Dict] = None
self.command_line_args = {
"backend": "Agg",
"flag_interact": False,
}
self.parser = ConfigParser(CONFIG, self.command_line_args)
if not Path(
yaml.load(self.parser.raw_config, Loader=yaml.SafeLoader)["root_folder"]
).is_dir():
self.skipTest(
reason="This test can only run locally with the example dataset"
)

def test_run(self):
expected_bragg_inplane = 0.4926488901267543
expected_bragg_outofplane = 35.36269069963432
expected_bragg_peak = [127, 214, 316]
expected_q = [-0.84164063, 2.63974482, -0.03198209]
with tempfile.TemporaryDirectory() as tmpdir:
self.args = self.parser.load_arguments()
self.args["save_dir"] = (tmpdir,)
run(self.args)

with h5py.File(
f"{tmpdir}/S11_preprocessing_norm_256_256_256_1_2_2.h5",
"r",
) as h5file:
bragg_inplane = h5file["output/bragg_inplane"][()]
bragg_outofplane = h5file["output/bragg_outofplane"][()]
bragg_peak = h5file["output/bragg_peak"][()]
q = h5file["output/q"][()]

self.assertAlmostEqual(bragg_inplane, expected_bragg_inplane)
self.assertAlmostEqual(bragg_outofplane, expected_bragg_outofplane)
self.assertTrue(
val1 == val2 for val1, val2 in zip(bragg_peak, expected_bragg_peak)
)
self.assertTrue(np.allclose(q, expected_q))


if __name__ == "__main__":
run_tests(TestRun)
2 changes: 2 additions & 0 deletions tests/utils/test_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,8 @@ def test_check_config_interpolate_reload_orthogonal(self):

def test_check_config_align_q(self):
self.checker.initial_params["align_q"] = True
self.checker._checked_params["use_rawdata"] = False
self.checker._checked_params["interpolation_method"] = "linearization"
out = self.checker.check_config()
self.assertEqual(
out["comment"],
Expand Down
4 changes: 2 additions & 2 deletions tests/utils/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
CONFIG = str(here.parents[1] / "bcdi/examples/S11_config_postprocessing.yml")


class Test_str_to_list(unittest.TestCase):
class TestStrToList(unittest.TestCase):
"""
Tests on the function str_to_list.
Expand Down Expand Up @@ -146,4 +146,4 @@ def test_repr(self):

if __name__ == "__main__":
run_tests(TestConfigParser)
run_tests(Test_str_to_list)
run_tests(TestStrToList)

0 comments on commit 587d2ea

Please sign in to comment.