pyvale 2025.7.1__cp311-cp311-musllinux_1_2_aarch64.whl → 2025.8.1__cp311-cp311-musllinux_1_2_aarch64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pyvale might be problematic. Click here for more details.
- pyvale/__init__.py +12 -92
- pyvale/blender/__init__.py +23 -0
- pyvale/{pyvaleexceptions.py → blender/blenderexceptions.py} +0 -3
- pyvale/{blenderlightdata.py → blender/blenderlightdata.py} +3 -3
- pyvale/{blendermaterialdata.py → blender/blendermaterialdata.py} +1 -1
- pyvale/{blenderrenderdata.py → blender/blenderrenderdata.py} +5 -3
- pyvale/{blenderscene.py → blender/blenderscene.py} +33 -30
- pyvale/{blendertools.py → blender/blendertools.py} +14 -10
- pyvale/dataset/__init__.py +7 -0
- pyvale/dataset/dataset.py +443 -0
- pyvale/dic/__init__.py +20 -0
- pyvale/dic/cpp/dicfourier.cpp +36 -4
- pyvale/dic/cpp/dicinterpolator.cpp +56 -1
- pyvale/dic/cpp/dicmain.cpp +24 -19
- pyvale/dic/cpp/dicoptimizer.cpp +6 -1
- pyvale/dic/cpp/dicscanmethod.cpp +32 -32
- pyvale/dic/cpp/dicsignalhandler.cpp +16 -0
- pyvale/dic/cpp/dicstrain.cpp +7 -3
- pyvale/dic/cpp/dicutil.cpp +79 -23
- pyvale/{dic2d.py → dic/dic2d.py} +51 -29
- pyvale/dic/dic2dconv.py +6 -0
- pyvale/{dic2dcpp.cpython-311-aarch64-linux-musl.so → dic/dic2dcpp.cpython-311-aarch64-linux-musl.so} +0 -0
- pyvale/{dicchecks.py → dic/dicchecks.py} +28 -16
- pyvale/dic/dicdataimport.py +370 -0
- pyvale/{dicregionofinterest.py → dic/dicregionofinterest.py} +169 -12
- pyvale/{dicresults.py → dic/dicresults.py} +4 -1
- pyvale/{dicstrain.py → dic/dicstrain.py} +9 -9
- pyvale/examples/basics/{ex1_1_basicscalars_therm2d.py → ex1a_basicscalars_therm2d.py} +12 -9
- pyvale/examples/basics/{ex1_2_sensormodel_therm2d.py → ex1b_sensormodel_therm2d.py} +17 -14
- pyvale/examples/basics/{ex1_3_customsens_therm3d.py → ex1c_customsens_therm3d.py} +27 -24
- pyvale/examples/basics/{ex1_4_basicerrors_therm3d.py → ex1d_basicerrors_therm3d.py} +32 -29
- pyvale/examples/basics/{ex1_5_fielderrs_therm3d.py → ex1e_fielderrs_therm3d.py} +19 -15
- pyvale/examples/basics/{ex1_6_caliberrs_therm2d.py → ex1f_caliberrs_therm2d.py} +20 -16
- pyvale/examples/basics/{ex1_7_spatavg_therm2d.py → ex1g_spatavg_therm2d.py} +19 -16
- pyvale/examples/basics/{ex2_1_basicvectors_disp2d.py → ex2a_basicvectors_disp2d.py} +13 -10
- pyvale/examples/basics/{ex2_2_vectorsens_disp2d.py → ex2b_vectorsens_disp2d.py} +19 -15
- pyvale/examples/basics/{ex2_3_sensangle_disp2d.py → ex2c_sensangle_disp2d.py} +21 -18
- pyvale/examples/basics/{ex2_4_chainfielderrs_disp2d.py → ex2d_chainfielderrs_disp2d.py} +31 -29
- pyvale/examples/basics/{ex2_5_vectorfields3d_disp3d.py → ex2e_vectorfields3d_disp3d.py} +21 -18
- pyvale/examples/basics/{ex3_1_basictensors_strain2d.py → ex3a_basictensors_strain2d.py} +16 -14
- pyvale/examples/basics/{ex3_2_tensorsens2d_strain2d.py → ex3b_tensorsens2d_strain2d.py} +17 -14
- pyvale/examples/basics/{ex3_3_tensorsens3d_strain3d.py → ex3c_tensorsens3d_strain3d.py} +25 -22
- pyvale/examples/basics/{ex4_1_expsim2d_thermmech2d.py → ex4a_expsim2d_thermmech2d.py} +17 -14
- pyvale/examples/basics/{ex4_2_expsim3d_thermmech3d.py → ex4b_expsim3d_thermmech3d.py} +37 -34
- pyvale/examples/basics/ex5_nomesh.py +24 -0
- pyvale/examples/dic/ex1_2_blenderdeformed.py +174 -0
- pyvale/examples/dic/ex1_region_of_interest.py +6 -3
- pyvale/examples/dic/ex2_plate_with_hole.py +21 -18
- pyvale/examples/dic/ex3_plate_with_hole_strain.py +8 -6
- pyvale/examples/dic/ex4_dic_blender.py +17 -15
- pyvale/examples/dic/ex5_dic_challenge.py +19 -14
- pyvale/examples/genanalyticdata/ex1_1_scalarvisualisation.py +16 -10
- pyvale/examples/genanalyticdata/ex1_2_scalarcasebuild.py +3 -3
- pyvale/examples/genanalyticdata/ex2_1_analyticsensors.py +29 -23
- pyvale/examples/genanalyticdata/ex2_2_analyticsensors_nomesh.py +67 -0
- pyvale/examples/imagedef2d/ex_imagedef2d_todisk.py +12 -9
- pyvale/examples/mooseherder/ex0_create_moose_config.py +65 -0
- pyvale/examples/mooseherder/ex1a_modify_moose_input.py +71 -0
- pyvale/examples/mooseherder/ex1b_modify_gmsh_input.py +69 -0
- pyvale/examples/mooseherder/ex2a_run_moose_once.py +80 -0
- pyvale/examples/mooseherder/ex2b_run_gmsh_once.py +64 -0
- pyvale/examples/mooseherder/ex2c_run_both_once.py +114 -0
- pyvale/examples/mooseherder/ex3_run_moose_seq_para.py +157 -0
- pyvale/examples/mooseherder/ex4_run_gmsh-moose_seq_para.py +176 -0
- pyvale/examples/mooseherder/ex5_run_moose_paramulti.py +136 -0
- pyvale/examples/mooseherder/ex6_read_moose_exodus.py +163 -0
- pyvale/examples/mooseherder/ex7a_read_moose_herd_results.py +153 -0
- pyvale/examples/mooseherder/ex7b_read_multi_herd_results.py +116 -0
- pyvale/examples/mooseherder/ex7c_read_multi_gmshmoose_results.py +127 -0
- pyvale/examples/mooseherder/ex7d_readconfig_multi_gmshmoose_results.py +143 -0
- pyvale/examples/mooseherder/ex8_read_existing_sweep_output.py +72 -0
- pyvale/examples/renderblender/ex1_1_blenderscene.py +24 -20
- pyvale/examples/renderblender/ex1_2_blenderdeformed.py +22 -18
- pyvale/examples/renderblender/ex2_1_stereoscene.py +36 -29
- pyvale/examples/renderblender/ex2_2_stereodeformed.py +26 -20
- pyvale/examples/renderblender/ex3_1_blendercalibration.py +24 -17
- pyvale/examples/renderrasterisation/ex_rastenp.py +14 -12
- pyvale/examples/renderrasterisation/ex_rastercyth_oneframe.py +14 -15
- pyvale/examples/renderrasterisation/ex_rastercyth_static_cypara.py +13 -11
- pyvale/examples/renderrasterisation/ex_rastercyth_static_pypara.py +13 -11
- pyvale/mooseherder/__init__.py +32 -0
- pyvale/mooseherder/directorymanager.py +416 -0
- pyvale/mooseherder/exodusreader.py +763 -0
- pyvale/mooseherder/gmshrunner.py +163 -0
- pyvale/mooseherder/inputmodifier.py +236 -0
- pyvale/mooseherder/mooseconfig.py +226 -0
- pyvale/mooseherder/mooseherd.py +527 -0
- pyvale/mooseherder/mooserunner.py +303 -0
- pyvale/mooseherder/outputreader.py +22 -0
- pyvale/mooseherder/simdata.py +92 -0
- pyvale/mooseherder/simrunner.py +31 -0
- pyvale/mooseherder/sweepreader.py +356 -0
- pyvale/mooseherder/sweeptools.py +76 -0
- pyvale/sensorsim/__init__.py +82 -0
- pyvale/{camera.py → sensorsim/camera.py} +7 -7
- pyvale/{camerasensor.py → sensorsim/camerasensor.py} +7 -7
- pyvale/{camerastereo.py → sensorsim/camerastereo.py} +2 -2
- pyvale/{cameratools.py → sensorsim/cameratools.py} +4 -4
- pyvale/{cython → sensorsim/cython}/rastercyth.c +596 -596
- pyvale/sensorsim/cython/rastercyth.cpython-311-aarch64-linux-musl.so +0 -0
- pyvale/{cython → sensorsim/cython}/rastercyth.py +16 -17
- pyvale/{errorcalculator.py → sensorsim/errorcalculator.py} +1 -1
- pyvale/{errorintegrator.py → sensorsim/errorintegrator.py} +2 -2
- pyvale/{errorrand.py → sensorsim/errorrand.py} +4 -4
- pyvale/{errorsyscalib.py → sensorsim/errorsyscalib.py} +2 -2
- pyvale/{errorsysdep.py → sensorsim/errorsysdep.py} +2 -2
- pyvale/{errorsysfield.py → sensorsim/errorsysfield.py} +8 -8
- pyvale/{errorsysindep.py → sensorsim/errorsysindep.py} +3 -3
- pyvale/sensorsim/exceptions.py +8 -0
- pyvale/{experimentsimulator.py → sensorsim/experimentsimulator.py} +23 -3
- pyvale/{field.py → sensorsim/field.py} +1 -1
- pyvale/{fieldconverter.py → sensorsim/fieldconverter.py} +72 -19
- pyvale/sensorsim/fieldinterp.py +37 -0
- pyvale/sensorsim/fieldinterpmesh.py +124 -0
- pyvale/sensorsim/fieldinterppoints.py +55 -0
- pyvale/{fieldsampler.py → sensorsim/fieldsampler.py} +4 -4
- pyvale/{fieldscalar.py → sensorsim/fieldscalar.py} +28 -24
- pyvale/{fieldtensor.py → sensorsim/fieldtensor.py} +33 -31
- pyvale/{fieldvector.py → sensorsim/fieldvector.py} +33 -31
- pyvale/{imagedef2d.py → sensorsim/imagedef2d.py} +9 -5
- pyvale/{integratorfactory.py → sensorsim/integratorfactory.py} +6 -6
- pyvale/{integratorquadrature.py → sensorsim/integratorquadrature.py} +3 -3
- pyvale/{integratorrectangle.py → sensorsim/integratorrectangle.py} +3 -3
- pyvale/{integratorspatial.py → sensorsim/integratorspatial.py} +1 -1
- pyvale/{rastercy.py → sensorsim/rastercy.py} +5 -5
- pyvale/{rasternp.py → sensorsim/rasternp.py} +9 -9
- pyvale/{rasteropts.py → sensorsim/rasteropts.py} +1 -1
- pyvale/{renderer.py → sensorsim/renderer.py} +1 -1
- pyvale/{rendermesh.py → sensorsim/rendermesh.py} +5 -5
- pyvale/{renderscene.py → sensorsim/renderscene.py} +2 -2
- pyvale/{sensorarray.py → sensorsim/sensorarray.py} +1 -1
- pyvale/{sensorarrayfactory.py → sensorsim/sensorarrayfactory.py} +12 -12
- pyvale/{sensorarraypoint.py → sensorsim/sensorarraypoint.py} +10 -8
- pyvale/{sensordata.py → sensorsim/sensordata.py} +1 -1
- pyvale/{sensortools.py → sensorsim/sensortools.py} +2 -20
- pyvale/sensorsim/simtools.py +174 -0
- pyvale/{visualexpplotter.py → sensorsim/visualexpplotter.py} +3 -3
- pyvale/{visualimages.py → sensorsim/visualimages.py} +2 -2
- pyvale/{visualsimanimator.py → sensorsim/visualsimanimator.py} +4 -4
- pyvale/{visualsimplotter.py → sensorsim/visualsimplotter.py} +5 -5
- pyvale/{visualsimsensors.py → sensorsim/visualsimsensors.py} +12 -12
- pyvale/{visualtools.py → sensorsim/visualtools.py} +1 -1
- pyvale/{visualtraceplotter.py → sensorsim/visualtraceplotter.py} +2 -2
- pyvale/simcases/case17.geo +3 -0
- pyvale/simcases/case17.i +4 -4
- pyvale/simcases/run_1case.py +1 -9
- pyvale/simcases/run_all_cases.py +1 -1
- pyvale/simcases/run_build_case.py +1 -1
- pyvale/simcases/run_example_cases.py +1 -1
- pyvale/verif/__init__.py +12 -0
- pyvale/{analyticsimdatafactory.py → verif/analyticsimdatafactory.py} +2 -2
- pyvale/{analyticsimdatagenerator.py → verif/analyticsimdatagenerator.py} +2 -2
- pyvale/verif/psens.py +125 -0
- pyvale/verif/psensconst.py +18 -0
- pyvale/verif/psensmech.py +227 -0
- pyvale/verif/psensmultiphys.py +187 -0
- pyvale/verif/psensscalar.py +347 -0
- pyvale/verif/psenstensor.py +123 -0
- pyvale/verif/psensvector.py +116 -0
- {pyvale-2025.7.1.dist-info → pyvale-2025.8.1.dist-info}/METADATA +6 -7
- pyvale-2025.8.1.dist-info/RECORD +263 -0
- pyvale/cython/rastercyth.cpython-311-aarch64-linux-musl.so +0 -0
- pyvale/dataset.py +0 -415
- pyvale/dicdataimport.py +0 -247
- pyvale/simtools.py +0 -67
- pyvale-2025.7.1.dist-info/RECORD +0 -214
- /pyvale/{blendercalibrationdata.py → blender/blendercalibrationdata.py} +0 -0
- /pyvale/{dicspecklegenerator.py → dic/dicspecklegenerator.py} +0 -0
- /pyvale/{dicspecklequality.py → dic/dicspecklequality.py} +0 -0
- /pyvale/{dicstrainresults.py → dic/dicstrainresults.py} +0 -0
- /pyvale/{cameradata.py → sensorsim/cameradata.py} +0 -0
- /pyvale/{cameradata2d.py → sensorsim/cameradata2d.py} +0 -0
- /pyvale/{errordriftcalc.py → sensorsim/errordriftcalc.py} +0 -0
- /pyvale/{fieldtransform.py → sensorsim/fieldtransform.py} +0 -0
- /pyvale/{generatorsrandom.py → sensorsim/generatorsrandom.py} +0 -0
- /pyvale/{imagetools.py → sensorsim/imagetools.py} +0 -0
- /pyvale/{integratortype.py → sensorsim/integratortype.py} +0 -0
- /pyvale/{output.py → sensorsim/output.py} +0 -0
- /pyvale/{raster.py → sensorsim/raster.py} +0 -0
- /pyvale/{sensordescriptor.py → sensorsim/sensordescriptor.py} +0 -0
- /pyvale/{visualimagedef.py → sensorsim/visualimagedef.py} +0 -0
- /pyvale/{visualopts.py → sensorsim/visualopts.py} +0 -0
- /pyvale/{analyticmeshgen.py → verif/analyticmeshgen.py} +0 -0
- {pyvale-2025.7.1.dist-info → pyvale-2025.8.1.dist-info}/WHEEL +0 -0
- {pyvale-2025.7.1.dist-info → pyvale-2025.8.1.dist-info}/licenses/LICENSE +0 -0
- {pyvale-2025.7.1.dist-info → pyvale-2025.8.1.dist-info}/top_level.txt +0 -0
pyvale/{dic2d.py → dic/dic2d.py}
RENAMED
|
@@ -9,33 +9,38 @@
|
|
|
9
9
|
import numpy as np
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
|
|
12
|
-
#
|
|
13
|
-
import pyvale.dic2dcpp as dic2dcpp
|
|
14
|
-
import pyvale.dicchecks as dicchecks
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
12
|
+
# pyvale
|
|
13
|
+
import pyvale.dic.dic2dcpp as dic2dcpp
|
|
14
|
+
import pyvale.dic.dicchecks as dicchecks
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def two_dimensional(reference: np.ndarray | str | Path,
|
|
18
|
+
deformed: np.ndarray | str | Path,
|
|
19
|
+
roi_mask: np.ndarray,
|
|
20
|
+
seed: list[int] | list[np.int32] | np.ndarray,
|
|
21
|
+
subset_size: int = 21,
|
|
22
|
+
subset_step: int = 10,
|
|
23
|
+
correlation_criteria: str="ZNSSD",
|
|
24
|
+
shape_function: str="AFFINE",
|
|
25
|
+
interpolation_routine: str="BICUBIC",
|
|
26
|
+
max_iterations: int=40,
|
|
27
|
+
opt_precision: float=0.001,
|
|
28
|
+
opt_threshold: float=0.9,
|
|
29
|
+
bf_threshold: float=0.6,
|
|
30
|
+
num_threads: int | None = None,
|
|
31
|
+
max_displacement: int=128,
|
|
32
|
+
scanning_method: str="RG",
|
|
33
|
+
fft_mad: bool=False,
|
|
34
|
+
fft_mad_scale: float=3.0,
|
|
35
|
+
output_at_end: bool=False,
|
|
36
|
+
output_basepath: Path | str = "./",
|
|
37
|
+
output_binary: bool=False,
|
|
38
|
+
output_prefix: str="dic_results_",
|
|
39
|
+
output_delimiter: str=",",
|
|
40
|
+
output_unconverged: bool=False,
|
|
41
|
+
output_shape_params: bool=False,
|
|
42
|
+
debug_level: int=0) -> None:
|
|
43
|
+
|
|
39
44
|
"""
|
|
40
45
|
Perform 2D Digital Image Correlation (DIC) between a reference image and one or more deformed images.
|
|
41
46
|
|
|
@@ -51,7 +56,7 @@ def dic_2d(reference: np.ndarray | str | Path,
|
|
|
51
56
|
The deformed image(s) (3D array for multiple images) or path/pattern to image files.
|
|
52
57
|
roi_mask : np.ndarray
|
|
53
58
|
A binary mask indicating the Region of Interest (ROI) for analysis (same size as image).
|
|
54
|
-
seed : list
|
|
59
|
+
seed : list[int], list[np.int32] or np.ndarray
|
|
55
60
|
Coordinates `[x, y]` of the seed point for Reliability-Guided (RG) scanning, default is empty.
|
|
56
61
|
subset_size : int, optional
|
|
57
62
|
Size of the square subset window in pixels (default: 21).
|
|
@@ -70,6 +75,8 @@ def dic_2d(reference: np.ndarray | str | Path,
|
|
|
70
75
|
Precision threshold for iterative optimization convergence (default: 0.001).
|
|
71
76
|
opt_threshold : float, optional
|
|
72
77
|
Minimum correlation improvement threshold to continue iterations (default: 0.9).
|
|
78
|
+
num_threads : int, optional
|
|
79
|
+
Number of threads to use for parallel computation (default: None, uses all available).
|
|
73
80
|
bf_threshold : float, optional
|
|
74
81
|
Correlation threshold used in rigid bruteforce check for a subset to be considered a
|
|
75
82
|
good match(default: 0.6).
|
|
@@ -103,6 +110,12 @@ def dic_2d(reference: np.ndarray | str | Path,
|
|
|
103
110
|
changed to ".csv" or ".dic2d" depending on whether outputting as a binary.
|
|
104
111
|
output_delimiter : str, optional
|
|
105
112
|
Delimiter used in text output files (default: ",").
|
|
113
|
+
output_unconverged : bool, optional
|
|
114
|
+
If True, subset results as they were for the final iteration of the optimization
|
|
115
|
+
that did not converge will be saved (default: False).
|
|
116
|
+
output_shape_params : bool, optional
|
|
117
|
+
If True, all shape parameters will be saved in the output files (default: False).
|
|
118
|
+
debug_level:
|
|
106
119
|
|
|
107
120
|
Returns
|
|
108
121
|
-------
|
|
@@ -151,6 +164,7 @@ def dic_2d(reference: np.ndarray | str | Path,
|
|
|
151
164
|
config.filenames = filenames
|
|
152
165
|
config.fft_mad = fft_mad
|
|
153
166
|
config.fft_mad_scale = fft_mad_scale
|
|
167
|
+
config.debug_level = debug_level
|
|
154
168
|
|
|
155
169
|
# assigning c++ struct vals for save config
|
|
156
170
|
saveconf = dic2dcpp.SaveConfig()
|
|
@@ -159,6 +173,14 @@ def dic_2d(reference: np.ndarray | str | Path,
|
|
|
159
173
|
saveconf.prefix = output_prefix
|
|
160
174
|
saveconf.delimiter = output_delimiter
|
|
161
175
|
saveconf.at_end = output_at_end
|
|
176
|
+
saveconf.output_unconverged = output_unconverged
|
|
177
|
+
saveconf.shape_params = output_shape_params
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
#set the number of OMP threads
|
|
181
|
+
if num_threads is not None:
|
|
182
|
+
dic2dcpp.set_num_threads(num_threads)
|
|
162
183
|
|
|
163
184
|
# calling the c++ dic engine
|
|
164
|
-
dic2dcpp.
|
|
185
|
+
with dic2dcpp.ostream_redirect(stdout=True, stderr=True):
|
|
186
|
+
dic2dcpp.dic_engine(ref_arr, def_arr, roi_c, config, saveconf)
|
pyvale/dic/dic2dconv.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# ================================================================================
|
|
2
|
+
# pyvale: the python validation engine
|
|
3
|
+
# License: MIT
|
|
4
|
+
# Copyright (C) 2025 The Computer Aided Validation Team
|
|
5
|
+
# ================================================================================
|
|
6
|
+
|
pyvale/{dic2dcpp.cpython-311-aarch64-linux-musl.so → dic/dic2dcpp.cpython-311-aarch64-linux-musl.so}
RENAMED
|
Binary file
|
|
@@ -55,16 +55,18 @@ def check_output_directory(output_basepath: str,
|
|
|
55
55
|
|
|
56
56
|
if conflicting_files:
|
|
57
57
|
conflicting_files.sort()
|
|
58
|
-
print("The following output files already exist and may be overwritten:")
|
|
58
|
+
print("WARNING: The following output files already exist and may be overwritten:")
|
|
59
59
|
for f in conflicting_files:
|
|
60
60
|
print(f" - {os.path.join(output_basepath, f)}")
|
|
61
61
|
print("")
|
|
62
62
|
|
|
63
|
-
user_input = input("Do you want to continue? (y/n): ").strip().lower()
|
|
64
63
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
###### TURNING USER INPUT OFF FOR NOW ######
|
|
65
|
+
# user_input = input("Do you want to continue? (y/n): ").strip().lower()
|
|
66
|
+
|
|
67
|
+
# if user_input not in ("y", "yes", "Y", "YES"):
|
|
68
|
+
# print("Aborting to avoid overwriting data in output directory.")
|
|
69
|
+
# exit(0)
|
|
68
70
|
|
|
69
71
|
|
|
70
72
|
def check_correlation_criteria(correlation_criteria: str) -> None:
|
|
@@ -247,7 +249,7 @@ def check_subsets(subset_size: int, subset_step: int) -> None:
|
|
|
247
249
|
|
|
248
250
|
|
|
249
251
|
|
|
250
|
-
def check_and_update_rg_seed(seed: list[int], roi_mask: np.ndarray, scanning_method: str, px_hori: int, px_vert: int, subset_size: int, subset_step: int) -> list[int]:
|
|
252
|
+
def check_and_update_rg_seed(seed: list[int] | list[np.int32] | np.ndarray, roi_mask: np.ndarray, scanning_method: str, px_hori: int, px_vert: int, subset_size: int, subset_step: int) -> list[int]:
|
|
251
253
|
"""
|
|
252
254
|
Validate and update the region-growing seed location to align with image bounds and subset spacing.
|
|
253
255
|
|
|
@@ -260,7 +262,7 @@ def check_and_update_rg_seed(seed: list[int], roi_mask: np.ndarray, scanning_met
|
|
|
260
262
|
|
|
261
263
|
Parameters
|
|
262
264
|
----------
|
|
263
|
-
seed : list
|
|
265
|
+
seed : list[int], list[np.int32] or np.ndarray
|
|
264
266
|
The initial seed coordinates as a list of two integers: [x, y].
|
|
265
267
|
roi_mask : np.ndarray
|
|
266
268
|
A 2D binary mask (same size as the image) indicating the region of interest.
|
|
@@ -287,11 +289,19 @@ def check_and_update_rg_seed(seed: list[int], roi_mask: np.ndarray, scanning_met
|
|
|
287
289
|
if scanning_method != "RG":
|
|
288
290
|
return [0,0]
|
|
289
291
|
|
|
290
|
-
if
|
|
291
|
-
raise ValueError("Reliability Guided seed
|
|
292
|
+
if (len(seed) != 2):
|
|
293
|
+
raise ValueError(f"Reliability Guided seed does not have two elements: " \
|
|
294
|
+
f"seed={seed}. Seed " \
|
|
295
|
+
f" must be a list of two integers: seed=[x, y]")
|
|
296
|
+
|
|
297
|
+
if not isinstance(seed, (list, np.ndarray)) or not all(isinstance(coord, (int, np.int32)) for coord in seed):
|
|
298
|
+
raise ValueError("Reliability Guided seed must be a list of two integers: seed=[x, y]")
|
|
292
299
|
|
|
293
300
|
x, y = seed
|
|
294
301
|
|
|
302
|
+
if x < 0 or x >= px_hori or y < 0 or y >= px_vert:
|
|
303
|
+
raise ValueError(f"Seed ({x}, {y}) goes outside the image bounds: ({px_hori}, {px_vert})")
|
|
304
|
+
|
|
295
305
|
corner_x = x - subset_size//2
|
|
296
306
|
corner_y = y - subset_size//2
|
|
297
307
|
|
|
@@ -302,17 +312,11 @@ def check_and_update_rg_seed(seed: list[int], roi_mask: np.ndarray, scanning_met
|
|
|
302
312
|
new_x = round_to_step(corner_x, subset_step)
|
|
303
313
|
new_y = round_to_step(corner_y, subset_step)
|
|
304
314
|
|
|
305
|
-
|
|
306
|
-
# Clamp to image bounds
|
|
307
|
-
new_x = min(max(new_x, 0), px_hori - 1)
|
|
308
|
-
new_y = min(max(new_y, 0), px_vert - 1)
|
|
309
|
-
|
|
310
315
|
# check if all pixel values within the seed location are within the ROI
|
|
311
316
|
# seed coordinates are the central pixel to the subset
|
|
312
317
|
max_x = new_x + subset_size//2+1
|
|
313
318
|
max_y = new_y + subset_size//2+1
|
|
314
319
|
|
|
315
|
-
|
|
316
320
|
# Check if all pixel values in the ROI are valid
|
|
317
321
|
for i in range(corner_x, max_x):
|
|
318
322
|
for j in range(corner_y, max_y):
|
|
@@ -435,9 +439,17 @@ def check_and_get_images(reference: np.ndarray | str | Path,
|
|
|
435
439
|
ref_arr = reference
|
|
436
440
|
def_arr = deformed
|
|
437
441
|
|
|
438
|
-
|
|
442
|
+
# user might only pass a single deformed image. need to convert to 'stack'
|
|
443
|
+
if (reference.shape == deformed.shape):
|
|
444
|
+
def_arr = def_arr.reshape((1,def_arr.shape[0],def_arr.shape[1]))
|
|
445
|
+
|
|
446
|
+
elif (reference.shape != deformed[0].shape or reference.shape != roi.shape):
|
|
439
447
|
raise ValueError(f"Shape mismatch: reference {reference.shape}, "
|
|
440
448
|
f"deformed[0] {deformed[0].shape}, roi {roi.shape}")
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
# need to set some dummy filenames in the case that the user passes numpy arrays
|
|
452
|
+
filenames = [f"deformed image {i}" for i in range(def_arr.shape[0])]
|
|
441
453
|
|
|
442
454
|
# it might be the case that the roi has been manipulated prior to DIC run
|
|
443
455
|
# and therefore we need to to prevent the roi mask from being a 'view'
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
# ================================================================================
|
|
2
|
+
# pyvale: the python validation engine
|
|
3
|
+
# License: MIT
|
|
4
|
+
# Copyright (C) 2025 The Computer Aided Validation Team
|
|
5
|
+
# ================================================================================
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
import glob
|
|
11
|
+
import os
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
# Pyvale modules
|
|
15
|
+
from pyvale.dic.dicresults import Results
|
|
16
|
+
|
|
17
|
+
"""
|
|
18
|
+
Module responsible for handling importing of DIC results from completed
|
|
19
|
+
calculations.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def data_import(data: str | Path,
|
|
24
|
+
binary: bool = False,
|
|
25
|
+
layout: str = "matrix",
|
|
26
|
+
delimiter: str = ",") -> Results:
|
|
27
|
+
"""
|
|
28
|
+
Import DIC result data from human readable text or binary files.
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
|
|
33
|
+
data : str or pathlib.Path
|
|
34
|
+
Path pattern to the data files (can include wildcards). Default is "./".
|
|
35
|
+
|
|
36
|
+
layout : str, optional
|
|
37
|
+
Format of the output data layout: "column" (flat array per frame) or "matrix"
|
|
38
|
+
(reshaped grid per frame). Default is "column".
|
|
39
|
+
|
|
40
|
+
binary : bool, optional
|
|
41
|
+
If True, expects files in a specific binary format. If False, expects text data.
|
|
42
|
+
Default is False.
|
|
43
|
+
|
|
44
|
+
delimiter : str, optional
|
|
45
|
+
Delimiter used in text data files. Ignored if binary=True. Default is a single space.
|
|
46
|
+
|
|
47
|
+
Returns
|
|
48
|
+
-------
|
|
49
|
+
Results
|
|
50
|
+
A named container with the following fields:
|
|
51
|
+
- ss_x, ss_y (grid arrays if layout=="matrix"; otherwise, 1D integer arrays)
|
|
52
|
+
- u, v, m, converged, cost, ftol, xtol, niter (arrays with shape depending on layout)
|
|
53
|
+
- filenames (python list)
|
|
54
|
+
|
|
55
|
+
Raises
|
|
56
|
+
------
|
|
57
|
+
ValueError:
|
|
58
|
+
If `layout` is not "column" or "matrix", or text data has insufficient columns,
|
|
59
|
+
or binary rows are malformed.
|
|
60
|
+
import cython module
|
|
61
|
+
FileNotFoundError:
|
|
62
|
+
If no matching data files are found.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
print("")
|
|
67
|
+
print("Attempting DIC Data import...")
|
|
68
|
+
print("")
|
|
69
|
+
|
|
70
|
+
# convert to str
|
|
71
|
+
if isinstance(data, Path):
|
|
72
|
+
data = str(data)
|
|
73
|
+
|
|
74
|
+
files = sorted(glob.glob(data))
|
|
75
|
+
filenames = files
|
|
76
|
+
if not files:
|
|
77
|
+
raise FileNotFoundError(f"No results found in: {data}")
|
|
78
|
+
|
|
79
|
+
print(f"Found {len(files)} files containing DIC results:")
|
|
80
|
+
for file in files:
|
|
81
|
+
print(f" - {file}")
|
|
82
|
+
print("")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# Read first file to define reference coordinates
|
|
86
|
+
read_data = read_binary if binary else read_text
|
|
87
|
+
ss_x_ref, ss_y_ref, *fields = read_data(files[0], delimiter=delimiter)
|
|
88
|
+
frames = [list(fields)]
|
|
89
|
+
|
|
90
|
+
for file in files[1:]:
|
|
91
|
+
ss_x, ss_y, *f = read_data(file, delimiter)
|
|
92
|
+
if not (np.array_equal(ss_x_ref, ss_x) and np.array_equal(ss_y_ref, ss_y)):
|
|
93
|
+
raise ValueError("Mismatch in coordinates across frames.")
|
|
94
|
+
frames.append(f)
|
|
95
|
+
|
|
96
|
+
# Stack results (except ss_x and ss_y) into arrays
|
|
97
|
+
arrays = [np.stack([frame[i] for frame in frames]) for i in range(len(fields))]
|
|
98
|
+
|
|
99
|
+
if layout == "matrix":
|
|
100
|
+
|
|
101
|
+
# convert x and y data to meshgrid
|
|
102
|
+
x_unique = np.unique(ss_x_ref)
|
|
103
|
+
y_unique = np.unique(ss_y_ref)
|
|
104
|
+
X, Y = np.meshgrid(x_unique, y_unique)
|
|
105
|
+
shape = (len(files), len(y_unique), len(x_unique))
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
arrays = [to_grid(a,shape,ss_x_ref, ss_y_ref, x_unique,y_unique) for a in arrays]
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# sorting out shape function parameters if they are present in the files
|
|
112
|
+
current_shape = arrays[0].shape # (file,x,y)
|
|
113
|
+
shape_params = np.zeros(())
|
|
114
|
+
|
|
115
|
+
# rigid
|
|
116
|
+
if len(fields) == 10:
|
|
117
|
+
shape_params = np.zeros(current_shape+(2,))
|
|
118
|
+
shape_params[:,:,:,0] = arrays[8]
|
|
119
|
+
shape_params[:,:,:,1] = arrays[9]
|
|
120
|
+
if len(fields) == 14:
|
|
121
|
+
shape_params = np.zeros(current_shape+(6,))
|
|
122
|
+
shape_params[:,:,:,0] = arrays[8]
|
|
123
|
+
shape_params[:,:,:,1] = arrays[9]
|
|
124
|
+
shape_params[:,:,:,2] = arrays[10]
|
|
125
|
+
shape_params[:,:,:,3] = arrays[11]
|
|
126
|
+
shape_params[:,:,:,4] = arrays[12]
|
|
127
|
+
shape_params[:,:,:,5] = arrays[13]
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
return Results(X, Y, arrays[0], arrays[1], arrays[2], arrays[3],
|
|
133
|
+
arrays[4], arrays[5], arrays[6], arrays[7],
|
|
134
|
+
shape_params, filenames)
|
|
135
|
+
# column layout
|
|
136
|
+
else:
|
|
137
|
+
|
|
138
|
+
shape_params = np.zeros(())
|
|
139
|
+
current_shape = arrays[0].shape # (file,(x,y))
|
|
140
|
+
# rigid
|
|
141
|
+
if len(fields) == 10:
|
|
142
|
+
shape_params = np.zeros(current_shape+(2,))
|
|
143
|
+
shape_params[:,:,0] = arrays[8]
|
|
144
|
+
shape_params[:,:,1] = arrays[9]
|
|
145
|
+
if len(fields) == 14:
|
|
146
|
+
shape_params = np.zeros(current_shape+(6,))
|
|
147
|
+
shape_params[:,:,0] = arrays[8]
|
|
148
|
+
shape_params[:,:,1] = arrays[9]
|
|
149
|
+
shape_params[:,:,2] = arrays[10]
|
|
150
|
+
shape_params[:,:,3] = arrays[11]
|
|
151
|
+
shape_params[:,:,4] = arrays[12]
|
|
152
|
+
shape_params[:,:,5] = arrays[13]
|
|
153
|
+
|
|
154
|
+
return Results(ss_x_ref, ss_y_ref, arrays[0], arrays[1], arrays[2], arrays[3],
|
|
155
|
+
arrays[4], arrays[5], arrays[6], arrays[7],
|
|
156
|
+
shape_params, filenames)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def read_binary(file: str, delimiter: str):
|
|
163
|
+
"""
|
|
164
|
+
Read a binary DIC result file and extract DIC fields.
|
|
165
|
+
|
|
166
|
+
Assumes a fixed binary structure with each row containing:
|
|
167
|
+
- 2 × int32 (subset coordinates)
|
|
168
|
+
- 6 × float64 (u, v, match quality, cost, ftol, xtol)
|
|
169
|
+
- 1 × int32 (number of iterations)
|
|
170
|
+
- 1 × uint8 (convergence flag)
|
|
171
|
+
- 2 or 6 × float64 (shape parameters)
|
|
172
|
+
|
|
173
|
+
Parameters
|
|
174
|
+
----------
|
|
175
|
+
file : str
|
|
176
|
+
Path to the binary result file.
|
|
177
|
+
|
|
178
|
+
delimiter : str
|
|
179
|
+
Ignored for binary data (included for API consistency).
|
|
180
|
+
|
|
181
|
+
Returns
|
|
182
|
+
-------
|
|
183
|
+
tuple of np.ndarray
|
|
184
|
+
Arrays corresponding to:
|
|
185
|
+
(ss_x, ss_y, u, v, m, cost, ftol, xtol, niter)
|
|
186
|
+
|
|
187
|
+
Raises
|
|
188
|
+
------
|
|
189
|
+
ValueError
|
|
190
|
+
If the binary file size does not align with expected row size.
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
# row size can either be 3×4 + 6×8 + 1 = 61 bytes (without shape params)
|
|
194
|
+
# or 3×4 + 6×8 + 1 + 6×8 = 109 bytes (with shape params)
|
|
195
|
+
with open(file, "rb") as f:
|
|
196
|
+
raw = f.read()
|
|
197
|
+
|
|
198
|
+
has_shape_params = False
|
|
199
|
+
has_rigid_params = False
|
|
200
|
+
has_affine_params = False
|
|
201
|
+
|
|
202
|
+
row_size_basic = 3 * 4 + 6 * 8 + 1 # 61 bytes
|
|
203
|
+
row_size_with_rigid = row_size_basic + 2 * 8 # 77 bytes
|
|
204
|
+
row_size_with_affine = row_size_basic + 6 * 8 # 109 bytes
|
|
205
|
+
|
|
206
|
+
if len(raw) % row_size_basic == 0:
|
|
207
|
+
row_size = row_size_basic
|
|
208
|
+
has_shape_params = False
|
|
209
|
+
elif len(raw) % row_size_with_rigid == 0:
|
|
210
|
+
has_shape_params = True
|
|
211
|
+
row_size = row_size_with_rigid
|
|
212
|
+
has_rigid_params = True
|
|
213
|
+
has_affine_params = False
|
|
214
|
+
elif len(raw) % row_size_with_affine == 0:
|
|
215
|
+
has_shape_params = True
|
|
216
|
+
row_size = row_size_with_affine
|
|
217
|
+
has_affine_params = True
|
|
218
|
+
has_rigid_params = False
|
|
219
|
+
else:
|
|
220
|
+
raise ValueError(
|
|
221
|
+
f"Binary file has incomplete rows: {file}. "
|
|
222
|
+
f"Expected row size: 65 ((without shape params), "
|
|
223
|
+
f"81 (with rigid shape params) bytes, "
|
|
224
|
+
f"109 (with affine shape params). "
|
|
225
|
+
f"Actual size: {len(raw)} bytes."
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
rows = len(raw) // row_size
|
|
229
|
+
arr = np.frombuffer(raw, dtype=np.uint8).reshape(rows, row_size)
|
|
230
|
+
|
|
231
|
+
def extract(col, dtype, start):
|
|
232
|
+
return np.frombuffer(arr[:, start:start+col].copy(), dtype=dtype)
|
|
233
|
+
|
|
234
|
+
ss_x = extract(4, np.int32, 0)
|
|
235
|
+
ss_y = extract(4, np.int32, 4)
|
|
236
|
+
u = extract(8, np.float64, 8)
|
|
237
|
+
v = extract(8, np.float64, 16)
|
|
238
|
+
m = extract(8, np.float64, 24)
|
|
239
|
+
conv = extract(1, np.uint8, 32).astype(bool)
|
|
240
|
+
cost = extract(8, np.float64, 33)
|
|
241
|
+
ftol = extract(8, np.float64, 41)
|
|
242
|
+
xtol = extract(8, np.float64, 49)
|
|
243
|
+
niter = extract(4, np.int32, 57)
|
|
244
|
+
|
|
245
|
+
if has_shape_params:
|
|
246
|
+
if has_rigid_params:
|
|
247
|
+
p0 = extract(8, np.float64, 61)
|
|
248
|
+
p1 = extract(8, np.float64, 69)
|
|
249
|
+
return ss_x, ss_y, u, v, m, conv, cost, ftol, xtol, niter, p0,p1
|
|
250
|
+
if has_affine_params:
|
|
251
|
+
p0 = extract(8, np.float64, 61)
|
|
252
|
+
p1 = extract(8, np.float64, 69)
|
|
253
|
+
p2 = extract(8, np.float64, 77)
|
|
254
|
+
p3 = extract(8, np.float64, 85)
|
|
255
|
+
p4 = extract(8, np.float64, 93)
|
|
256
|
+
p5 = extract(8, np.float64, 101)
|
|
257
|
+
return ss_x, ss_y, u, v, m, conv, cost, ftol, xtol, niter, p0,p1,p2,p3,p4,p5
|
|
258
|
+
else:
|
|
259
|
+
return ss_x, ss_y, u, v, m, conv, cost, ftol, xtol, niter
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def read_text(file: str, delimiter: str):
|
|
265
|
+
"""
|
|
266
|
+
Read a human-readable text DIC result file and extract DIC fields.
|
|
267
|
+
|
|
268
|
+
Expects at least 9 columns:
|
|
269
|
+
[ss_x, ss_y, u, v, m, conv, cost, ftol, xtol, niter]
|
|
270
|
+
Could also include shape parameters if present.
|
|
271
|
+
|
|
272
|
+
Parameters
|
|
273
|
+
----------
|
|
274
|
+
file : str
|
|
275
|
+
Path to the text result file.
|
|
276
|
+
|
|
277
|
+
delimiter : str
|
|
278
|
+
Delimiter used in the text file (e.g., space, tab, comma).
|
|
279
|
+
|
|
280
|
+
Returns
|
|
281
|
+
-------
|
|
282
|
+
tuple of np.ndarray
|
|
283
|
+
Arrays corresponding to:
|
|
284
|
+
(ss_x, ss_y, u, v, m, conv, cost, ftol, xtol, niter, shape_params)
|
|
285
|
+
|
|
286
|
+
Raises
|
|
287
|
+
------
|
|
288
|
+
ValueError
|
|
289
|
+
If the text file has fewer than 9 columns.
|
|
290
|
+
"""
|
|
291
|
+
|
|
292
|
+
data = np.loadtxt(file, delimiter=delimiter, skiprows=1)
|
|
293
|
+
|
|
294
|
+
if data.shape[1] < 9:
|
|
295
|
+
raise ValueError("Text data must have at least 9 columns.")
|
|
296
|
+
|
|
297
|
+
if data.shape[1] == 10:
|
|
298
|
+
return (
|
|
299
|
+
data[:, 0].astype(np.int32), # ss_x
|
|
300
|
+
data[:, 1].astype(np.int32), # ss_y
|
|
301
|
+
data[:, 2], data[:, 3], data[:, 4], # u, v, mag
|
|
302
|
+
data[:, 5].astype(np.bool_), # convergence
|
|
303
|
+
data[:, 6], data[:, 7], data[:,8], # cost, ftol, xtol
|
|
304
|
+
data[:, 9].astype(np.int32) #niter
|
|
305
|
+
)
|
|
306
|
+
#rigid
|
|
307
|
+
elif data.shape[1]==12:
|
|
308
|
+
return (
|
|
309
|
+
data[:, 0].astype(np.int32), # ss_x
|
|
310
|
+
data[:, 1].astype(np.int32), # ss_y
|
|
311
|
+
data[:, 2], data[:, 3], data[:, 4], # u, v, mag
|
|
312
|
+
data[:, 5].astype(np.bool_), # convergence
|
|
313
|
+
data[:, 6], data[:, 7], data[:,8], # cost, ftol, xtol
|
|
314
|
+
data[:, 9].astype(np.int32), #niter
|
|
315
|
+
data[:,10], data[:,11] # shape params (rigid)
|
|
316
|
+
)
|
|
317
|
+
#affine
|
|
318
|
+
elif data.shape[1]==16:
|
|
319
|
+
return (
|
|
320
|
+
data[:, 0].astype(np.int32), # ss_x
|
|
321
|
+
data[:, 1].astype(np.int32), # ss_y
|
|
322
|
+
data[:, 2], data[:, 3], data[:, 4], # u, v, mag
|
|
323
|
+
data[:, 5].astype(np.bool_), # convergence
|
|
324
|
+
data[:, 6], data[:, 7], data[:,8], # cost, ftol, xtol
|
|
325
|
+
data[:, 9].astype(np.int32), #niter
|
|
326
|
+
data[:,10], data[:,11], data[:,12], data[:,13], data[:,14], data[:,15] # shape params (affine)
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def to_grid(data, shape, ss_x_ref, ss_y_ref, x_unique, y_unique):
|
|
332
|
+
"""
|
|
333
|
+
Reshape a 2D DIC field from flat (column) format into grid (matrix) format.
|
|
334
|
+
|
|
335
|
+
This is used when output layout is specified as "matrix".
|
|
336
|
+
Maps values using reference subset coordinates (ss_x_ref, ss_y_ref).
|
|
337
|
+
|
|
338
|
+
Parameters
|
|
339
|
+
ol
|
|
340
|
+
----------
|
|
341
|
+
data : np.ndarray
|
|
342
|
+
Array of shape (n_frames, n_points) to be reshaped into (n_frames, height, width).
|
|
343
|
+
|
|
344
|
+
shape : tuple
|
|
345
|
+
Target shape of output array: (n_frames, height, width).
|
|
346
|
+
|
|
347
|
+
ss_x_ref : np.ndarray
|
|
348
|
+
X coordinates of subset centers.
|
|
349
|
+
|
|
350
|
+
ss_y_ref : np.ndarray
|
|
351
|
+
Y coordinates of subset centers.
|
|
352
|
+
|
|
353
|
+
x_unique : np.ndarray
|
|
354
|
+
Sorted unique X coordinates in the grid.
|
|
355
|
+
|
|
356
|
+
y_unique : np.ndarray
|
|
357
|
+
Sorted unique Y coordinates in the grid.
|
|
358
|
+
|
|
359
|
+
Returns
|
|
360
|
+
-------
|
|
361
|
+
np.ndarray
|
|
362
|
+
Reshaped array with shape `shape`, filled with NaNs where no data exists.
|
|
363
|
+
"""
|
|
364
|
+
|
|
365
|
+
grid = np.full(shape, np.nan)
|
|
366
|
+
for i, (x, y) in enumerate(zip(ss_x_ref, ss_y_ref)):
|
|
367
|
+
x_idx = np.where(x_unique == x)[0][0]
|
|
368
|
+
y_idx = np.where(y_unique == y)[0][0]
|
|
369
|
+
grid[:, y_idx, x_idx] = data[:, i]
|
|
370
|
+
return grid
|