Skip to content

Commit 8199e2e

Browse files
committed
Merge branch 'master' into master-reorg
2 parents 4d75310 + 003da9d commit 8199e2e

File tree

16 files changed

+4381
-5030
lines changed

16 files changed

+4381
-5030
lines changed

hexrd/core/gridutil.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,10 @@ def cellCentroids(crd, con):
146146

147147
@numba.njit(nogil=True, cache=True)
148148
def compute_areas(xy_eval_vtx, conn):
149+
# NOTE: this function may return negative areas if the vertices
150+
# are passed in the opposite order to the function. This happens
151+
# if the beam vector is in the opposite direction (positive Z
152+
# instead of the usual negative Z)
149153
areas = np.empty(len(conn))
150154
for i in range(len(conn)):
151155
vtx0x, vtx0y = xy_eval_vtx[conn[i, 0]]

hexrd/core/instrument/detector.py

Lines changed: 103 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
PHOSPHOR_DEFAULT,
1010
)
1111
from hexrd.core.instrument.physics_package import AbstractPhysicsPackage
12+
import numba
1213
import numpy as np
1314

1415
from hexrd.core import constants as ct
@@ -1122,11 +1123,9 @@ def interpolate_nearest(self, xy, img, pad_with_nans=True):
11221123

11231124
def interpolate_bilinear(
11241125
self,
1125-
xy,
1126-
img,
1127-
pad_with_nans=True,
1128-
clip_to_panel=True,
1129-
on_panel: Optional[np.ndarray] = None,
1126+
xy: np.ndarray,
1127+
img: np.ndarray,
1128+
pad_with_nans: bool = True,
11301129
):
11311130
"""
11321131
Interpolate an image array at the specified cartesian points.
@@ -1137,13 +1136,10 @@ def interpolate_bilinear(
11371136
Array of cartesian coordinates in the image plane at which
11381137
to evaluate intensity.
11391138
img : array_like
1140-
2-dimensional image array.
1139+
2-dimensional image array. The shape must match (rows, cols).
11411140
pad_with_nans : bool, optional
11421141
Toggle for assigning NaN to points that fall off the detector.
11431142
The default is True.
1144-
on_panel : np.ndarray, optional
1145-
If you want to skip clip_to_panel() for performance reasons,
1146-
just provide an array of which pixels are on the panel.
11471143
11481144
Returns
11491145
-------
@@ -1155,28 +1151,30 @@ def interpolate_bilinear(
11551151
-----
11561152
TODO: revisit normalization in here?
11571153
"""
1154+
fill_value = np.nan if pad_with_nans else 0
1155+
int_xy = np.full(len(xy), fill_value, dtype=float)
11581156

1159-
is_2d = img.ndim == 2
1160-
right_shape = img.shape[0] == self.rows and img.shape[1] == self.cols
1161-
assert (
1162-
is_2d and right_shape
1163-
), "input image must be 2-d with shape (%d, %d)" % (
1164-
self.rows,
1165-
self.cols,
1166-
)
1157+
# clip away points too close to or off the detector edges
1158+
xy_clip, on_panel = self.clip_to_panel(xy, buffer_edges=True)
11671159

1168-
# initialize output with nans
1169-
if pad_with_nans:
1170-
int_xy = np.nan * np.ones(len(xy))
1171-
else:
1172-
int_xy = np.zeros(len(xy))
1160+
# Generate the interpolation dict
1161+
interp_dict = self._generate_bilinear_interp_dict(xy_clip)
11731162

1174-
if on_panel is None:
1175-
# clip away points too close to or off the edges of the detector
1176-
xy_clip, on_panel = self.clip_to_panel(xy, buffer_edges=True)
1177-
else:
1178-
xy_clip = xy[on_panel]
1163+
# Set the output and return
1164+
int_xy[on_panel] = _interpolate_bilinear(img, **interp_dict)
1165+
return int_xy
11791166

1167+
def _generate_bilinear_interp_dict(
1168+
self,
1169+
xy_clip: np.ndarray,
1170+
) -> dict[str, np.ndarray]:
1171+
"""Compute bilinear interpolation multipliers and indices for the panel
1172+
1173+
If you are going to be using the same panel settings and performing
1174+
interpolation on multiple images, it is advised to run this beforehand
1175+
to precompute the interpolation parameters, so you can use them
1176+
repeatedly.
1177+
"""
11801178
# grab fractional pixel indices of clipped points
11811179
ij_frac = self.cartToPixel(xy_clip)
11821180

@@ -1196,20 +1194,24 @@ def interpolate_bilinear(
11961194
j_ceil = j_floor + 1
11971195
j_ceil_img = _fix_indices(j_ceil, 0, self.cols - 1)
11981196

1199-
# first interpolate at top/bottom rows
1200-
row_floor_int = (j_ceil - ij_frac[:, 1]) * img[
1201-
i_floor_img, j_floor_img
1202-
] + (ij_frac[:, 1] - j_floor) * img[i_floor_img, j_ceil_img]
1203-
row_ceil_int = (j_ceil - ij_frac[:, 1]) * img[
1204-
i_ceil_img, j_floor_img
1205-
] + (ij_frac[:, 1] - j_floor) * img[i_ceil_img, j_ceil_img]
1206-
1207-
# next interpolate across cols
1208-
int_vals = (i_ceil - ij_frac[:, 0]) * row_floor_int + (
1209-
ij_frac[:, 0] - i_floor
1210-
) * row_ceil_int
1211-
int_xy[on_panel] = int_vals
1212-
return int_xy
1197+
# Compute differences between raw coordinates to use for interpolation
1198+
j_ceil_sub = j_ceil - ij_frac[:, 1]
1199+
j_floor_sub = ij_frac[:, 1] - j_floor
1200+
i_ceil_sub = i_ceil - ij_frac[:, 0]
1201+
i_floor_sub = ij_frac[:, 0] - i_floor
1202+
1203+
return {
1204+
# Compute interpolation multipliers for every pixel
1205+
'cc': j_ceil_sub * i_ceil_sub,
1206+
'fc': j_floor_sub * i_ceil_sub,
1207+
'cf': j_ceil_sub * i_floor_sub,
1208+
'ff': j_floor_sub * i_floor_sub,
1209+
# Store needed pixel indices
1210+
'i_floor_img': i_floor_img,
1211+
'j_floor_img': j_floor_img,
1212+
'i_ceil_img': i_ceil_img,
1213+
'j_ceil_img': j_ceil_img,
1214+
}
12131215

12141216
def make_powder_rings(
12151217
self,
@@ -2138,3 +2140,63 @@ def _row_edge_vec(rows, pixel_size_row):
21382140

21392141
def _col_edge_vec(cols, pixel_size_col):
21402142
return pixel_size_col * (np.arange(cols + 1) - 0.5 * cols)
2143+
2144+
2145+
@numba.njit(nogil=True, cache=True)
2146+
def _interpolate_bilinear(
2147+
img: np.ndarray,
2148+
cc: np.ndarray,
2149+
fc: np.ndarray,
2150+
cf: np.ndarray,
2151+
ff: np.ndarray,
2152+
i_floor_img: np.ndarray,
2153+
j_floor_img: np.ndarray,
2154+
i_ceil_img: np.ndarray,
2155+
j_ceil_img: np.ndarray,
2156+
) -> np.ndarray:
2157+
# The math is faster and uses the GIL less (which is more
2158+
# multi-threading friendly) when we run this code in numba.
2159+
result = np.zeros(i_floor_img.shape[0], dtype=img.dtype)
2160+
on_panel_idx = np.arange(i_floor_img.shape[0])
2161+
_interpolate_bilinear_in_place(
2162+
img,
2163+
cc,
2164+
fc,
2165+
cf,
2166+
ff,
2167+
i_floor_img,
2168+
j_floor_img,
2169+
i_ceil_img,
2170+
j_ceil_img,
2171+
on_panel_idx,
2172+
result,
2173+
)
2174+
return result
2175+
2176+
2177+
@numba.njit(nogil=True, cache=True)
2178+
def _interpolate_bilinear_in_place(
2179+
img: np.ndarray,
2180+
cc: np.ndarray,
2181+
fc: np.ndarray,
2182+
cf: np.ndarray,
2183+
ff: np.ndarray,
2184+
i_floor_img: np.ndarray,
2185+
j_floor_img: np.ndarray,
2186+
i_ceil_img: np.ndarray,
2187+
j_ceil_img: np.ndarray,
2188+
on_panel_idx: np.ndarray,
2189+
output_img: np.ndarray,
2190+
):
2191+
# The math is faster and uses the GIL less (which is more
2192+
# multi-threading friendly) when we run this code in numba.
2193+
# Running in-place eliminates some intermediary arrays for
2194+
# even faster performance.
2195+
for i in range(on_panel_idx.shape[0]):
2196+
idx = on_panel_idx[i]
2197+
output_img[idx] += (
2198+
cc[i] * img[i_floor_img[i], j_floor_img[i]] +
2199+
fc[i] * img[i_floor_img[i], j_ceil_img[i]] +
2200+
cf[i] * img[i_ceil_img[i], j_floor_img[i]] +
2201+
ff[i] * img[i_ceil_img[i], j_ceil_img[i]]
2202+
)

hexrd/core/instrument/hedm_instrument.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2941,6 +2941,12 @@ def _extract_ring_line_positions(
29412941
# strip relevant objects out of current patch
29422942
vtx_angs, vtx_xys, conn, areas, xys_eval, ijs = patch
29432943

2944+
# These areas can be negative if the beam vector is in
2945+
# the opposite direction than it normally is in (positive
2946+
# Z instead of the usual negative Z). Take the absolute
2947+
# value of the areas to ensure they are positive.
2948+
areas = np.abs(areas)
2949+
29442950
# need to reshape eval pts for interpolation
29452951
xy_eval = np.vstack([xys_eval[0].flatten(), xys_eval[1].flatten()]).T
29462952

hexrd/core/material/unitcell.py

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -592,28 +592,19 @@ def CalcStar(self, v, space, applyLaue=False):
592592
this function calculates the symmetrically equivalent hkls (or uvws)
593593
for the reciprocal (or direct) point group symmetry.
594594
'''
595-
if space == 'd':
596-
mat = self.dmt.astype(np.float64)
597-
if applyLaue:
598-
sym = self.SYM_PG_d_laue.astype(np.float64)
599-
else:
600-
sym = self.SYM_PG_d.astype(np.float64)
601-
elif space == 'r':
602-
mat = self.rmt.astype(np.float64)
603-
if applyLaue:
604-
sym = self.SYM_PG_r_laue.astype(np.float64)
605-
else:
606-
sym = self.SYM_PG_r.astype(np.float64)
607-
elif space == 'c':
595+
if space not in ('d', 'r', 'c'):
596+
raise ValueError('CalcStar: unrecognized space.')
597+
598+
if space == 'c':
608599
mat = np.eye(3)
609-
if applyLaue:
610-
sym = self.SYM_PG_c_laue.astype(np.float64)
611-
else:
612-
sym = self.SYM_PG_c.astype(np.float64)
613600
else:
614-
raise ValueError('CalcStar: unrecognized space.')
601+
mat = (self.dmt if space == 'd' else self.rmt).astype(float)
602+
603+
suffix = '_laue' if applyLaue else ''
604+
name = f'SYM_PG_{space}{suffix}'
605+
sym = getattr(self, name).astype(float)
615606

616-
vv = np.array(v).astype(np.float64)
607+
vv = np.asarray(v).astype(float)
617608
return _calcstar(vv, sym, mat)
618609

619610
def CalcPositions(self):

0 commit comments

Comments
 (0)