Skip to content

Commit 55e6494

Browse files
authored
Merge pull request #558 from hpcflow/pyvale_dic
Add tasks for running DIC
2 parents 628286c + a1376d0 commit 55e6494

9 files changed

Lines changed: 344 additions & 0 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,4 @@ docs/source/reference/ts_*.html
139139
docs/source/reference/api.rst
140140

141141
problems/
142+
.DS_Store

matflow/data/scripts/pyvale/__init__.py

Whitespace-only changes.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from pathlib import Path
2+
import pyvale.dic as dic
3+
import numpy as np
4+
from PIL import Image
5+
6+
7+
def create_dic_roi_from_rect_boundary(
8+
reference_image_path: str, crop_dists: dict[str, int] | None = None
9+
):
10+
ref_image = np.array(Image.open(Path(reference_image_path)))
11+
12+
crop_dists_default = {
13+
"left": 0,
14+
"right": 0,
15+
"top": 0,
16+
"bottom": 0,
17+
}
18+
if crop_dists is not None:
19+
crop_dists_default.update(crop_dists)
20+
crop_dists = crop_dists_default
21+
22+
roi = dic.RegionOfInterest(ref_image=ref_image)
23+
roi.rect_boundary(**crop_dists)
24+
25+
return {"DIC_mask": roi.mask}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from pathlib import Path
2+
from natsort import natsorted
3+
import pyvale.dic as dic
4+
5+
6+
def extract_dic_result(pyvale_dic_result_files: list[Path]):
7+
dicdata = dic.import_2d(
8+
natsorted(pyvale_dic_result_files), binary=True, layout="matrix"
9+
)
10+
return {
11+
k: getattr(dicdata, k)
12+
for k in [
13+
"ss_x",
14+
"ss_y",
15+
"u",
16+
"v",
17+
"mag",
18+
"converged",
19+
"cost",
20+
"ftol",
21+
"xtol",
22+
"niter",
23+
]
24+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from pathlib import Path
2+
from natsort import natsorted
3+
import pyvale.dic as dic
4+
import numpy as np
5+
from PIL import Image
6+
7+
Image.MAX_IMAGE_PIXELS = None
8+
9+
10+
def run_dic(
11+
reference_image_path: str,
12+
deformed_image_path: str,
13+
DIC_mask: np.ndarray | None = None,
14+
DIC_seed: list[int] | None = None,
15+
**kwargs
16+
):
17+
reference_image_path = Path(reference_image_path)
18+
ref_image = np.array(Image.open(reference_image_path))
19+
20+
deformed_image_path = Path(deformed_image_path)
21+
deformed_image_paths = natsorted(
22+
deformed_image_path.parent.glob(deformed_image_path.name)
23+
)
24+
def_images = []
25+
for deformed_image_path in deformed_image_paths:
26+
if deformed_image_path == reference_image_path:
27+
continue
28+
def_images.append(np.array(Image.open(deformed_image_path)))
29+
def_images = np.array(def_images)
30+
31+
if DIC_seed is None:
32+
DIC_seed = [ref_image.shape[1] // 2, ref_image.shape[0] // 2]
33+
34+
if DIC_mask is None:
35+
roi = dic.RegionOfInterest(ref_image=ref_image)
36+
roi.rect_boundary(left=0, right=0, top=0, bottom=0)
37+
DIC_mask = roi.mask
38+
39+
dic_args = {k: v for k, v in kwargs.items() if v is not None}
40+
dic.calculate_2d(
41+
reference=ref_image,
42+
deformed=def_images,
43+
roi_mask=DIC_mask,
44+
seed=DIC_seed,
45+
output_at_end=True,
46+
output_binary=True,
47+
output_below_threshold=True,
48+
**dic_args
49+
)

matflow/data/template_components/command_files.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,8 @@
108108
name: (.*\.e)
109109
is_regex: true
110110
doc: Moose output file in the Exodus (NetCDF) format.
111+
112+
- label: pyvale_dic_result_files
113+
name:
114+
name: (.*\.dic2d)
115+
is_regex: true

matflow/data/template_components/parameters.yaml

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,145 @@
649649
description: The Gmsh-generated mesh, as a string.
650650
condition: { value.is_instance: [str] }
651651

652+
- type: reference_image_path
653+
_validation:
654+
- path: []
655+
doc:
656+
condition: { value.is_instance: [str, path] }
657+
- type: deformed_image_path
658+
_validation:
659+
- path: []
660+
doc:
661+
condition: { value.is_instance: [str, path] }
662+
- type: subset_size
663+
_validation:
664+
- path: []
665+
doc: "Size of the square subset window in pixels (default: 21)."
666+
condition: { value.is_instance: [int] }
667+
- type: subset_step
668+
_validation:
669+
- path: []
670+
doc: "Step size between subset centers in pixels (default: 10)."
671+
condition: { value.is_instance: [int] }
672+
- type: correlation_criteria
673+
_validation:
674+
- path: []
675+
doc: "Metric for matching subsets: `ZNSSD`, `NSSD` or `SSD` (default: `ZNSSD`)."
676+
condition:
677+
and:
678+
- value.is_instance: [str]
679+
- value.in: [ZNSSD, NSSD, SSD]
680+
- type: shape_function
681+
_validation:
682+
- path: []
683+
doc: "Deformation model: e.g., `AFFINE`, `RIGID` (default: `AFFINE`)."
684+
condition:
685+
and:
686+
- value.is_instance: [str]
687+
- value.in: [AFFINE, RIGID]
688+
- type: interpolation_routine
689+
_validation:
690+
- path: []
691+
doc: >
692+
Interpolation method used on image intensity. `BICUBIC` is currently the
693+
only supported option.
694+
condition:
695+
and:
696+
- value.is_instance: [str]
697+
- value.in: [BICUBIC]
698+
- type: max_iterations
699+
_validation:
700+
- path: []
701+
doc: "Maximum number of iterations allowed for subset optimization (default: 40)."
702+
condition: { value.is_instance: [int] }
703+
- type: precision
704+
_validation:
705+
- path: []
706+
doc: "Precision threshold for iterative optimization convergence (default: 0.001)."
707+
condition: { value.is_instance: [float] }
708+
- type: threshold
709+
_validation:
710+
- path: []
711+
doc: "Minimum correlation/cost coefficient value to be considered a matching subset (default: 0.9)."
712+
condition: { value.is_instance: [float] }
713+
- type: bf_threshold
714+
_validation:
715+
- path: []
716+
doc: >
717+
Correlation threshold used in rigid bruteforce check for a subset to be
718+
considered a good match(default: 0.6).
719+
condition: { value.is_instance: [float] }
720+
- type: max_displacement
721+
_validation:
722+
- path: []
723+
doc: "Estimate for the Maximum displacement in any direction (in pixels) (default: 128)."
724+
condition: { value.is_instance: [int] }
725+
- type: method
726+
_validation:
727+
- path: []
728+
doc: >
729+
Subset scanning method: `RG` for Reliability-Guided (best overall approach),
730+
`IMAGE_SCAN` for a standard scan across the image with no seeding (best
731+
performance with for subpixel displacements with high quality images), `FFT`
732+
for a multi-window FFT based approach (Good for large displacements)
733+
condition:
734+
and:
735+
- value.is_instance: [str]
736+
- value.in: [RG, IMAGE_SCAN, FFT]
737+
- type: fft_mad
738+
_validation:
739+
- path: []
740+
doc: >
741+
The option to smooth FFT windowing data by identifying and replacing outliers
742+
using a robust statistical method. For each subset, the function collects
743+
values from its neighboring subsets (within a 5x5 window, i.e., radius = 2),
744+
computes the median and Median Absolute Deviation (MAD), and determines whether
745+
the value at the current subset is an outlier. If it is, the value is replaced
746+
with the median of its neighbors. (default: False)
747+
condition: { value.is_instance: [bool] }
748+
- type: fft_mad_scale
749+
_validation:
750+
- path: []
751+
doc: >
752+
An outlier is defined as a value whose deviation from the local median exceeds
753+
fft_mad_scale times the MAD. This value choses the scaling factor that determines
754+
the threshold for detecting outliers relative to the MAD.
755+
condition: { value.is_instance: [bool] }
756+
- type: output_delimiter
757+
_validation:
758+
- path: []
759+
doc: "Delimiter used in text output files (default: `,`)."
760+
condition: { value.is_instance: [str] }
761+
- type: output_below_threshold
762+
_validation:
763+
- path: []
764+
doc: >
765+
If True, subset results with cost values that did not exceed the cost threshold
766+
will still be present in output (default: False).
767+
condition: { value.is_instance: [bool] }
768+
- type: dic_seed
769+
_validation:
770+
- path: []
771+
doc: >
772+
Coordinates `[x, y]` of the seed point for Reliability-Guided (RG) scanning,
773+
default is centre of image.
774+
condition:
775+
and:
776+
- value.is_instance: [list]
777+
- value.length.equal_to: 2
778+
- path: [{ "type": "list_value" }]
779+
condition: { value.is_instance: [int] }
780+
- type: crop_dists
781+
_validation:
782+
- path: []
783+
doc: Number of px to exclude from each edge of image (`left`, `right`, `top`, `bottom`).
784+
condition:
785+
and:
786+
- value.is_instance: [dict]
787+
- key.in: ["left", "right", "top", "bottom"]
788+
- path: [{ "type": "map_value" }]
789+
condition: { value.is_instance: [int] }
790+
652791
# Types only used to transfer values between tasks
653792
- type: damask_post_processing_result
654793
- type: damask_viz_result

matflow/data/template_components/task_schemas.yaml

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2787,3 +2787,80 @@
27872787
script_pass_workflow: true
27882788
environments:
27892789
any: python_env
2790+
2791+
- objective: create_DIC_ROI
2792+
implementation: pyvale
2793+
method: from_rect_boundary
2794+
inputs:
2795+
- parameter: reference_image_path
2796+
- parameter: crop_dists
2797+
default_value: null
2798+
outputs:
2799+
- parameter: DIC_mask
2800+
actions:
2801+
- script: <<script:pyvale/create_dic_roi_from_rect_boundary.py>>
2802+
script_data_in: direct
2803+
script_data_out: direct
2804+
script_exe: python_script
2805+
environments:
2806+
- scope:
2807+
type: any
2808+
environment: pyvale_env
2809+
2810+
- objective: run_DIC
2811+
doc: Run digital image correlation on two images
2812+
implementation: pyvale
2813+
inputs:
2814+
- parameter: reference_image_path
2815+
- parameter: deformed_image_path
2816+
- parameter: DIC_mask
2817+
default_value: null
2818+
- parameter: DIC_seed
2819+
default_value: null
2820+
- parameter: subset_size
2821+
default_value: null
2822+
- parameter: subset_step
2823+
default_value: null
2824+
- parameter: correlation_criteria
2825+
default_value: null
2826+
- parameter: shape_function
2827+
default_value: null
2828+
- parameter: interpolation_routine
2829+
default_value: null
2830+
- parameter: max_iterations
2831+
default_value: null
2832+
- parameter: precision
2833+
default_value: null
2834+
- parameter: threshold
2835+
default_value: null
2836+
- parameter: bf_threshold
2837+
default_value: null
2838+
- parameter: max_displacement
2839+
default_value: null
2840+
- parameter: method
2841+
default_value: null
2842+
- parameter: fft_mad
2843+
default_value: null
2844+
- parameter: fft_mad_scale
2845+
default_value: null
2846+
- parameter: output_delimiter
2847+
default_value: null
2848+
- parameter: output_below_threshold
2849+
default_value: null
2850+
- parameter: debug_level
2851+
default_value: 0
2852+
outputs:
2853+
- parameter: DIC_results
2854+
actions:
2855+
- script: <<script:pyvale/run_dic.py>>
2856+
script_data_in: direct
2857+
script_exe: python_script
2858+
environments:
2859+
- scope:
2860+
type: any
2861+
environment: pyvale_env
2862+
output_file_parsers:
2863+
DIC_results:
2864+
from_files: [pyvale_dic_result_files]
2865+
save_files: [pyvale_dic_result_files]
2866+
script: <<script:pyvale/extract_dic_result.py>>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
tasks:
2+
- schema: create_DIC_ROI_from_rect_boundary_pyvale
3+
inputs:
4+
reference_image_path: path/to/dic/images/0.bmp
5+
crop_dists:
6+
left: 100
7+
right: 100
8+
9+
- schema: run_DIC_pyvale
10+
inputs:
11+
deformed_image_path: path/to/dic/images/*.bmp
12+
max_displacement: 2000
13+
fft_mad: true
14+
fft_mad_scale: 5.0
15+
correlation_criteria: ZNSSD
16+
shape_function: AFFINE
17+
threshold: 0.8
18+
method: MULTIWINDOW
19+
sequences:
20+
- path: inputs.subset_size
21+
values:
22+
- 15
23+
- 31
24+
- 45

0 commit comments

Comments
 (0)