Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid out-of-bounds values in Lambert interpolation parameters #718

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from

Conversation

tgwoodcock
Copy link
Contributor

Calling kikuchipy.signals.util._master_pattern_project_single_pattern_from_master_pattern(), sometimes results in the kernel dying without any error message.

The outputs nii, nij, niip and nijp of _get_lambert_interpolation_parameters() define the indices used by the function _get_pixel_from_master_pattern() to select a pixel from a master pattern hemisphere in the Lambert projection.

Normally, trying to access out of bounds indices would result in an IndexError; however, the function _get_pixel_from_master_pattern() is optimized with Numba and according to the Numba docs, by default, no IndexError is raised and either invalid values are returned or an access violation error occurs.

In order to avoid this, we need to make a small change to _get_lambert_interpolation_parameters(). The parameters nii and niip must be in the range 0 <= n < npy and nij and nijp must be in the range 0 <= n < npx. The outputs nii and nij are automatically in these ranges; however, niip and nijp may not be and we need to exclude indices which are < 0 or >= npx or >= npy.

Description of the change

Previously, _get_lambert_interpolation_parameters() checked if niip > npy and nijp > npx, which led to some values being out of bounds. This has now been changed to niip >= npy and nijp >= npx to avoid returning out-of-bounds indices. The function _get_lambert_interpolation_parameters() now only outputs indices which are in range for the master pattern and therefore no further problems in the Numba-optimized _get_pixel_from_master_pattern() are caused.

Progress of the PR

Minimal example of the bug fix or new feature

>>> import numpy as np
>>> from orix.quaternion import Rotation
>>> from kikuchipy.signals.util._master_pattern import (
... _project_single_pattern_from_master_pattern,
... _get_lambert_interpolation_parameters,
... )
>>> from kikuchipy._utils.numba import rotate_vector
>>>  # simulate master pattern hemisphere
>>> sim_mp = np.random.random(size=1001*1001).reshape((1001, 1001,)).astype(np.float32)
>>> # make some vectors to test
>>> vecs_all = np.array([[1, 0, 0],
                     [0, 1, 0],
                     [0, 0, 1],
                     [-1, 0, 0],
                     [0, -1, 0],
                     [0, 0, -1]], dtype=np.float64)
>>> #  Rotate the detector's view of the crystal (as in _project_single_pattern_from_master_pattern())
>>> rot_0 = Rotation.identity().data[0]
>>> dc_rotated = rotate_vector(rot_0, vecs_all)
>>> # check if these are identical:
>>> print(f"vecs_all and dc_rotated are identical: {(vecs_all == dc_rotated).all()}")
vecs_all and dc_rotated are identical: True
>>> # check the min and max
>>> print(f"dc_rotated, min: {dc_rotated.min()}, max: {dc_rotated.max()}")
dc_rotated, min: -1.0, max: 1.0
>>> # get and analyse the Lambert interpolation parameters
>>> npx, npy = sim_mp.shape
>>> scale = (npx - 1) / 2
>>> (nii, nij, niip, nijp, di, dj, dim, djm) = _get_lambert_interpolation_parameters(
... v=dc_rotated, npx=npx, npy=npy, scale=scale
... )
>>> # check the min and max values of Lambert interpolation parameters
>>> names = ["nii", "nij", "niip", "nijp", "di", "dj", "dim", "djm"]
>>> things = [nii, nij, niip, nijp, di, dj, dim, djm]
>>> for i, j in enumerate(names):
... print(f"{j} max: {np.max(things[i])}, min: {np.min(things[i])}")
nii max: 1000, min: 0
nij max: 1000, min: 0
niip max: 1001, min: 1
nijp max: 1001, min: 1
di max: 0.0, min: -5.684341886080802e-14
dj max: 0.0, min: -5.684341886080802e-14
dim max: 1.0000000000000568, min: 1.0
djm max: 1.0000000000000568, min: 1.0
>>> # see where the min and max values are
>>> print("where niip == 1001:", np.where(niip == 1001))
where niip == 1001: (array([1]),)
>>> print("where nijp == 1001:", np.where(nijp == 1001))
where nijp == 1001: (array([0]),)
>>> # Test vectors with IN-RANGE Lambert parameters (indices 2:)
>>> ## On the current develop branch and the PR branch:
>>> ##    this call passes because there are no coordinates out of range.
>>> pix_val_IR = _project_single_pattern_from_master_pattern(rot_0,
... vecs_all[2:],
... sim_mp,
... sim_mp,
... npx,
... npy,
... scale,
... False,
... 0,
... 1,
... np.float64,
... )
>>> # Test first vector with OUT-OF-RANGE Lambert parameters (index 0)
>>> ## On the current develop branch:
>>> ##    this call delivers an incorrect value.
>>> ##    nijp is out of range and an IndexError should be raised.
>>> ## On the PR branch:
>>> ##    call passes and delivers a correct value (no out of range coords).
>>> pix_val_0 = _project_single_pattern_from_master_pattern(rot_0,
... np.expand_dims(vecs_all[0], 0),
... sim_mp,
... sim_mp,
... npx,
... npy,
... scale,
... False,
... 0,
... 1,
... np.float64,
... )
>>> # Test second vector with OUT-OF-RANGE Lambert parameters (index 1)
>>> ## On the current develop branch:
>>> ##  this call results in the kernel dying.
>>> ##   niip is out of range and an IndexError should be raised.
>>> ## On the PR branch:
>>> ##   call passes and delivers a correct value (no out of range coords).
>>> pix_val_1 = _project_single_pattern_from_master_pattern(rot_0,
... np.expand_dims(vecs_all[1], 0),
... sim_mp,
... sim_mp,
... npx,
... npy,
... scale,
... False,
... 0,
... 1,
... np.float64,
... )
The kernel died, restarting...

For reviewers

  • The PR title is short, concise, and will make sense 1 year later.
  • New functions are imported in corresponding __init__.py.
  • New features, API changes, and deprecations are mentioned in the unreleased
    section in CHANGELOG.rst.
  • New contributors are added to kikuchipy/__init__.py and .zenodo.json.

The outputs nii, nij, niip and nijp of _get_lambert_interpolation_parameters()
are used by the function _get_pixel_from_master_pattern() to select a pixel.

In order to avoid IndexError, nii and niip must be in the range
0 <= n < npy and nij and nijp must be in the range
0 <= n < npx. The outputs nii and nij are automatically in these
ranges; however, niip and nijp may not be and we need to *exclude*
indices which are < 0 or >= npx or >= npy.

Previously, _get_lambert_interpolation_parameters() checked if
niip and nijp were > npx or > npy. This has now been changed to
>= npx and >= npy to fulfil the above conditions. This function
now only outputs indices which are in range for the master pattern
and therefore no further problems in _get_pixel_from_master_pattern()
are caused.

Signed-off-by: tgwoodcock <[email protected]>
Copy link

codecov bot commented Jan 17, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Please upload report for BASE (develop@aae3a10). Learn more about missing BASE report.

Additional details and impacted files
@@            Coverage Diff             @@
##             develop     #718   +/-   ##
==========================================
  Coverage           ?   98.14%           
==========================================
  Files              ?       94           
  Lines              ?     7337           
  Branches           ?      990           
==========================================
  Hits               ?     7201           
  Misses             ?       50           
  Partials           ?       86           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant