Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
# IMPORTANT: this permission is mandatory for trusted publishing:
id-token: write
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7

- name: Set up Python
uses: actions/setup-python@v6
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
name: check manifest
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7

- uses: actions/setup-python@v6
with:
Expand Down Expand Up @@ -49,7 +49,7 @@ jobs:
DEPENDENCIES: matplotlib==3.8 numba==0.56.4 pyopencl==2023.1.2
LABEL: -oldest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
Expand Down
33 changes: 33 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,39 @@ Changelog
All notable changes to PyEBSDIndex will be documented in this file. The format is based
on `Keep a Changelog <https://keepachangelog.com/en/1.1.0>`_.


0.3.10.1 (2026-07-03)
==================
BUG/ERROR FIX RELEASE
---------------------
There was an error when attempting to multi-process GPU calculations in NLPAR/NLSTEM
that caused new patterns to not be written to the NLPAR/NLSTEM files when using HDF5 as output.
With the GPU enabled calculations, non-parallel diskIO ends up being the rate limiting step, and
thus the multiprocess NLPAR is no longer the default method for discrete GPUs, and a single process
GPU is now the default. Overall safer, even if it does take longer to process files.

The NLPAR calculations are still done in parallel, either with GPU or multithreaded CPU calculations.

This should require no changes from the examples behaviors outlined in the tutorials.

A thanks to Dan Pennachio for alerting me to this issue and useful example data.

Added
-----

Changed
-------

Fixed
-----
- Properly send in the vendor keyword to gnomonic correction setup
(currently no correction is applied).
- Removed multi-process NLPAR as default for discrete GPUs as it mostly was fasteer due-to
slightly risky multi-process file writes, which are not compatible with standard HDF5.
- Fixed region-to-region minimum scaling for ``stem_scale=True`` in NLPAR, now using the file
global minimum.


0.3.10 (2026-05-11)
==================
Added
Expand Down
2 changes: 1 addition & 1 deletion pyebsdindex/__version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.3.10"
__version__ = "0.3.10.1"
6 changes: 4 additions & 2 deletions pyebsdindex/_ebsd_index_parallel.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
from pyebsdindex import band_detect as band_detect

os.environ["GRPC_VERBOSITY"] = "ERROR"
os.environ["RAY_ACCEL_ENV_VAR_OVERRIDE_ON_ZERO"] = "0"

RAYIPADDRESS = '127.0.0.1'
#RAYIPADDRESS = '0.0.0.0'
Expand Down Expand Up @@ -493,7 +494,7 @@ def index_pats_distributed(
)
)
gtaskindex.append(gjob)

time.sleep(1)
#gpu_launched += 1

gpuwrker_cycles = -1000
Expand Down Expand Up @@ -524,7 +525,8 @@ def index_pats_distributed(

gpuwrker_cycles +=1

donewrker, busy = ray.wait(gputask,num_returns=len(gputask), timeout=0.1)
#donewrker, busy = ray.wait(gputask,num_returns=len(gputask), timeout=0.1)
donewrker, busy = ray.wait(gputask, num_returns=min(1,len(gputask)), timeout=0.1)

#print(len(donewrker), nret)
#print()
Expand Down
3 changes: 2 additions & 1 deletion pyebsdindex/_ebsd_index_single.py
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,7 @@ def __init__(
("nmatch", np.int32),
("matchattempts", np.int32, 4),
("totvotes", np.int32),
("grainid", np.int64),
]
)

