From 5f9b9a33e784981b0fd07153ccea6aa63ca01164 Mon Sep 17 00:00:00 2001 From: Rhys Thomas <19435305+rhysgt@users.noreply.github.com> Date: Thu, 15 Oct 2026 16:19:09 +0100 Subject: [PATCH 1/4] fix: Handle non-integer x, y in pyvale PyVale can produce files where x and y are not integers. This used to be accepted by numpy <2.1.3, but load_text is more strict since 2.5.2. This commit changes the data format of x and y to be double. Fixes issue #157 --- defdap/file_readers.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/defdap/file_readers.py b/defdap/file_readers.py index 8b9539e..d437ae0 100644 --- a/defdap/file_readers.py +++ b/defdap/file_readers.py @@ -988,8 +988,8 @@ def load(self, file_name: pathlib.Path) -> None: int_type = 'int32' double_type = 'double' data_format = np.dtype([ - ('x', int_type), - ('y', int_type), + ('x', double_type), + ('y', double_type), ('u', double_type), ('v', double_type), ('displacement_mag', double_type), @@ -1026,12 +1026,12 @@ def load(self, file_name: pathlib.Path) -> None: yx_max = yx_array.max(axis=0) shape = yx_max - yx_min assert np.allclose(shape % binning, 0.) + shape = np.rint(shape).astype(int) shape = tuple((shape // binning + 1).tolist()) self.loaded_metadata['shape'] = shape self.checkMetadata() - - index_array = (yx_array - yx_min) // binning + index_array = np.rint((yx_array - yx_min) / binning).astype(int) disp_dense = np.zeros(shape + (2,)) disp_dense[index_array[:, 0], index_array[:, 1]] = ( structured_to_unstructured(data[['u', 'v']])) From 2eed82e7c20dbc5bbd0499400febaf59dc9a1148 Mon Sep 17 00:00:00 2001 From: Rhys Thomas <19435305+rhysgt@users.noreply.github.com> Date: Thu, 15 Oct 2026 16:21:02 +0100 Subject: [PATCH 2/4] fix: cost column in csv has different name New versions of PyVale use cost_zncc as column name --- defdap/file_readers.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/defdap/file_readers.py b/defdap/file_readers.py index d437ae0..2f64098 100644 --- a/defdap/file_readers.py +++ b/defdap/file_readers.py @@ -984,7 +984,13 @@ def load(self, file_name: pathlib.Path) -> None: """ if not file_name.is_file(): raise FileNotFoundError(f"Cannot open file {file_name}") - + + # Column can be called 'cost' or 'cost_zncc' + with open(str(file_name), 'r') as f: + header = f.readline() + if 'cost_zncc' in header: cost_key = 'cost_zncc' + elif 'cost' in header: cost_key = 'cost' + int_type = 'int32' double_type = 'double' data_format = np.dtype([ @@ -994,7 +1000,7 @@ def load(self, file_name: pathlib.Path) -> None: ('v', double_type), ('displacement_mag', double_type), ('converged', 'uint8'), - ('cost', double_type), + (cost_key, double_type), ('ftol', double_type), ('xtol', double_type), ('num_iterations', int_type), @@ -1045,7 +1051,7 @@ def load(self, file_name: pathlib.Path) -> None: self.loaded_data.coordinate = coord_dense self.loaded_data.displacement = disp_dense self.loaded_data.add( - 'cost', data['cost'].reshape(shape), + 'cost', data[cost_key].reshape(shape), unit='', type='map', order=0, plot_params={ 'plot_colour_bar': True, From 5e064dc2531ff67feeff09aa0f741b1a2dd87a1d Mon Sep 17 00:00:00 2001 From: Rhys Thomas <19435305+rhysgt@users.noreply.github.com> Date: Fri, 16 Oct 2026 10:54:12 +0100 Subject: [PATCH 3/4] fix: Relax pattern image size check in HRDIC Map The pattern image size check required an exact match with the DIC map shape times binning. DIC packages round the number of subwindows differently, so the image can legitimately differ by a pixel or so. The check now accepts a range of sizes depending on the map format (DaVis rounds up; PyVale and OpenPIV round down). The error message reports the expected range. --- defdap/hrdic.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/defdap/hrdic.py b/defdap/hrdic.py index 81b9230..5de3c85 100755 --- a/defdap/hrdic.py +++ b/defdap/hrdic.py @@ -534,12 +534,27 @@ def load_pattern(self): raise FileNotFoundError("First set path to pattern image.") img = imread(path) - exp_shape = tuple(v * binning for v in self.original_shape) - if img.shape != exp_shape: + + bounds = { + 'DaVis': (-1, 0), # Davis rounds up to nearest subwindow + 'PyVale': (0, 1), # PyVale rounds down to nearest subwindow + 'OpenPIV': (0, 1) # OpenPIV rounds down to nearest subwindow + } + lower, upper = bounds[self.format] + + img_shape = np.array(img.shape) + dic_shape = np.array(self.original_shape) + + min_shape = np.array((dic_shape + lower) * binning, dtype=int) + max_shape = np.array((dic_shape + upper) * binning, dtype=int) + + if not np.all((min_shape <= img_shape) & (img_shape <= max_shape)): raise ValueError( f'Incorrect size of pattern image. For binning of {binning} ' - f'expected size {exp_shape[::-1]} but got {img.shape[::-1]}' + f'expected size {min_shape[::-1]} to {max_shape[::-1]} ' + f'but got {img_shape[::-1]}' ) + return img def plot_grain_av_max_shear(self, **kwargs): From f2faa0d601ccdc404ee6671e15013c3a091ff8f6 Mon Sep 17 00:00:00 2001 From: Rhys Thomas <19435305+rhysgt@users.noreply.github.com> Date: Wed, 21 Oct 2026 16:35:13 +0100 Subject: [PATCH 4/4] fix: Revert change to file_reader No need to change the data_format. It always maps the 7th column to 'cost', regardless of header. --- defdap/file_readers.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/defdap/file_readers.py b/defdap/file_readers.py index 2f64098..1c3cecb 100644 --- a/defdap/file_readers.py +++ b/defdap/file_readers.py @@ -985,12 +985,6 @@ def load(self, file_name: pathlib.Path) -> None: if not file_name.is_file(): raise FileNotFoundError(f"Cannot open file {file_name}") - # Column can be called 'cost' or 'cost_zncc' - with open(str(file_name), 'r') as f: - header = f.readline() - if 'cost_zncc' in header: cost_key = 'cost_zncc' - elif 'cost' in header: cost_key = 'cost' - int_type = 'int32' double_type = 'double' data_format = np.dtype([ @@ -1000,7 +994,7 @@ def load(self, file_name: pathlib.Path) -> None: ('v', double_type), ('displacement_mag', double_type), ('converged', 'uint8'), - (cost_key, double_type), + ('cost', double_type), ('ftol', double_type), ('xtol', double_type), ('num_iterations', int_type), @@ -1051,7 +1045,7 @@ def load(self, file_name: pathlib.Path) -> None: self.loaded_data.coordinate = coord_dense self.loaded_data.displacement = disp_dense self.loaded_data.add( - 'cost', data[cost_key].reshape(shape), + 'cost', data['cost'].reshape(shape), unit='', type='map', order=0, plot_params={ 'plot_colour_bar': True,