From 2c8436953aa65a54f11175c444d43d54daba5c7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20Wiik=20=C3=85nes?= Date: Tue, 4 May 2021 18:03:35 +0200 Subject: [PATCH 1/7] Test examples in docstring with doctest via pytest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- .github/workflows/build.yml | 4 +- doc/changelog.rst | 4 +- doc/contributing.rst | 5 + kikuchipy/conftest.py | 72 ++++++++---- kikuchipy/detectors/ebsd_detector.py | 17 ++- kikuchipy/filters/window.py | 23 ++-- .../generators/ebsd_simulation_generator.py | 47 ++++---- kikuchipy/generators/virtual_bse_generator.py | 8 +- kikuchipy/io/_io.py | 8 +- kikuchipy/projections/spherical_projection.py | 6 +- kikuchipy/signals/_common_image.py | 40 ++++--- kikuchipy/signals/ebsd.py | 108 +++++++++++------- kikuchipy/simulations/features.py | 2 +- setup.cfg | 10 +- setup.py | 1 + 15 files changed, 225 insertions(+), 130 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 74c217ad..65ae7a1a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,10 +30,12 @@ jobs: run: python -V; pip -V - name: Install depedencies and package shell: bash - run: pip install -U -e .'[doc, tests]' + run: pip install -U -e .'[tests]' - name: Install oldest supported version if: ${{ matrix.OLDEST_SUPPORTED_VERSION }} run: pip install ${{ matrix.DEPENDENCIES }} + - name: Run docstring tests + run: pytest --doctest-modules --ignore-glob=kikuchipy/*/tests - name: Run tests run: pytest --cov=kikuchipy --pyargs kikuchipy - name: Generate line coverage diff --git a/doc/changelog.rst b/doc/changelog.rst index cd1b114a..4e0cbb79 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -21,8 +21,10 @@ Contributors Added ----- +- Unit testing of docstring examples. + (`#? `_) - Support for Python 3.9. - (`#348 )`_ + (`#348 `_) - Projection/pattern center calibration via the moving screen technique in a kikuchipy.detectors.calibration module. (`#322 `_) diff --git a/doc/contributing.rst b/doc/contributing.rst index 894bd9f4..428e1b25 100644 --- a/doc/contributing.rst +++ b/doc/contributing.rst @@ -208,6 +208,11 @@ terminal. For an even nicer presentation, you can use ``coverage.py`` directly:: Then, you can open the created ``htmlcov/index.html`` in the browser and inspect the coverage in more detail. +Docstring examples are tested +`with pytest `_ as well:: + + $ pytest --doctest-modules --ignore-glob=kikuchipy/*/tests + Adding data to the data module ============================== diff --git a/kikuchipy/conftest.py b/kikuchipy/conftest.py index e42e40c3..1763549b 100644 --- a/kikuchipy/conftest.py +++ b/kikuchipy/conftest.py @@ -25,8 +25,10 @@ from diffpy.structure import Atom, Lattice, Structure from diffsims.crystallography import ReciprocalLatticePoint +import hyperspy.api as hs from hyperspy import __version__ as hs_version from hyperspy.misc.utils import DictionaryTreeBrowser +import matplotlib.pyplot as plt import numpy as np from orix.crystal_map import CrystalMap, Phase, PhaseList from orix.quaternion.rotation import Rotation @@ -43,6 +45,9 @@ from kikuchipy.simulations.features import KikuchiBand, ZoneAxis +# ------------------------- Helper functions ------------------------- # + + def assert_dictionary(dict1, dict2): """Assert that two dictionaries are (almost) equal. @@ -70,6 +75,31 @@ def assert_dictionary(dict1, dict2): assert dict1[key] == dict2[key] +def _get_spatial_array_dicts( + nav_shape: Tuple[int, int], step_sizes: Tuple[int, int] = (1.5, 1) +) -> Tuple[dict, int]: + ny, nx = nav_shape + dy, dx = step_sizes + d = {"x": None, "y": None, "z": None} + map_size = 1 + if nx > 1: + if ny > 1: + d["x"] = np.tile(np.arange(nx) * dx, ny) + else: + d["x"] = np.arange(nx) * dx + map_size *= nx + if ny > 1: + if nx > 1: + d["y"] = np.sort(np.tile(np.arange(ny) * dy, nx)) + else: + d["y"] = np.arange(ny) * dy + map_size *= ny + return d, map_size + + +# ----------------------------- Fixtures ----------------------------- # + + @pytest.fixture def dummy_signal(): """Dummy signal of shape <(3, 3)|(3, 3)>. If this is changed, all @@ -358,23 +388,25 @@ def _get_single_phase_xmap( return _get_single_phase_xmap -def _get_spatial_array_dicts( - nav_shape: Tuple[int, int], step_sizes: Tuple[int, int] = (1.5, 1) -) -> Tuple[dict, int]: - ny, nx = nav_shape - dy, dx = step_sizes - d = {"x": None, "y": None, "z": None} - map_size = 1 - if nx > 1: - if ny > 1: - d["x"] = np.tile(np.arange(nx) * dx, ny) - else: - d["x"] = np.arange(nx) * dx - map_size *= nx - if ny > 1: - if nx > 1: - d["y"] = np.sort(np.tile(np.arange(ny) * dy, nx)) - else: - d["y"] = np.arange(ny) * dy - map_size *= ny - return d, map_size +# ---------------------- pytest doctest-modules ---------------------- # + + +@pytest.fixture(autouse=True) +def doctest_setup_teardown(request): + # Setup + plt.ioff() # Interactive plotting off + temporary_directory = tempfile.TemporaryDirectory() + original_directory = os.getcwd() + os.chdir(temporary_directory.name) + yield + + # Teardown + os.chdir(original_directory) + temporary_directory.cleanup() + plt.close("all") + + +@pytest.fixture(autouse=True) +def import_to_namespace(doctest_namespace): + DIR_PATH = os.path.dirname(__file__) + doctest_namespace["DATA_DIR"] = os.path.join(DIR_PATH, "data/kikuchipy") diff --git a/kikuchipy/detectors/ebsd_detector.py b/kikuchipy/detectors/ebsd_detector.py index cff4aca4..d2889df8 100644 --- a/kikuchipy/detectors/ebsd_detector.py +++ b/kikuchipy/detectors/ebsd_detector.py @@ -87,10 +87,11 @@ def __init__( Examples -------- + >>> import numpy as np >>> from kikuchipy.detectors import EBSDDetector >>> det = EBSDDetector( ... shape=(60, 60), - ... pc=np.ones((149, 200)) * [0.421, 0.779, 0.505], + ... pc=np.ones((149, 200, 3)) * (0.421, 0.779, 0.505), ... convention="tsl", ... px_size=70, ... binning=8, @@ -98,14 +99,13 @@ def __init__( ... sample_tilt=70, ... ) >>> det - EBSDDetector (60, 60), px_size 70 um, binning 8, tilt 0, pc - (0.421, 0.221, 0.505) + EBSDDetector (60, 60), px_size 70 um, binning 8, tilt 5, pc (0.421, 0.221, 0.505) >>> det.navigation_shape # (nrows, ncols) (149, 200) >>> det.bounds - array([ 0, 60, 0, 60]) - >>> det.gnomonic_bounds - array([-0.83366337, 1.14653465, -1.54257426, 0.43762376]) + array([ 0, 59, 0, 59]) + >>> det.gnomonic_bounds[0, 0] + array([-0.83366337, 1.14653465, -0.83366337, 1.14653465]) >>> det.plot() """ self.shape = shape @@ -527,11 +527,8 @@ def plot( >>> from kikuchipy.detectors import EBSDDetector >>> det = EBSDDetector( ... shape=(60, 60), - ... pc=np.ones((149, 200)) * [0.421, 0.779, 0.505], + ... pc=np.ones((149, 200, 3)) * (0.421, 0.779, 0.505), ... convention="tsl", - ... pixel_size=70, - ... binning=8, - ... tilt=5, ... sample_tilt=70, ... ) >>> det.plot() diff --git a/kikuchipy/filters/window.py b/kikuchipy/filters/window.py index 943c940d..1c727492 100644 --- a/kikuchipy/filters/window.py +++ b/kikuchipy/filters/window.py @@ -63,8 +63,8 @@ class Window(np.ndarray): Examples -------- - >>> import kikuchipy as kp >>> import numpy as np + >>> import kikuchipy as kp The following passed parameters are the default @@ -95,9 +95,9 @@ class Window(np.ndarray): >>> w = kp.filters.Window(np.arange(6).reshape(3, 2)) >>> w Window (3, 2) custom - [[0, 1] - [2, 3] - [4, 5]] + [[0 1] + [2 3] + [4 5]] To create a Gaussian window with a standard deviation of 2, obtained from :func:`scipy.signal.windows.gaussian` @@ -105,9 +105,9 @@ class Window(np.ndarray): >>> w = kp.filters.Window(window="gaussian", std=2) >>> w Window (3, 3) gaussian - [[0.77880078, 0.8824969 , 0.77880078] - [0.8824969 , 1. , 0.8824969 ] - [0.77880078, 0.8824969 , 0.77880078]] + [[0.7788 0.8825 0.7788] + [0.8825 1. 0.8825] + [0.7788 0.8825 0.7788]] See Also -------- @@ -326,6 +326,8 @@ def plot( showing element values and x/y ticks, can be produced and written to file + >>> import kikuchipy as kp + >>> w = kp.filters.Window() >>> figure, image, colorbar = w.plot( ... cmap="inferno", grid=True, show_values=True) >>> figure.savefig('my_kernel.png') @@ -490,11 +492,14 @@ def lowpass_fft_filter( Examples -------- + >>> import numpy as np >>> import kikuchipy as kp >>> w1 = kp.filters.Window( - ... "lowpass", cutoff=30, cutoff_width=15, shape=(96, 96)) + ... "lowpass", cutoff=30, cutoff_width=15, shape=(96, 96) + ... ) >>> w2 = kp.filters.lowpass_fft_filter( - shape=(96, 96), cutoff=30, cutoff_width=15) + ... shape=(96, 96), cutoff=30, cutoff_width=15 + ... ) >>> np.allclose(w1, w2) True """ diff --git a/kikuchipy/generators/ebsd_simulation_generator.py b/kikuchipy/generators/ebsd_simulation_generator.py index 7bda9e34..c1c88e5f 100644 --- a/kikuchipy/generators/ebsd_simulation_generator.py +++ b/kikuchipy/generators/ebsd_simulation_generator.py @@ -59,26 +59,22 @@ def __init__( Examples -------- - >>> from orix.crystal_map import Phase - >>> from orix.quaternion import Rotation - >>> from kikuchipy.detectors import EBSDDetector - >>> from kikuchipy.generators import EBSDSimulationGenerator - >>> det = EBSDDetector( + >>> from orix import crystal_map, quaternion + >>> import kikuchipy as kp + >>> det = kp.detectors.EBSDDetector( ... shape=(60, 60), sample_tilt=70, pc=[0.5,] * 3 ... ) - >>> p = Phase(name="ni", space_group=225) + >>> p = crystal_map.Phase(name="ni", space_group=225) >>> p.structure.lattice.setLatPar(3.52, 3.52, 3.52, 90, 90, 90) - >>> simgen = EBSDSimulationGenerator( + >>> simgen = kp.generators.EBSDSimulationGenerator( ... detector=det, ... phase=p, ... rotations=Rotation.from_euler([90, 45, 90]) ... ) >>> simgen EBSDSimulationGenerator (1,) - EBSDDetector (60, 60), px_size 1 um, binning 1, tilt 0, pc - (0.5, 0.5, 0.5) - + EBSDDetector (60, 60), px_size 1 um, binning 1, tilt 0, pc (0.5, 0.5, 0.5) + Rotation (1,) """ self.detector = detector.deepcopy() @@ -130,7 +126,7 @@ def __repr__(self): f"{self.__class__.__name__} {self.navigation_shape}\n" f"{self.detector}\n" f"{self.phase}\n" - f"{rotation_repr}\n" + f"{rotation_repr}" ) def __getitem__(self, key): @@ -164,22 +160,27 @@ def geometrical_simulation( Examples -------- >>> from diffsims.crystallography import ReciprocalLatticePoint - >>> simgen - EBSDSimulationGenerator (1,) - EBSDDetector (60, 60), px_size 1 um, binning 1, tilt 0, pc - (0.5, 0.5, 0.5) - - Rotation (1,) + >>> from orix import crystal_map, quaternion + >>> import kikuchipy as kp + >>> det = kp.detectors.EBSDDetector( + ... shape=(60, 60), sample_tilt=70, pc=[0.5,] * 3 + ... ) + >>> p = crystal_map.Phase(name="ni", space_group=225) + >>> p.structure.lattice.setLatPar(3.52, 3.52, 3.52, 90, 90, 90) + >>> simgen = kp.generators.EBSDSimulationGenerator( + ... detector=det, + ... phase=p, + ... rotations=quaternion.Rotation.from_euler([0, 0, 0]) + ... ) >>> sim1 = simgen.geometrical_simulation() >>> sim1.bands.size 94 >>> rlp = ReciprocalLatticePoint( - ... phase=simgen.phase, hkl=[[1, 1, 1], [2, 0, 0]] - ... ) - >>> sim2 = simgen.geometrical_simulation() + ... phase=p, hkl=[[1, 1, 1], [2, 0, 0]] + ... ).symmetrise() + >>> sim2 = simgen.geometrical_simulation(rlp) >>> sim2.bands.size - 13 + 7 """ rlp = reciprocal_lattice_point if rlp is None and ( diff --git a/kikuchipy/generators/virtual_bse_generator.py b/kikuchipy/generators/virtual_bse_generator.py index ee695f37..25ad0fe9 100644 --- a/kikuchipy/generators/virtual_bse_generator.py +++ b/kikuchipy/generators/virtual_bse_generator.py @@ -201,13 +201,15 @@ def get_images_from_grid( Examples -------- + >>> import kikuchipy as kp + >>> s = kp.data.nickel_ebsd_large() >>> s - - >>> vbse_gen = VirtualBSEGenerator(s) + + >>> vbse_gen = kp.generators.VirtualBSEGenerator(s) >>> vbse_gen.grid_shape = (5, 5) >>> vbse = vbse_gen.get_images_from_grid() >>> vbse - + """ grid_shape = self.grid_shape new_shape = grid_shape + self.signal.axes_manager.navigation_shape[::-1] diff --git a/kikuchipy/io/_io.py b/kikuchipy/io/_io.py index f640a4fa..47f1f568 100644 --- a/kikuchipy/io/_io.py +++ b/kikuchipy/io/_io.py @@ -17,7 +17,7 @@ # along with kikuchipy. If not, see . import os -from typing import Optional, Union +from typing import Optional from hyperspy.io_plugins import hspy from hyperspy.misc.io.tools import overwrite as overwrite_method @@ -79,10 +79,12 @@ def load(filename: str, lazy: bool = False, **kwargs): Examples -------- + Import nine patterns from an HDF5 file in a directory `DATA_DIR` + >>> import kikuchipy as kp - >>> s = kp.load("patterns.h5") + >>> s = kp.load(DATA_DIR + "/patterns.h5") >>> s - + """ if not os.path.isfile(filename): raise IOError(f"No filename matches '{filename}'.") diff --git a/kikuchipy/projections/spherical_projection.py b/kikuchipy/projections/spherical_projection.py index 390ce059..405e6415 100644 --- a/kikuchipy/projections/spherical_projection.py +++ b/kikuchipy/projections/spherical_projection.py @@ -63,10 +63,10 @@ def project(cls, v: Union[Vector3d, np.ndarray]) -> np.ndarray: ... SphericalProjection ... ) >>> v = np.random.random_sample(30).reshape((10, 3)) - >>> theta, phi, r = SphericalProjection.project(v) - >>> np.allclose(np.arccos(v[: 2] / r), theta) + >>> theta, phi, r = SphericalProjection.project(v).T + >>> np.allclose(np.arccos(v[:, 2] / r), theta) True - >>> np.allclose(np.arctan2(v[:, 1], v[:, 2]), phi) + >>> np.allclose(np.arctan2(v[:, 1], v[:, 0]), phi) True """ return _get_polar(v) diff --git a/kikuchipy/signals/_common_image.py b/kikuchipy/signals/_common_image.py index c1ee4178..5ffebc57 100644 --- a/kikuchipy/signals/_common_image.py +++ b/kikuchipy/signals/_common_image.py @@ -91,27 +91,37 @@ def rescale_intensity( Examples -------- + >>> import numpy as np + >>> import kikuchipy as kp + >>> s = kp.data.nickel_ebsd_small() + Image intensities are stretched to fill the available grey levels in the input images' data type range or any :class:`numpy.dtype` range passed to `dtype_out`, either keeping relative intensities between images or not: - >>> print(s.data.dtype_out, s.data.min(), s.data.max(), - ... s.inav[0, 0].data.min(), s.inav[0, 0].data.max()) - uint8 20 254 24 233 + >>> print( + ... s.data.dtype, s.data.min(), s.data.max(), + ... s.inav[0, 0].data.min(), s.inav[0, 0].data.max() + ... ) + uint8 23 246 26 245 >>> s2 = s.deepcopy() - >>> s.rescale_intensity(dtype_out=np.uint16) - >>> print(s.data.dtype_out, s.data.min(), s.data.max(), - ... s.inav[0, 0].data.min(), s.inav[0, 0].data.max()) + >>> s.rescale_intensity(dtype_out=np.uint16) # doctest: +SKIP + >>> print( + ... s.data.dtype, s.data.min(), s.data.max(), + ... s.inav[0, 0].data.min(), s.inav[0, 0].data.max() + ... ) # doctest: +SKIP uint16 0 65535 0 65535 - >>> s2.rescale_intensity(relative=True) - >>> print(s2.data.dtype_out, s2.data.min(), s2.data.max(), - ... s2.inav[0, 0].data.min(), s2.inav[0, 0].data.max()) - uint8 0 255 4 232 + >>> s2.rescale_intensity(relative=True) # doctest: +SKIP + >>> print( + ... s2.data.dtype, s2.data.min(), s2.data.max(), + ... s2.inav[0, 0].data.min(), s2.inav[0, 0].data.max() + ... ) # doctest: +SKIP + uint8 0 255 3 253 Contrast stretching can be performed by passing percentiles: - >>> s.rescale_intensity(percentiles=(1, 99)) + >>> s.rescale_intensity(percentiles=(1, 99)) # doctest: +SKIP Here, the darkest and brightest pixels within the 1% percentile are set to the ends of the data type range, e.g. 0 and 255 @@ -199,11 +209,13 @@ def normalize_intensity( Examples -------- + >>> import numpy as np + >>> import kikuchipy as kp + >>> s = kp.data.nickel_ebsd_small() >>> np.mean(s.data) 146.0670987654321 - >>> s.change_dtype(np.float32) # Or passing dtype_out=np.float32 - >>> s.normalize_intensity() - >>> np.mean(s.data) + >>> s.normalize_intensity(dtype_out=np.float32) # doctest: +SKIP + >>> np.mean(s.data) # doctest: +SKIP 2.6373216e-08 Notes diff --git a/kikuchipy/signals/ebsd.py b/kikuchipy/signals/ebsd.py index afc36939..026f4143 100644 --- a/kikuchipy/signals/ebsd.py +++ b/kikuchipy/signals/ebsd.py @@ -215,9 +215,10 @@ def set_experimental_parameters( Examples -------- >>> import kikuchipy as kp - >>> ebsd_node = metadata_nodes("ebsd") + >>> s = kp.data.nickel_ebsd_small() + >>> ebsd_node = kp.signals.util.metadata_nodes("ebsd") >>> s.metadata.get_item(ebsd_node + '.xpc') - 1.0 + -5.64 >>> s.set_experimental_parameters(xpc=0.50726) >>> s.metadata.get_item(ebsd_node + '.xpc') 0.50726 @@ -316,20 +317,26 @@ def set_phase_parameters( Examples -------- + >>> import kikuchipy as kp + >>> s = kp.data.nickel_ebsd_small() >>> s.metadata.Sample.Phases.Number_1.atom_coordinates.Number_1 - ├── atom = - ├── coordinates = array([0., 0., 0.]) - ├── debye_waller_factor = 0.0 - └── site_occupation = 0.0 + ├── atom = Ni + ├── coordinates = array([0, 0, 0]) + ├── debye_waller_factor = 0.0035 + └── site_occupation = 1 >>> s.set_phase_parameters( - ... number=1, atom_coordinates={ - ... '1': {'atom': 'Ni', 'coordinates': [0, 0, 0], + ... number=1, + ... atom_coordinates={'1': { + ... 'atom': 'Fe', + ... 'coordinates': [0, 0, 0], ... 'site_occupation': 1, - ... 'debye_waller_factor': 0.0035}}) + ... 'debye_waller_factor': 0.005 + ... }} + ... ) >>> s.metadata.Sample.Phases.Number_1.atom_coordinates.Number_1 - ├── atom = Ni - ├── coordinates = array([0., 0., 0.]) - ├── debye_waller_factor = 0.0035 + ├── atom = Fe + ├── coordinates = array([0, 0, 0]) + ├── debye_waller_factor = 0.005 └── site_occupation = 1 """ # Ensure atom coordinates are numpy arrays @@ -375,11 +382,13 @@ def set_scan_calibration( Examples -------- - >>> s.axes_manager.['x'].scale # Default value - 1.0 - >>> s.set_scan_calibration(step_x=1.5) # Microns + >>> import kikuchipy as kp + >>> s = kp.data.nickel_ebsd_small() >>> s.axes_manager['x'].scale 1.5 + >>> s.set_scan_calibration(step_x=2) # Microns + >>> s.axes_manager['x'].scale + 2.0 """ x, y = self.axes_manager.navigation_axes x.name, y.name = ("x", "y") @@ -401,6 +410,8 @@ def set_detector_calibration(self, delta: Union[int, float]): Examples -------- + >>> import kikuchipy as kp + >>> s = kp.data.nickel_ebsd_small() >>> s.axes_manager['dx'].scale # Default value 1.0 >>> s.set_detector_calibration(delta=70.) @@ -455,21 +466,23 @@ def remove_static_background( >>> import kikuchipy as kp >>> ebsd_node = kp.signals.util.metadata_nodes("ebsd") + >>> s = kp.data.nickel_ebsd_small() >>> s.metadata.get_item(ebsd_node + '.static_background') - [[84 87 90 ... 27 29 30] - [87 90 93 ... 27 28 30] - [92 94 97 ... 39 28 29] - ... - [80 82 84 ... 36 30 26] - [79 80 82 ... 28 26 26] - [76 78 80 ... 26 26 25]] + array([[84, 87, 90, ..., 27, 29, 30], + [87, 90, 93, ..., 27, 28, 30], + [92, 94, 97, ..., 39, 28, 29], + ..., + [80, 82, 84, ..., 36, 30, 26], + [79, 80, 82, ..., 28, 26, 26], + [76, 78, 80, ..., 26, 26, 25]], dtype=uint8) The static background can be removed by subtracting or dividing this background from each pattern while keeping relative intensities between patterns (or not): >>> s.remove_static_background( - ... operation='subtract', relative=True) + ... operation='subtract', relative=True + ... ) # doctest: +SKIP If the metadata has no background pattern, this must be passed in the `static_bg` parameter as a numpy or dask array. @@ -593,13 +606,15 @@ def remove_dynamic_background( dynamic corrections (whether `relative` is set to True or False in :meth:`~remove_static_background`): - >>> s.remove_static_background(operation="subtract") + >>> import kikuchipy as kp + >>> s = kp.data.nickel_ebsd_small() + >>> s.remove_static_background(operation="subtract") # doctest: +SKIP >>> s.remove_dynamic_background( ... operation="subtract", # Default ... filter_domain="frequency", # Default ... truncate=4.0, # Default ... std=5, - ... ) + ... ) # doctest: +SKIP """ # Create a dask array of signal patterns and do the processing on this dtype = np.float32 @@ -783,19 +798,19 @@ def adaptive_histogram_equalization( >>> import numpy as np >>> import matplotlib.pyplot as plt - >>> s2 = s.inav[0, 0] - >>> s2.adaptive_histogram_equalization() - >>> imin = np.iinfo(s.data.dtype_out).min - >>> imax = np.iinfo(s.data.dtype_out).max + 1 + >>> import kikuchipy as kp + >>> s = kp.data.nickel_ebsd_small() + >>> s2 = s.inav[0, 0].deepcopy() + >>> s2.adaptive_histogram_equalization() # doctest: +SKIP >>> hist, _ = np.histogram( - ... s.inav[0, 0].data, bins=imax, range=(imin, imax)) - >>> hist2, _ = np.histogram( - ... s2.inav[0, 0].data, bins=imax, range=(imin, imax)) + ... s.inav[0, 0].data, bins=255, range=(0, 255) + ... ) + >>> hist2, _ = np.histogram(s2.data, bins=255, range=(0, 255)) >>> fig, ax = plt.subplots(nrows=2, ncols=2) - >>> ax[0, 0].imshow(s.inav[0, 0].data) - >>> ax[1, 0].plot(hist) - >>> ax[0, 1].imshow(s2.inav[0, 0].data) - >>> ax[1, 1].plot(hist2) + >>> _ = ax[0, 0].imshow(s.inav[0, 0].data) + >>> _ = ax[1, 0].plot(hist) + >>> _ = ax[0, 1].imshow(s2.data) + >>> _ = ax[1, 1].plot(hist2) Notes ----- @@ -862,8 +877,11 @@ def get_image_quality(self, normalize: bool = True) -> np.ndarray: Examples -------- - >>> iq = s.get_image_quality(normalize=True) # Default - >>> plt.imshow(iq) + >>> import matplotlib.pyplot as plt + >>> import kikuchipy as kp + >>> s = kp.data.nickel_ebsd_small() + >>> iq = s.get_image_quality(normalize=True) # doctest: +SKIP + >>> plt.imshow(iq) # doctest: +SKIP See Also -------- @@ -1012,14 +1030,17 @@ def fft_filter( Applying a Gaussian low pass filter with a cutoff frequency of 20 to an EBSD object ``s``: + >>> import kikuchipy as kp + >>> s = kp.data.nickel_ebsd_small() >>> pattern_shape = s.axes_manager.signal_shape[::-1] >>> w = kp.filters.Window( - ... "lowpass", cutoff=20, shape=pattern_shape) + ... "lowpass", cutoff=20, shape=pattern_shape + ... ) >>> s.fft_filter( ... transfer_function=w, ... function_domain="frequency", ... shift=True, - ... ) + ... ) # doctest: +SKIP See Also -------- @@ -1406,6 +1427,8 @@ def plot_virtual_bse_intensity( Examples -------- >>> import hyperspy.api as hs + >>> import kikuchipy as kp + >>> s = kp.data.nickel_ebsd_small() >>> roi = hs.roi.RectangularROI( ... left=0, right=5, top=0, bottom=5) >>> s.plot_virtual_bse_intensity(roi) @@ -1484,8 +1507,11 @@ def get_virtual_bse_intensity( Examples -------- >>> import hyperspy.api as hs + >>> import kikuchipy as kp >>> roi = hs.roi.RectangularROI( - ... left=0, right=5, top=0, bottom=5) + ... left=0, right=5, top=0, bottom=5 + ... ) + >>> s = kp.data.nickel_ebsd_small() >>> vbse_image = s.get_virtual_bse_intensity(roi) See Also diff --git a/kikuchipy/simulations/features.py b/kikuchipy/simulations/features.py index aef93b4e..9c70ce27 100644 --- a/kikuchipy/simulations/features.py +++ b/kikuchipy/simulations/features.py @@ -88,7 +88,7 @@ def __init__( KikuchiBand (|2) Phase: ni (m-3m) [[-1 1 1] - [ 0 -2 0]] + [-2 0 0]] """ super().__init__(phase=phase, hkl=hkl) self._hkl_detector = Vector3d(hkl_detector) diff --git a/setup.cfg b/setup.cfg index 99d714f5..25214959 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,15 @@ # Note that Black does not support setup.cfg [tool:pytest] -addopts = -ra +addopts = + -ra + --ignore=doc/_static/data/pattern_match_ni_large.py + --ignore=kikuchipy/data/bruker/create_bruker_h5ebsd_file.py + --ignore=kikuchipy/data/bruker/create_bruker_h5ebsd_file.py + --ignore=kikuchipy/data/emsoft_ebsd/create_dummy_emsoft_ebsd_file.py + --ignore=kikuchipy/data/emsoft_ebsd_master_pattern/master_patterns.h5 + --ignore=kikuchipy/data/emsoft_ebsd_master_pattern/convert_emsoft_ebsd_masterpattern_file_uint8_gzip.py +doctest_optionflags = NORMALIZE_WHITESPACE [coverage:run] source = kikuchipy diff --git a/setup.py b/setup.py index d148dba4..e8e34533 100644 --- a/setup.py +++ b/setup.py @@ -100,6 +100,7 @@ "SEM", "scanning electron microscopy", "kikuchi pattern", + "dictionary indexing", ], zip_safe=True, # Contact From b6bf7db9d95e015fb05e5f4bbf981243ef3b9f11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20Wiik=20=C3=85nes?= Date: Tue, 4 May 2021 18:10:36 +0200 Subject: [PATCH 2/7] Make pytest docstring tests ignore scripts for dummy data creation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- setup.cfg | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/setup.cfg b/setup.cfg index 25214959..ecd3aa76 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,12 +3,11 @@ [tool:pytest] addopts = -ra + # Documentation scripts --ignore=doc/_static/data/pattern_match_ni_large.py - --ignore=kikuchipy/data/bruker/create_bruker_h5ebsd_file.py - --ignore=kikuchipy/data/bruker/create_bruker_h5ebsd_file.py - --ignore=kikuchipy/data/emsoft_ebsd/create_dummy_emsoft_ebsd_file.py - --ignore=kikuchipy/data/emsoft_ebsd_master_pattern/master_patterns.h5 - --ignore=kikuchipy/data/emsoft_ebsd_master_pattern/convert_emsoft_ebsd_masterpattern_file_uint8_gzip.py + --ignore=doc/_static/image/doc_reference_frames.py + # Scripts for creating dummy EBSD files + --ignore-glob=kikuchipy/data/*/*.py doctest_optionflags = NORMALIZE_WHITESPACE [coverage:run] From edd9becacd10a6f9fb92332bc9f48326524cfb1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20Wiik=20=C3=85nes?= Date: Tue, 4 May 2021 18:17:33 +0200 Subject: [PATCH 3/7] Fix PR number in changelog link MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- doc/changelog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/changelog.rst b/doc/changelog.rst index 4e0cbb79..2f6825fd 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -22,7 +22,7 @@ Contributors Added ----- - Unit testing of docstring examples. - (`#? `_) + (`#350 `_) - Support for Python 3.9. (`#348 `_) - Projection/pattern center calibration via the moving screen technique in a From 0f9d6046f7fb8df32328500e9774263e956b7d80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20Wiik=20=C3=85nes?= Date: Tue, 4 May 2021 18:19:01 +0200 Subject: [PATCH 4/7] Use small data set in VBSE docstring test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- kikuchipy/generators/virtual_bse_generator.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kikuchipy/generators/virtual_bse_generator.py b/kikuchipy/generators/virtual_bse_generator.py index 25ad0fe9..e7f26ead 100644 --- a/kikuchipy/generators/virtual_bse_generator.py +++ b/kikuchipy/generators/virtual_bse_generator.py @@ -202,14 +202,14 @@ def get_images_from_grid( Examples -------- >>> import kikuchipy as kp - >>> s = kp.data.nickel_ebsd_large() + >>> s = kp.data.nickel_ebsd_small() >>> s - + >>> vbse_gen = kp.generators.VirtualBSEGenerator(s) >>> vbse_gen.grid_shape = (5, 5) >>> vbse = vbse_gen.get_images_from_grid() >>> vbse - + """ grid_shape = self.grid_shape new_shape = grid_shape + self.signal.axes_manager.navigation_shape[::-1] From 8f5f7383c822c19b7cc9170d360b98a72b6dc465 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20Wiik=20=C3=85nes?= Date: Tue, 4 May 2021 18:23:18 +0200 Subject: [PATCH 5/7] Import numpy in filters module docstrings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- kikuchipy/filters/window.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/kikuchipy/filters/window.py b/kikuchipy/filters/window.py index 1c727492..4436bee1 100644 --- a/kikuchipy/filters/window.py +++ b/kikuchipy/filters/window.py @@ -329,7 +329,8 @@ def plot( >>> import kikuchipy as kp >>> w = kp.filters.Window() >>> figure, image, colorbar = w.plot( - ... cmap="inferno", grid=True, show_values=True) + ... cmap="inferno", grid=True, show_values=True + ... ) >>> figure.savefig('my_kernel.png') """ if not self.is_valid(): @@ -439,6 +440,7 @@ def modified_hann(Nx: int) -> np.ndarray: Examples -------- + >>> import numpy as np >>> import kikuchipy as kp >>> w1 = kp.filters.modified_hann(Nx=30) >>> w2 = kp.filters.Window("modified_hann", shape=(30,)) @@ -559,11 +561,14 @@ def highpass_fft_filter( Examples -------- + >>> import numpy as np >>> import kikuchipy as kp >>> w1 = kp.filters.Window( - ... "highpass", cutoff=1, cutoff_width=0.5, shape=(96, 96)) + ... "highpass", cutoff=1, cutoff_width=0.5, shape=(96, 96) + ... ) >>> w2 = kp.filters.highpass_fft_filter( - ... shape=(96, 96), cutoff=1, cutoff_width=0.5) + ... shape=(96, 96), cutoff=1, cutoff_width=0.5 + ... ) >>> np.allclose(w1, w2) True """ From ffc9f4b4f2e45e944ea7e6277293e10355a7c339 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20Wiik=20=C3=85nes?= Date: Tue, 4 May 2021 20:06:06 +0200 Subject: [PATCH 6/7] Download nickel_ebsd_large dataset before pytest run MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- kikuchipy/conftest.py | 27 ++++++++++--------- .../tests/test_ebsd_simulation_generator.py | 2 +- setup.cfg | 8 ++++-- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/kikuchipy/conftest.py b/kikuchipy/conftest.py index 1763549b..8be32d83 100644 --- a/kikuchipy/conftest.py +++ b/kikuchipy/conftest.py @@ -25,7 +25,6 @@ from diffpy.structure import Atom, Lattice, Structure from diffsims.crystallography import ReciprocalLatticePoint -import hyperspy.api as hs from hyperspy import __version__ as hs_version from hyperspy.misc.utils import DictionaryTreeBrowser import matplotlib.pyplot as plt @@ -35,14 +34,11 @@ from orix.vector import Vector3d, neo_euler import pytest -from kikuchipy.detectors import EBSDDetector -from kikuchipy.generators import EBSDSimulationGenerator +import kikuchipy as kp from kikuchipy.projections.ebsd_projections import ( detector2reciprocal_lattice, detector2direct_lattice, ) -from kikuchipy.signals import EBSD -from kikuchipy.simulations.features import KikuchiBand, ZoneAxis # ------------------------- Helper functions ------------------------- # @@ -97,6 +93,13 @@ def _get_spatial_array_dicts( return d, map_size +# ------------------------------ Setup ------------------------------ # + + +def pytest_sessionstart(session): # pragma: no cover + _ = kp.data.nickel_ebsd_large(allow_download=True) + + # ----------------------------- Fixtures ----------------------------- # @@ -117,7 +120,7 @@ def dummy_signal(): dtype=np.uint8 ).reshape((3, 3, 3, 3)) # fmt: on - return EBSD(dummy_array) + return kp.signals.EBSD(dummy_array) @pytest.fixture @@ -176,7 +179,7 @@ def pc1(): @pytest.fixture(params=[(1,)]) def detector(request, pc1): """A NORDIF UF1100 EBSD detector with a TSL PC.""" - return EBSDDetector( + return kp.detectors.EBSDDetector( shape=(60, 60), binning=8, px_size=70, @@ -244,7 +247,7 @@ def nickel_ebsd_simulation_generator( """Generator for EBSD simulations of Kikuchi bands for the Nickel data set referenced above. """ - return EBSDSimulationGenerator( + return kp.generators.EBSDSimulationGenerator( detector=detector, phase=nickel_phase, rotations=nickel_rotations, ) @@ -258,7 +261,7 @@ def nickel_kikuchi_band(nickel_rlp, nickel_rotations, pc1): nav_shape = (5, 5) - detector = EBSDDetector( + detector = kp.detectors.EBSDDetector( shape=(60, 60), binning=8, px_size=70, @@ -292,7 +295,7 @@ def nickel_kikuchi_band(nickel_rlp, nickel_rotations, pc1): hkl_detector[is_in_some_pattern], source=0, destination=nav_dim ) - return KikuchiBand( + return kp.simulations.features.KikuchiBand( phase=phase, hkl=hkl, hkl_detector=hkl_detector, @@ -309,7 +312,7 @@ def nickel_zone_axes(nickel_kikuchi_band, nickel_rotations, pc1): nav_shape = (5, 5) - detector = EBSDDetector( + detector = kp.detectors.EBSDDetector( shape=(60, 60), binning=8, px_size=70, @@ -345,7 +348,7 @@ def nickel_zone_axes(nickel_kikuchi_band, nickel_rotations, pc1): uvw_detector[is_in_some_pattern], source=0, destination=nav_dim ) - return ZoneAxis( + return kp.simulations.features.ZoneAxis( phase=phase, uvw=uvw, uvw_detector=uvw_detector, diff --git a/kikuchipy/generators/tests/test_ebsd_simulation_generator.py b/kikuchipy/generators/tests/test_ebsd_simulation_generator.py index 5b013c11..1fb21081 100644 --- a/kikuchipy/generators/tests/test_ebsd_simulation_generator.py +++ b/kikuchipy/generators/tests/test_ebsd_simulation_generator.py @@ -126,7 +126,7 @@ def test_repr(self, nickel_ebsd_simulation_generator): "pc (0.421, 0.221, 0.505)\n" "\n" - "Rotation (25,)\n" + "Rotation (25,)" ) assert repr(nickel_ebsd_simulation_generator) == desired_repr diff --git a/setup.cfg b/setup.cfg index ecd3aa76..7c10fb69 100644 --- a/setup.cfg +++ b/setup.cfg @@ -6,8 +6,11 @@ addopts = # Documentation scripts --ignore=doc/_static/data/pattern_match_ni_large.py --ignore=doc/_static/image/doc_reference_frames.py - # Scripts for creating dummy EBSD files - --ignore-glob=kikuchipy/data/*/*.py + # Scripts for creating dummy data + --ignore=kikuchipy/data/bruker/create_bruker_h5ebsd_file.py + --ignore=kikuchipy/data/bruker/create_bruker_h5ebsd_file.py + --ignore=kikuchipy/data/emsoft_ebsd/create_dummy_emsoft_ebsd_file.py + --ignore-glob=kikuchipy/data/emsoft_ebsd_master_pattern/*.py doctest_optionflags = NORMALIZE_WHITESPACE [coverage:run] @@ -15,6 +18,7 @@ source = kikuchipy omit = setup.py kikuchipy/release.py +# kikuchipy/conftest.py relative_files = True [coverage:report] From 18f7f0a878d3a1465f0bce747856793ab9ad913b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20Wiik=20=C3=85nes?= Date: Tue, 4 May 2021 20:17:59 +0200 Subject: [PATCH 7/7] Make build run docstring tests only on ubuntu-latest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- .github/workflows/build.yml | 1 + setup.cfg | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 65ae7a1a..5b041110 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -35,6 +35,7 @@ jobs: if: ${{ matrix.OLDEST_SUPPORTED_VERSION }} run: pip install ${{ matrix.DEPENDENCIES }} - name: Run docstring tests + if: ${{ matrix.os == 'ubuntu-latest' }} run: pytest --doctest-modules --ignore-glob=kikuchipy/*/tests - name: Run tests run: pytest --cov=kikuchipy --pyargs kikuchipy diff --git a/setup.cfg b/setup.cfg index 7c10fb69..1e8f0246 100644 --- a/setup.cfg +++ b/setup.cfg @@ -18,7 +18,6 @@ source = kikuchipy omit = setup.py kikuchipy/release.py -# kikuchipy/conftest.py relative_files = True [coverage:report]