Expand Down Expand Up @@ -560,7 +561,7 @@ def index_pats(
except:
pass

self.gnomonic.calccorrection(PCpat)
#self.gnomonic.calccorrection(PCpat)
banddata, bandnorm = self._detectbands(pats, PCpat, xyloc=xyloc, clparams=clparams, verbose=verbose,
chunksize=chunksize, gpu_id=gpuid)
tic = timer()
Expand Down
50 changes: 45 additions & 5 deletions pyebsdindex/ebsd_pattern.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,8 @@ def __init__(self,path, filetype=None):
self.xStep = None # assumming square grid data, with constant step size
self.yStep = None
self.xyCenter = np.array([0.0, 0.0])
self.datamax = None
self.datamin = None
# This is the location of the center of the scan relative to center of SEM field-of-view
self.hexflag = False
self.filetype = filetype
Expand Down Expand Up @@ -423,6 +425,12 @@ def set_scan_rc(self, rc=(0,0)): # helper function for pattern files that don't
self.nRows = np.uint64(rc[0])
self.nPatterns = np.uint64(self.nCols * self.nRows)

def write_datamaxmin(self): # placeholder for non-HDF5 files
pass

def read_datamaxmin(self): # placeholder for non-HDF5 files
pass


class UPFile(EBSDPatternFile):

Expand Down Expand Up @@ -1547,6 +1555,37 @@ def pat_writer(self, pat2write, patStart, nPatToWrite, typewrite):
patterndset[patStart:patStart+nPatToWrite, :, :] = pat2write[0:nPatToWrite,:,:]
f.close()

def write_datamaxmin(self):
try:
f = h5py.File(Path(self.filepath).expanduser(), 'r+')
except:
print("File Not Found:", str(Path(self.filepath)))
return -1

patterndset = f[self.h5patdatpth]

if self.datamax is not None:
patterndset.attrs["DataMax"] = np.float32(self.datamax)
if self.datamin is not None:
patterndset.attrs["DataMin"] = np.float32(self.datamin)
f.close()

def read_datamaxmin(self):
try:
f = h5py.File(Path(self.filepath).expanduser(), 'r+')

except:
print("File Not Found:", str(Path(self.filepath)))
return -1

patterndset = f[self.h5patdatpth]

if 'DataMax' in patterndset.attrs:
self.datamax = np.float32(patterndset.attrs["DataMax"])
if 'DataMin' in patterndset.attrs:
self.datamin = np.float32(patterndset.attrs["DataMin"])
f.close()

def read_header(self, path=None):
if path is not None:
self.filepath = path
Expand Down Expand Up @@ -1931,10 +1970,9 @@ class DM5(HDF5PatFile):
def __init__(self, path=None):
HDF5PatFile.__init__(self, path)
self.vendor = 'GATAN'
# OXFORDOINA only attributes
self.filedatatype = None # np.uint8
self.patternh5id = 'Data' # Could also be 'Raw Patterns'

self.filedatatype = None # np.uint8
self.patternh5id = 'Data'
if self.filepath is not None:
self.get_data_paths()

Expand Down Expand Up @@ -2012,7 +2050,8 @@ def read_header(self, path=None):
#self.yStep = 1.0 #np.float32(headerpath['Y Step'][()][0])
self.xStep = ((f[self.h5patdatpth].parent)["Calibrations/Dimension/[2]"]).attrs['Scale'] #np.float32(headerpath['X Step'][()][0])
self.yStep = ((f[self.h5patdatpth].parent)["Calibrations/Dimension/[3]"]).attrs['Scale']

f.close()
self.read_datamaxmin()
return 0 # note this function uses multiple returns

def read_data(self, path=None, convertToFloat=False, patStartCount=[0, -1], returnArrayOnly=False):
Expand Down Expand Up @@ -2215,4 +2254,5 @@ def write_data(self, newpatterns=None, patStartCount = [0,-1], writeHead=False,

patterndset[int(rowstart):int(rowstart + nrowwrite),
int(colstart):int(colstart + ncolwrite), :, :] = pat2write
f.close()
f.close()

10 changes: 9 additions & 1 deletion pyebsdindex/ebsdfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,15 @@ def writeoh5(filename, indexer, data,
f.create_dataset(datasetname + '/EBSD/Data/X Position', data=x)
f.create_dataset(datasetname + '/EBSD/Data/Y Position', data=y)
f.create_dataset(datasetname + '/EBSD/Data/Valid', data=np.zeros(npoints, dtype=np.int8))
f.create_dataset(datasetname + '/EBSD/Data/SEM Signal', data=np.zeros(npoints, dtype=np.int32))
semsig = (data[-1]['pq']).copy()
semsig -= semsig.min()
semsig *= float(int(2**8 - 1))/semsig.max()

semsig = (np.round(semsig)-int(2**7-1)).astype(np.int8)

f.create_dataset(datasetname + '/EBSD/Data/SEM Signal', data=semsig)
f.create_dataset(datasetname + '/EBSD/Data/Grain ID', data=data[-1]['grainid'])


if version == '8.6':
versiontxt = 'OIM Analysis 8.6.103 x64 [29 Sep 2022]'
Expand Down
Loading
Loading