diff --git a/Code_Review/vv/WritePoleFigure/.gitignore b/Code_Review/vv/WritePoleFigure/.gitignore new file mode 100644 index 0000000000..f2ea6ef96a --- /dev/null +++ b/Code_Review/vv/WritePoleFigure/.gitignore @@ -0,0 +1,5 @@ +# Binary V&V evidence is archived to OneDrive, not committed to the repo. +# The scripts + pipelines here regenerate all of it; see README.md. +*.dream3d +*.pdf +*.png diff --git a/Code_Review/vv/WritePoleFigure/README.md b/Code_Review/vv/WritePoleFigure/README.md new file mode 100644 index 0000000000..8d9c2dcb01 --- /dev/null +++ b/Code_Review/vv/WritePoleFigure/README.md @@ -0,0 +1,79 @@ +# WritePoleFigure — V&V A/B evidence (regenerated 2026-07-08) + +Class-5 (expert-visual) A/B comparison for the `nx::core::WritePoleFigureFilter` (#1647). This folder +is a **full fresh regeneration** — the original working folder's legacy-input minting script and legacy +renders were unrecoverable (see `../../../vv_work/write_pole_figure/RECOVERY_STATUS.md`), so the entire +set was rebuilt here from first principles and re-run through all three engines. + +> **What is committed here:** only the text artifacts — the three generator scripts, the four pipeline +> files, and this README. The **binary evidence** (input `.dream3d` files and the rendered `.pdf`/`.png` +> pole figures) is **archived to OneDrive**, not committed to the repo (see `.gitignore`). Everything is +> reproducible from the committed scripts via the steps below. + +## What is compared + +The same 502 orientations are rendered by three engines, in two modes, for two Laue groups: + +| Engine | Runner | Output | +|--------|--------|--------| +| **DREAM3D 6.5.171** | `~/Applications/DREAM3D.app` PipelineRunner | PDF | +| **DREAM3D 6.5.172** | `Workspace3/6.5.172` PipelineRunner | PDF | +| **DREAM3D-NX** | `nxrunner` built from `topic/write_pole_figure_fixes` (#1647) | PNG | + +- **Modes:** Color (`GenerationAlgorithm 0`) and Discrete (`GenerationAlgorithm 1`). +- **Laue groups:** hex (Ti, `CrystalStructures=[999,0]`) and cubic (`[999,1]`, same Eulers relabeled). + +## Layout + +``` +input/ + pf_input_hex.dream3d NX-format input, hex (502 orientations) + pf_input_cubic.dream3d NX-format input, cubic + pf_input_legacy.dream3d SIMPL v7.0 input, hex (minted by build_legacy_input.py) + pf_input_cubic_legacy.dream3d SIMPL v7.0 input, cubic +out_nx/ nx_color_1.png, nx_discrete_1.png (NX, hex) +out_cubic_nx/ nx_color_1.png, nx_discrete_1.png (NX, cubic) +out_legacy_6_5_171/ color_Phase_1.pdf, discrete_Phase_1.pdf (6.5.171, hex) +out_legacy_6_5_172/ color_Phase_1.pdf, discrete_Phase_1.pdf (6.5.172, hex) +out_cubic_legacy_6_5_171/ color_Phase_1.pdf, discrete_Phase_1.pdf (6.5.171, cubic) +out_cubic_legacy_6_5_172/ color_Phase_1.pdf, discrete_Phase_1.pdf (6.5.172, cubic) +``` + +## Reproduce + +```bash +# 1. NX inputs are committed. Mint the legacy inputs from them: +/usr/bin/python3 build_legacy_input.py hex input/pf_input_legacy.dream3d +/usr/bin/python3 build_legacy_input.py cubic input/pf_input_cubic_legacy.dream3d + +# 2. NX renders (nxrunner built from #1647): +/usr/bin/python3 gen_nx_pf.py hex && nxrunner --execute pf_nx_hex.d3dpipeline +/usr/bin/python3 gen_nx_pf.py cubic && nxrunner --execute pf_nx_cubic.d3dpipeline + +# 3. Legacy renders (repeat for 172 runner and cubic variant): +/usr/bin/python3 gen_legacy_pf.py hex "$PWD/out_legacy_6_5_171" "$PWD/pf_legacy_171_hex.json" +"$DREAM3D_651/PipelineRunner" -p pf_legacy_171_hex.json +``` + +Scripts (all committed here): `build_legacy_input.py`, `gen_nx_pf.py`, `gen_legacy_pf.py`. + +## Results + +- **6.5.171 vs 6.5.172 are the same picture.** hex-color, hex-discrete, and cubic-discrete PDFs are + **byte-identical**; cubic-color differs only in a trailing PDF metadata block (identical rendering). + This confirms no pole-figure regression across the two legacy versions. +- **NX vs legacy** reproduces the expected pole-figure topology (pole positions, symmetry, color + mapping) in both color and discrete modes, for both Laue groups. NX renders to PNG rather than PDF; + compare visually. +- **Discrete mode** exercises the new `discrete_marker_radius` parameter fixed in #1647 (the develop + `nxrunner` lacked it and warned `-5433`; the #1647 build honors it). NX discrete PNGs were produced by + the branch-accurate build. + +## Caveat (honesty note) + +The legacy input was **reconstructed**, not recovered: `build_legacy_input.py` was rewritten from the +array contract in `gen_legacy_pf.py`'s reader proxy plus the SIMPL v7.0 HDF5 layout cloned from a +known-good legacy file. The 502 orientations themselves are the originals (extracted from the committed +`pf_input_*.dream3d`). Both legacy engines read the minted file without error and produced valid pole +figures, which validates the reconstruction. The trailing space in the phase name (`'Nickel '` in the +template; here `'Primary'`) matches the legacy convention noted in deviation D1. diff --git a/Code_Review/vv/WritePoleFigure/build_legacy_input.py b/Code_Review/vv/WritePoleFigure/build_legacy_input.py new file mode 100644 index 0000000000..c77fd0805d --- /dev/null +++ b/Code_Review/vv/WritePoleFigure/build_legacy_input.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +# Reconstructed 2026-07-08. The original build_legacy_input.py (which minted the legacy-format +# pf_input_legacy.dream3d for the WritePoleFigure A/B) was lost with no .pyc, so it is rebuilt here +# from (a) the array contract in gen_legacy_pf.py's reader() and (b) the exact legacy (SIMPL v7.0) +# HDF5 layout cloned from a known-good legacy file (6_6_stats_test_v2.dream3d). +# +# Extracts the 502 orientations from the NX Pole_Figure_Exemplars_v6 input and writes a +# DREAM3D 6.5.x-format .dream3d (ImageDataContainer, 502x1x1 ImageGeometry) that the 6.5.171 / +# 6.5.172 DataContainerReader can read. +# +# Usage: build_legacy_input.py +import sys +import h5py +import numpy as np + +BASE = '/Users/mjackson/Workspace3/simplnx/Code_Review/vv/WritePoleFigure' +DC = 'ImageDataContainer' +ASCII = h5py.string_dtype(encoding='ascii') + + +def legacy_arr(group, name, data, object_type, comp, ntuples): + # Legacy cell/ensemble array: dataset stored ZYX-major as (z, y, x, comp) flattened here to + # (ntuples, comp); attrs carry x-major TupleDimensions + the SIMPL metadata. + ds = group.create_dataset(name, data=data) + ds.attrs.create('ComponentDimensions', np.array([comp], dtype=np.uint64)) + ds.attrs.create('DataArrayVersion', np.array([2], dtype=np.int32)) + ds.attrs['ObjectType'] = np.bytes_(object_type) + ds.attrs['Tuple Axis Dimensions'] = np.bytes_(f'x={ntuples}') + ds.attrs.create('TupleDimensions', np.array([ntuples], dtype=np.uint64)) + + +def main(): + variant = sys.argv[1] + out = sys.argv[2] + src = f'{BASE}/input/pf_input_{variant}.dream3d' + + with h5py.File(src, 'r') as s: + eulers = s['DataStructure/Imported Data/Eulers'][:].reshape(-1, 3).astype(np.float32) + phases = s['DataStructure/Imported Data/Phases'][:].reshape(-1, 1).astype(np.int32) + mask = s['DataStructure/Imported Data/Mask'][:].reshape(-1, 1).astype(np.uint8) + xtal = s['DataStructure/EnsembleAttributeMatrix/CrystalStructures'][:].reshape(-1, 1).astype(np.uint32) + n = eulers.shape[0] + nens = xtal.shape[0] + + with h5py.File(out, 'w') as f: + f.attrs['FileVersion'] = np.bytes_('7.0') + f.attrs['DREAM3D Version'] = np.bytes_('6.5.171') + for empty in ('DataContainerBundles', 'Montages', 'Pipeline'): + f.create_group(empty) + dcs = f.create_group('DataContainers') + dc = dcs.create_group(DC) + + geom = dc.create_group('_SIMPL_GEOMETRY') + geom.attrs['GeometryName'] = np.bytes_('ImageGeometry') + geom.attrs.create('GeometryType', np.array([0], dtype=np.uint32)) + geom.attrs['GeometryTypeName'] = np.bytes_('ImageGeometry') + geom.attrs.create('SpatialDimensionality', np.array([3], dtype=np.uint32)) + geom.attrs.create('UnitDimensionality', np.array([3], dtype=np.uint32)) + geom.create_dataset('DIMENSIONS', data=np.array([n, 1, 1], dtype=np.int64)) + geom.create_dataset('ORIGIN', data=np.array([0, 0, 0], dtype=np.float32)) + geom.create_dataset('SPACING', data=np.array([1, 1, 1], dtype=np.float32)) + + cell = dc.create_group('CellData') + cell.attrs.create('AttributeMatrixType', np.array([3], dtype=np.uint32)) + cell.attrs.create('TupleDimensions', np.array([n, 1, 1], dtype=np.uint64)) + legacy_arr(cell, 'Eulers', eulers, 'DataArray', 3, n) + legacy_arr(cell, 'Phases', phases, 'DataArray', 1, n) + legacy_arr(cell, 'Mask', mask, 'DataArray', 1, n) + + ens = dc.create_group('CellEnsembleData') + ens.attrs.create('AttributeMatrixType', np.array([11], dtype=np.uint32)) + ens.attrs.create('TupleDimensions', np.array([nens], dtype=np.uint64)) + legacy_arr(ens, 'CrystalStructures', xtal, 'DataArray', 1, nens) + names = np.array(['Invalid Phase'] + ['Primary'] * (nens - 1), dtype=object) + mn = ens.create_dataset('MaterialName', shape=(nens,), dtype=ASCII, data=names) + mn.attrs.create('ComponentDimensions', np.array([1], dtype=np.uint64)) + mn.attrs.create('DataArrayVersion', np.array([2], dtype=np.int32)) + mn.attrs['ObjectType'] = np.bytes_('StringDataArray') + mn.attrs['Tuple Axis Dimensions'] = np.bytes_(f'x={nens}') + mn.attrs.create('TupleDimensions', np.array([nens], dtype=np.uint64)) + + print(f'wrote {out} ({n} orientations, {nens} ensembles, variant={variant})') + + +if __name__ == '__main__': + main() diff --git a/Code_Review/vv/WritePoleFigure/gen_legacy_pf.py b/Code_Review/vv/WritePoleFigure/gen_legacy_pf.py new file mode 100644 index 0000000000..b7bca739e3 --- /dev/null +++ b/Code_Review/vv/WritePoleFigure/gen_legacy_pf.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +# Regenerated 2026-07-08 for Code_Review/vv/WritePoleFigure. Legacy (SIMPL 6.5.x) side of the A/B. +# Reconstructed from the decompiled gen_legacy_pf.cpython-312.pyc; paths retargeted to this folder +# and parameterized for hex/cubic. Builds a SIMPL pipeline (DataContainerReader + legacy +# WritePoleFigure x2: color + discrete) that the DREAM3D 6.5.171 / 6.5.172 PipelineRunner executes. +import json +import sys + +BASE = '/Users/mjackson/Workspace3/simplnx/Code_Review/vv/WritePoleFigure' +DC = 'ImageDataContainer' + + +def da(name, path, obj_type, comps): + return { + 'Component Dimensions': [comps], + 'Flag': 2, + 'Name': name, + 'Object Type': obj_type, + 'Path': path, + 'Tuple Dimensions': [1], + 'Version': 2, + } + + +def reader(in_file): + cellpath = f'/DataContainers/{DC}/CellData' + enspath = f'/DataContainers/{DC}/CellEnsembleData' + proxy = { + 'Data Containers': [ + { + 'Attribute Matricies': [ + { + 'Data Arrays': [ + da('Eulers', cellpath, 'DataArray', 3), + da('Phases', cellpath, 'DataArray', 1), + da('Mask', cellpath, 'DataArray', 1), + ], + 'Flag': 2, + 'Name': 'CellData', + 'Type': 3, + }, + { + 'Data Arrays': [ + da('CrystalStructures', enspath, 'DataArray', 1), + da('MaterialName', enspath, 'StringDataArray', 1), + ], + 'Flag': 2, + 'Name': 'CellEnsembleData', + 'Type': 11, + }, + ], + 'Flag': 2, + 'Geometry': {'Geometry_Type': 0, 'Geometry_Type_Name': 'ImageGeometry'}, + 'Name': DC, + } + ], + 'Version': 6, + } + return { + 'FilterVersion': '6.5.171', + 'Filter_Enabled': True, + 'Filter_Human_Label': 'Read DREAM.3D Data File', + 'Filter_Name': 'DataContainerReader', + 'Filter_Uuid': '{043cbde5-3878-5718-958f-ae75714df0df}', + 'InputFile': in_file, + 'OverwriteExistingDataContainers': False, + 'InputFileDataContainerArrayProxy': proxy, + } + + +def dap(am, name): + return {'Data Container Name': DC, 'Attribute Matrix Name': am, 'Data Array Name': name} + + +def write_pf(out_dir, prefix, gen_algo, use_mask): + return { + 'FilterVersion': '6.5.171', + 'Filter_Enabled': True, + 'Filter_Human_Label': 'Export Pole Figure Images', + 'Filter_Name': 'WritePoleFigure', + 'Filter_Uuid': '{a10bb78e-fcff-553d-97d6-830a43c85385}', + 'CellEulerAnglesArrayPath': dap('CellData', 'Eulers'), + 'CellPhasesArrayPath': dap('CellData', 'Phases'), + 'CrystalStructuresArrayPath': dap('CellEnsembleData', 'CrystalStructures'), + 'MaterialNameArrayPath': dap('CellEnsembleData', 'MaterialName'), + 'GoodVoxelsArrayPath': dap('CellData', 'Mask'), + 'UseGoodVoxels': 1 if use_mask else 0, + 'GenerationAlgorithm': gen_algo, + 'ImageLayout': 0, + 'ImageSize': 512, + 'LambertSize': 64, + 'NumColors': 32, + 'OutputPath': out_dir, + 'ImagePrefix': prefix, + 'Title': 'WritePoleFigure AB', + } + + +if __name__ == '__main__': + variant = sys.argv[1] if len(sys.argv) > 1 else 'hex' # 'hex' or 'cubic' + out_dir = sys.argv[2] + out_json = sys.argv[3] + in_file = f'{BASE}/input/pf_input_legacy.dream3d' if variant == 'hex' else f'{BASE}/input/pf_input_cubic_legacy.dream3d' + pipe = { + '0': reader(in_file), + '1': write_pf(out_dir, 'color_', 0, False), + '2': write_pf(out_dir, 'discrete_', 1, False), + 'PipelineBuilder': { + 'Name': 'WritePoleFigure legacy AB', + 'Number_Filters': 3, + 'Version': 6, + }, + } + with open(out_json, 'w') as fh: + json.dump(pipe, fh, indent=2) + print('wrote', out_json, '-> output dir', out_dir) diff --git a/Code_Review/vv/WritePoleFigure/gen_nx_pf.py b/Code_Review/vv/WritePoleFigure/gen_nx_pf.py new file mode 100644 index 0000000000..08e5c11b50 --- /dev/null +++ b/Code_Review/vv/WritePoleFigure/gen_nx_pf.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +# Regenerated 2026-07-08 for Code_Review/vv/WritePoleFigure. NX side of the WritePoleFigure A/B. +# Reconstructed from the decompiled gen_nx_pf.cpython-312.pyc; paths retargeted to this folder and +# parameterized for the hex and cubic inputs. Builds a DREAM3D-NX pipeline (ReadDREAM3DFilter + +# WritePoleFigure x2: Color + Discrete) and writes it next to this script. +import json +import sys + +BASE = '/Users/mjackson/Workspace3/simplnx/Code_Review/vv/WritePoleFigure' + + +def a(v): + return {'value': v, 'version': 1} + + +def wpf(prefix, gen_algo, geom_name, outdir): + return { + 'args': { + 'title': a('WritePoleFigure AB'), + 'generation_algorithm_index': a(gen_algo), + 'lambert_size': a(64), + 'num_colors': a(32), + 'discrete_marker_radius': a(3), + 'image_layout_index': a(0), + 'image_size': a(512), + 'output_path': a(outdir), + 'image_prefix': a(prefix), + 'use_mask': a(False), + 'cell_euler_angles_array_path': a('Imported Data/Eulers'), + 'cell_phases_array_path': a('Imported Data/Phases'), + 'mask_array_path': a('Imported Data/Mask'), + 'crystal_structures_array_path': a('EnsembleAttributeMatrix/CrystalStructures'), + 'material_name_array_path': a('EnsembleAttributeMatrix/PhaseNames'), + 'save_as_image_geometry': a(True), + 'write_image_to_disk': a(True), + 'output_image_geometry_path': a(geom_name), + 'save_intensity_plots': a(False), + 'normalize_to_mrd': a(True), + 'intensity_geometry_path': a(geom_name + ' Intensity'), + 'intensity_plot_1_name': a('<0001>'), + 'intensity_plot_2_name': a('<11-20>'), + 'intensity_plot_3_name': a('<10-10>'), + 'hex_convention_index': a(0), + }, + 'comments': '', + 'filter': {'name': 'nx::core::WritePoleFigureFilter', 'uuid': '00cbb97e-a5c2-43e6-9a35-17a0f9ce26ed'}, + 'isDisabled': False, + } + + +def pipeline(in_file, outdir): + return { + 'name': 'WritePoleFigure NX AB', + 'pipeline': [ + { + 'args': {'import_data_object': a({'file_path': in_file, 'data_paths': None})}, + 'comments': '', + 'filter': {'name': 'nx::core::ReadDREAM3DFilter', 'uuid': '0dbd31c7-19e0-4077-83ef-f4a6459a0e2d'}, + 'isDisabled': False, + }, + wpf('nx_color_', 0, 'PoleFigure_Color', outdir), + wpf('nx_discrete_', 1, 'PoleFigure_Discrete', outdir), + ], + 'version': 1, + } + + +if __name__ == '__main__': + variant = sys.argv[1] if len(sys.argv) > 1 else 'hex' # 'hex' or 'cubic' + in_file = f'{BASE}/input/pf_input_{variant}.dream3d' + outdir = f'{BASE}/out_nx' if variant == 'hex' else f'{BASE}/out_cubic_nx' + out_pipe = f'{BASE}/pf_nx_{variant}.d3dpipeline' + with open(out_pipe, 'w') as fh: + json.dump(pipeline(in_file, outdir), fh, indent=2) + print('wrote', out_pipe, '-> output dir', outdir) diff --git a/Code_Review/vv/WritePoleFigure/pf_legacy_171_cubic.json b/Code_Review/vv/WritePoleFigure/pf_legacy_171_cubic.json new file mode 100644 index 0000000000..80429ae08e --- /dev/null +++ b/Code_Review/vv/WritePoleFigure/pf_legacy_171_cubic.json @@ -0,0 +1,192 @@ +{ + "0": { + "FilterVersion": "6.5.171", + "Filter_Enabled": true, + "Filter_Human_Label": "Read DREAM.3D Data File", + "Filter_Name": "DataContainerReader", + "Filter_Uuid": "{043cbde5-3878-5718-958f-ae75714df0df}", + "InputFile": "/Users/mjackson/Workspace3/simplnx/Code_Review/vv/WritePoleFigure/input/pf_input_cubic_legacy.dream3d", + "OverwriteExistingDataContainers": false, + "InputFileDataContainerArrayProxy": { + "Data Containers": [ + { + "Attribute Matricies": [ + { + "Data Arrays": [ + { + "Component Dimensions": [ + 3 + ], + "Flag": 2, + "Name": "Eulers", + "Object Type": "DataArray", + "Path": "/DataContainers/ImageDataContainer/CellData", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + }, + { + "Component Dimensions": [ + 1 + ], + "Flag": 2, + "Name": "Phases", + "Object Type": "DataArray", + "Path": "/DataContainers/ImageDataContainer/CellData", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + }, + { + "Component Dimensions": [ + 1 + ], + "Flag": 2, + "Name": "Mask", + "Object Type": "DataArray", + "Path": "/DataContainers/ImageDataContainer/CellData", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + } + ], + "Flag": 2, + "Name": "CellData", + "Type": 3 + }, + { + "Data Arrays": [ + { + "Component Dimensions": [ + 1 + ], + "Flag": 2, + "Name": "CrystalStructures", + "Object Type": "DataArray", + "Path": "/DataContainers/ImageDataContainer/CellEnsembleData", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + }, + { + "Component Dimensions": [ + 1 + ], + "Flag": 2, + "Name": "MaterialName", + "Object Type": "StringDataArray", + "Path": "/DataContainers/ImageDataContainer/CellEnsembleData", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + } + ], + "Flag": 2, + "Name": "CellEnsembleData", + "Type": 11 + } + ], + "Flag": 2, + "Geometry": { + "Geometry_Type": 0, + "Geometry_Type_Name": "ImageGeometry" + }, + "Name": "ImageDataContainer" + } + ], + "Version": 6 + } + }, + "1": { + "FilterVersion": "6.5.171", + "Filter_Enabled": true, + "Filter_Human_Label": "Export Pole Figure Images", + "Filter_Name": "WritePoleFigure", + "Filter_Uuid": "{a10bb78e-fcff-553d-97d6-830a43c85385}", + "CellEulerAnglesArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellData", + "Data Array Name": "Eulers" + }, + "CellPhasesArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellData", + "Data Array Name": "Phases" + }, + "CrystalStructuresArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellEnsembleData", + "Data Array Name": "CrystalStructures" + }, + "MaterialNameArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellEnsembleData", + "Data Array Name": "MaterialName" + }, + "GoodVoxelsArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellData", + "Data Array Name": "Mask" + }, + "UseGoodVoxels": 0, + "GenerationAlgorithm": 0, + "ImageLayout": 0, + "ImageSize": 512, + "LambertSize": 64, + "NumColors": 32, + "OutputPath": "/Users/mjackson/Workspace3/simplnx/Code_Review/vv/WritePoleFigure/out_cubic_legacy_6_5_171", + "ImagePrefix": "color_", + "Title": "WritePoleFigure AB" + }, + "2": { + "FilterVersion": "6.5.171", + "Filter_Enabled": true, + "Filter_Human_Label": "Export Pole Figure Images", + "Filter_Name": "WritePoleFigure", + "Filter_Uuid": "{a10bb78e-fcff-553d-97d6-830a43c85385}", + "CellEulerAnglesArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellData", + "Data Array Name": "Eulers" + }, + "CellPhasesArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellData", + "Data Array Name": "Phases" + }, + "CrystalStructuresArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellEnsembleData", + "Data Array Name": "CrystalStructures" + }, + "MaterialNameArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellEnsembleData", + "Data Array Name": "MaterialName" + }, + "GoodVoxelsArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellData", + "Data Array Name": "Mask" + }, + "UseGoodVoxels": 0, + "GenerationAlgorithm": 1, + "ImageLayout": 0, + "ImageSize": 512, + "LambertSize": 64, + "NumColors": 32, + "OutputPath": "/Users/mjackson/Workspace3/simplnx/Code_Review/vv/WritePoleFigure/out_cubic_legacy_6_5_171", + "ImagePrefix": "discrete_", + "Title": "WritePoleFigure AB" + }, + "PipelineBuilder": { + "Name": "WritePoleFigure legacy AB", + "Number_Filters": 3, + "Version": 6 + } +} \ No newline at end of file diff --git a/Code_Review/vv/WritePoleFigure/pf_legacy_171_hex.json b/Code_Review/vv/WritePoleFigure/pf_legacy_171_hex.json new file mode 100644 index 0000000000..f2eb7d9d85 --- /dev/null +++ b/Code_Review/vv/WritePoleFigure/pf_legacy_171_hex.json @@ -0,0 +1,192 @@ +{ + "0": { + "FilterVersion": "6.5.171", + "Filter_Enabled": true, + "Filter_Human_Label": "Read DREAM.3D Data File", + "Filter_Name": "DataContainerReader", + "Filter_Uuid": "{043cbde5-3878-5718-958f-ae75714df0df}", + "InputFile": "/Users/mjackson/Workspace3/simplnx/Code_Review/vv/WritePoleFigure/input/pf_input_legacy.dream3d", + "OverwriteExistingDataContainers": false, + "InputFileDataContainerArrayProxy": { + "Data Containers": [ + { + "Attribute Matricies": [ + { + "Data Arrays": [ + { + "Component Dimensions": [ + 3 + ], + "Flag": 2, + "Name": "Eulers", + "Object Type": "DataArray", + "Path": "/DataContainers/ImageDataContainer/CellData", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + }, + { + "Component Dimensions": [ + 1 + ], + "Flag": 2, + "Name": "Phases", + "Object Type": "DataArray", + "Path": "/DataContainers/ImageDataContainer/CellData", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + }, + { + "Component Dimensions": [ + 1 + ], + "Flag": 2, + "Name": "Mask", + "Object Type": "DataArray", + "Path": "/DataContainers/ImageDataContainer/CellData", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + } + ], + "Flag": 2, + "Name": "CellData", + "Type": 3 + }, + { + "Data Arrays": [ + { + "Component Dimensions": [ + 1 + ], + "Flag": 2, + "Name": "CrystalStructures", + "Object Type": "DataArray", + "Path": "/DataContainers/ImageDataContainer/CellEnsembleData", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + }, + { + "Component Dimensions": [ + 1 + ], + "Flag": 2, + "Name": "MaterialName", + "Object Type": "StringDataArray", + "Path": "/DataContainers/ImageDataContainer/CellEnsembleData", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + } + ], + "Flag": 2, + "Name": "CellEnsembleData", + "Type": 11 + } + ], + "Flag": 2, + "Geometry": { + "Geometry_Type": 0, + "Geometry_Type_Name": "ImageGeometry" + }, + "Name": "ImageDataContainer" + } + ], + "Version": 6 + } + }, + "1": { + "FilterVersion": "6.5.171", + "Filter_Enabled": true, + "Filter_Human_Label": "Export Pole Figure Images", + "Filter_Name": "WritePoleFigure", + "Filter_Uuid": "{a10bb78e-fcff-553d-97d6-830a43c85385}", + "CellEulerAnglesArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellData", + "Data Array Name": "Eulers" + }, + "CellPhasesArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellData", + "Data Array Name": "Phases" + }, + "CrystalStructuresArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellEnsembleData", + "Data Array Name": "CrystalStructures" + }, + "MaterialNameArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellEnsembleData", + "Data Array Name": "MaterialName" + }, + "GoodVoxelsArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellData", + "Data Array Name": "Mask" + }, + "UseGoodVoxels": 0, + "GenerationAlgorithm": 0, + "ImageLayout": 0, + "ImageSize": 512, + "LambertSize": 64, + "NumColors": 32, + "OutputPath": "/Users/mjackson/Workspace3/simplnx/Code_Review/vv/WritePoleFigure/out_legacy_6_5_171", + "ImagePrefix": "color_", + "Title": "WritePoleFigure AB" + }, + "2": { + "FilterVersion": "6.5.171", + "Filter_Enabled": true, + "Filter_Human_Label": "Export Pole Figure Images", + "Filter_Name": "WritePoleFigure", + "Filter_Uuid": "{a10bb78e-fcff-553d-97d6-830a43c85385}", + "CellEulerAnglesArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellData", + "Data Array Name": "Eulers" + }, + "CellPhasesArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellData", + "Data Array Name": "Phases" + }, + "CrystalStructuresArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellEnsembleData", + "Data Array Name": "CrystalStructures" + }, + "MaterialNameArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellEnsembleData", + "Data Array Name": "MaterialName" + }, + "GoodVoxelsArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellData", + "Data Array Name": "Mask" + }, + "UseGoodVoxels": 0, + "GenerationAlgorithm": 1, + "ImageLayout": 0, + "ImageSize": 512, + "LambertSize": 64, + "NumColors": 32, + "OutputPath": "/Users/mjackson/Workspace3/simplnx/Code_Review/vv/WritePoleFigure/out_legacy_6_5_171", + "ImagePrefix": "discrete_", + "Title": "WritePoleFigure AB" + }, + "PipelineBuilder": { + "Name": "WritePoleFigure legacy AB", + "Number_Filters": 3, + "Version": 6 + } +} \ No newline at end of file diff --git a/Code_Review/vv/WritePoleFigure/pf_legacy_172_cubic.json b/Code_Review/vv/WritePoleFigure/pf_legacy_172_cubic.json new file mode 100644 index 0000000000..5aa2e0bf71 --- /dev/null +++ b/Code_Review/vv/WritePoleFigure/pf_legacy_172_cubic.json @@ -0,0 +1,192 @@ +{ + "0": { + "FilterVersion": "6.5.171", + "Filter_Enabled": true, + "Filter_Human_Label": "Read DREAM.3D Data File", + "Filter_Name": "DataContainerReader", + "Filter_Uuid": "{043cbde5-3878-5718-958f-ae75714df0df}", + "InputFile": "/Users/mjackson/Workspace3/simplnx/Code_Review/vv/WritePoleFigure/input/pf_input_cubic_legacy.dream3d", + "OverwriteExistingDataContainers": false, + "InputFileDataContainerArrayProxy": { + "Data Containers": [ + { + "Attribute Matricies": [ + { + "Data Arrays": [ + { + "Component Dimensions": [ + 3 + ], + "Flag": 2, + "Name": "Eulers", + "Object Type": "DataArray", + "Path": "/DataContainers/ImageDataContainer/CellData", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + }, + { + "Component Dimensions": [ + 1 + ], + "Flag": 2, + "Name": "Phases", + "Object Type": "DataArray", + "Path": "/DataContainers/ImageDataContainer/CellData", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + }, + { + "Component Dimensions": [ + 1 + ], + "Flag": 2, + "Name": "Mask", + "Object Type": "DataArray", + "Path": "/DataContainers/ImageDataContainer/CellData", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + } + ], + "Flag": 2, + "Name": "CellData", + "Type": 3 + }, + { + "Data Arrays": [ + { + "Component Dimensions": [ + 1 + ], + "Flag": 2, + "Name": "CrystalStructures", + "Object Type": "DataArray", + "Path": "/DataContainers/ImageDataContainer/CellEnsembleData", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + }, + { + "Component Dimensions": [ + 1 + ], + "Flag": 2, + "Name": "MaterialName", + "Object Type": "StringDataArray", + "Path": "/DataContainers/ImageDataContainer/CellEnsembleData", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + } + ], + "Flag": 2, + "Name": "CellEnsembleData", + "Type": 11 + } + ], + "Flag": 2, + "Geometry": { + "Geometry_Type": 0, + "Geometry_Type_Name": "ImageGeometry" + }, + "Name": "ImageDataContainer" + } + ], + "Version": 6 + } + }, + "1": { + "FilterVersion": "6.5.171", + "Filter_Enabled": true, + "Filter_Human_Label": "Export Pole Figure Images", + "Filter_Name": "WritePoleFigure", + "Filter_Uuid": "{a10bb78e-fcff-553d-97d6-830a43c85385}", + "CellEulerAnglesArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellData", + "Data Array Name": "Eulers" + }, + "CellPhasesArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellData", + "Data Array Name": "Phases" + }, + "CrystalStructuresArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellEnsembleData", + "Data Array Name": "CrystalStructures" + }, + "MaterialNameArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellEnsembleData", + "Data Array Name": "MaterialName" + }, + "GoodVoxelsArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellData", + "Data Array Name": "Mask" + }, + "UseGoodVoxels": 0, + "GenerationAlgorithm": 0, + "ImageLayout": 0, + "ImageSize": 512, + "LambertSize": 64, + "NumColors": 32, + "OutputPath": "/Users/mjackson/Workspace3/simplnx/Code_Review/vv/WritePoleFigure/out_cubic_legacy_6_5_172", + "ImagePrefix": "color_", + "Title": "WritePoleFigure AB" + }, + "2": { + "FilterVersion": "6.5.171", + "Filter_Enabled": true, + "Filter_Human_Label": "Export Pole Figure Images", + "Filter_Name": "WritePoleFigure", + "Filter_Uuid": "{a10bb78e-fcff-553d-97d6-830a43c85385}", + "CellEulerAnglesArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellData", + "Data Array Name": "Eulers" + }, + "CellPhasesArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellData", + "Data Array Name": "Phases" + }, + "CrystalStructuresArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellEnsembleData", + "Data Array Name": "CrystalStructures" + }, + "MaterialNameArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellEnsembleData", + "Data Array Name": "MaterialName" + }, + "GoodVoxelsArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellData", + "Data Array Name": "Mask" + }, + "UseGoodVoxels": 0, + "GenerationAlgorithm": 1, + "ImageLayout": 0, + "ImageSize": 512, + "LambertSize": 64, + "NumColors": 32, + "OutputPath": "/Users/mjackson/Workspace3/simplnx/Code_Review/vv/WritePoleFigure/out_cubic_legacy_6_5_172", + "ImagePrefix": "discrete_", + "Title": "WritePoleFigure AB" + }, + "PipelineBuilder": { + "Name": "WritePoleFigure legacy AB", + "Number_Filters": 3, + "Version": 6 + } +} \ No newline at end of file diff --git a/Code_Review/vv/WritePoleFigure/pf_legacy_172_hex.json b/Code_Review/vv/WritePoleFigure/pf_legacy_172_hex.json new file mode 100644 index 0000000000..2700c4a5d2 --- /dev/null +++ b/Code_Review/vv/WritePoleFigure/pf_legacy_172_hex.json @@ -0,0 +1,192 @@ +{ + "0": { + "FilterVersion": "6.5.171", + "Filter_Enabled": true, + "Filter_Human_Label": "Read DREAM.3D Data File", + "Filter_Name": "DataContainerReader", + "Filter_Uuid": "{043cbde5-3878-5718-958f-ae75714df0df}", + "InputFile": "/Users/mjackson/Workspace3/simplnx/Code_Review/vv/WritePoleFigure/input/pf_input_legacy.dream3d", + "OverwriteExistingDataContainers": false, + "InputFileDataContainerArrayProxy": { + "Data Containers": [ + { + "Attribute Matricies": [ + { + "Data Arrays": [ + { + "Component Dimensions": [ + 3 + ], + "Flag": 2, + "Name": "Eulers", + "Object Type": "DataArray", + "Path": "/DataContainers/ImageDataContainer/CellData", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + }, + { + "Component Dimensions": [ + 1 + ], + "Flag": 2, + "Name": "Phases", + "Object Type": "DataArray", + "Path": "/DataContainers/ImageDataContainer/CellData", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + }, + { + "Component Dimensions": [ + 1 + ], + "Flag": 2, + "Name": "Mask", + "Object Type": "DataArray", + "Path": "/DataContainers/ImageDataContainer/CellData", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + } + ], + "Flag": 2, + "Name": "CellData", + "Type": 3 + }, + { + "Data Arrays": [ + { + "Component Dimensions": [ + 1 + ], + "Flag": 2, + "Name": "CrystalStructures", + "Object Type": "DataArray", + "Path": "/DataContainers/ImageDataContainer/CellEnsembleData", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + }, + { + "Component Dimensions": [ + 1 + ], + "Flag": 2, + "Name": "MaterialName", + "Object Type": "StringDataArray", + "Path": "/DataContainers/ImageDataContainer/CellEnsembleData", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + } + ], + "Flag": 2, + "Name": "CellEnsembleData", + "Type": 11 + } + ], + "Flag": 2, + "Geometry": { + "Geometry_Type": 0, + "Geometry_Type_Name": "ImageGeometry" + }, + "Name": "ImageDataContainer" + } + ], + "Version": 6 + } + }, + "1": { + "FilterVersion": "6.5.171", + "Filter_Enabled": true, + "Filter_Human_Label": "Export Pole Figure Images", + "Filter_Name": "WritePoleFigure", + "Filter_Uuid": "{a10bb78e-fcff-553d-97d6-830a43c85385}", + "CellEulerAnglesArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellData", + "Data Array Name": "Eulers" + }, + "CellPhasesArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellData", + "Data Array Name": "Phases" + }, + "CrystalStructuresArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellEnsembleData", + "Data Array Name": "CrystalStructures" + }, + "MaterialNameArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellEnsembleData", + "Data Array Name": "MaterialName" + }, + "GoodVoxelsArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellData", + "Data Array Name": "Mask" + }, + "UseGoodVoxels": 0, + "GenerationAlgorithm": 0, + "ImageLayout": 0, + "ImageSize": 512, + "LambertSize": 64, + "NumColors": 32, + "OutputPath": "/Users/mjackson/Workspace3/simplnx/Code_Review/vv/WritePoleFigure/out_legacy_6_5_172", + "ImagePrefix": "color_", + "Title": "WritePoleFigure AB" + }, + "2": { + "FilterVersion": "6.5.171", + "Filter_Enabled": true, + "Filter_Human_Label": "Export Pole Figure Images", + "Filter_Name": "WritePoleFigure", + "Filter_Uuid": "{a10bb78e-fcff-553d-97d6-830a43c85385}", + "CellEulerAnglesArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellData", + "Data Array Name": "Eulers" + }, + "CellPhasesArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellData", + "Data Array Name": "Phases" + }, + "CrystalStructuresArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellEnsembleData", + "Data Array Name": "CrystalStructures" + }, + "MaterialNameArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellEnsembleData", + "Data Array Name": "MaterialName" + }, + "GoodVoxelsArrayPath": { + "Data Container Name": "ImageDataContainer", + "Attribute Matrix Name": "CellData", + "Data Array Name": "Mask" + }, + "UseGoodVoxels": 0, + "GenerationAlgorithm": 1, + "ImageLayout": 0, + "ImageSize": 512, + "LambertSize": 64, + "NumColors": 32, + "OutputPath": "/Users/mjackson/Workspace3/simplnx/Code_Review/vv/WritePoleFigure/out_legacy_6_5_172", + "ImagePrefix": "discrete_", + "Title": "WritePoleFigure AB" + }, + "PipelineBuilder": { + "Name": "WritePoleFigure legacy AB", + "Number_Filters": 3, + "Version": 6 + } +} \ No newline at end of file diff --git a/Code_Review/vv/WritePoleFigure/pf_nx_cubic.d3dpipeline b/Code_Review/vv/WritePoleFigure/pf_nx_cubic.d3dpipeline new file mode 100644 index 0000000000..a19d6bf451 --- /dev/null +++ b/Code_Review/vv/WritePoleFigure/pf_nx_cubic.d3dpipeline @@ -0,0 +1,243 @@ +{ + "name": "WritePoleFigure NX AB", + "pipeline": [ + { + "args": { + "import_data_object": { + "value": { + "file_path": "/Users/mjackson/Workspace3/simplnx/Code_Review/vv/WritePoleFigure/input/pf_input_cubic.dream3d", + "data_paths": null + }, + "version": 1 + } + }, + "comments": "", + "filter": { + "name": "nx::core::ReadDREAM3DFilter", + "uuid": "0dbd31c7-19e0-4077-83ef-f4a6459a0e2d" + }, + "isDisabled": false + }, + { + "args": { + "title": { + "value": "WritePoleFigure AB", + "version": 1 + }, + "generation_algorithm_index": { + "value": 0, + "version": 1 + }, + "lambert_size": { + "value": 64, + "version": 1 + }, + "num_colors": { + "value": 32, + "version": 1 + }, + "discrete_marker_radius": { + "value": 3, + "version": 1 + }, + "image_layout_index": { + "value": 0, + "version": 1 + }, + "image_size": { + "value": 512, + "version": 1 + }, + "output_path": { + "value": "/Users/mjackson/Workspace3/simplnx/Code_Review/vv/WritePoleFigure/out_cubic_nx", + "version": 1 + }, + "image_prefix": { + "value": "nx_color_", + "version": 1 + }, + "use_mask": { + "value": false, + "version": 1 + }, + "cell_euler_angles_array_path": { + "value": "Imported Data/Eulers", + "version": 1 + }, + "cell_phases_array_path": { + "value": "Imported Data/Phases", + "version": 1 + }, + "mask_array_path": { + "value": "Imported Data/Mask", + "version": 1 + }, + "crystal_structures_array_path": { + "value": "EnsembleAttributeMatrix/CrystalStructures", + "version": 1 + }, + "material_name_array_path": { + "value": "EnsembleAttributeMatrix/PhaseNames", + "version": 1 + }, + "save_as_image_geometry": { + "value": true, + "version": 1 + }, + "write_image_to_disk": { + "value": true, + "version": 1 + }, + "output_image_geometry_path": { + "value": "PoleFigure_Color", + "version": 1 + }, + "save_intensity_plots": { + "value": false, + "version": 1 + }, + "normalize_to_mrd": { + "value": true, + "version": 1 + }, + "intensity_geometry_path": { + "value": "PoleFigure_Color Intensity", + "version": 1 + }, + "intensity_plot_1_name": { + "value": "<0001>", + "version": 1 + }, + "intensity_plot_2_name": { + "value": "<11-20>", + "version": 1 + }, + "intensity_plot_3_name": { + "value": "<10-10>", + "version": 1 + }, + "hex_convention_index": { + "value": 0, + "version": 1 + } + }, + "comments": "", + "filter": { + "name": "nx::core::WritePoleFigureFilter", + "uuid": "00cbb97e-a5c2-43e6-9a35-17a0f9ce26ed" + }, + "isDisabled": false + }, + { + "args": { + "title": { + "value": "WritePoleFigure AB", + "version": 1 + }, + "generation_algorithm_index": { + "value": 1, + "version": 1 + }, + "lambert_size": { + "value": 64, + "version": 1 + }, + "num_colors": { + "value": 32, + "version": 1 + }, + "discrete_marker_radius": { + "value": 3, + "version": 1 + }, + "image_layout_index": { + "value": 0, + "version": 1 + }, + "image_size": { + "value": 512, + "version": 1 + }, + "output_path": { + "value": "/Users/mjackson/Workspace3/simplnx/Code_Review/vv/WritePoleFigure/out_cubic_nx", + "version": 1 + }, + "image_prefix": { + "value": "nx_discrete_", + "version": 1 + }, + "use_mask": { + "value": false, + "version": 1 + }, + "cell_euler_angles_array_path": { + "value": "Imported Data/Eulers", + "version": 1 + }, + "cell_phases_array_path": { + "value": "Imported Data/Phases", + "version": 1 + }, + "mask_array_path": { + "value": "Imported Data/Mask", + "version": 1 + }, + "crystal_structures_array_path": { + "value": "EnsembleAttributeMatrix/CrystalStructures", + "version": 1 + }, + "material_name_array_path": { + "value": "EnsembleAttributeMatrix/PhaseNames", + "version": 1 + }, + "save_as_image_geometry": { + "value": true, + "version": 1 + }, + "write_image_to_disk": { + "value": true, + "version": 1 + }, + "output_image_geometry_path": { + "value": "PoleFigure_Discrete", + "version": 1 + }, + "save_intensity_plots": { + "value": false, + "version": 1 + }, + "normalize_to_mrd": { + "value": true, + "version": 1 + }, + "intensity_geometry_path": { + "value": "PoleFigure_Discrete Intensity", + "version": 1 + }, + "intensity_plot_1_name": { + "value": "<0001>", + "version": 1 + }, + "intensity_plot_2_name": { + "value": "<11-20>", + "version": 1 + }, + "intensity_plot_3_name": { + "value": "<10-10>", + "version": 1 + }, + "hex_convention_index": { + "value": 0, + "version": 1 + } + }, + "comments": "", + "filter": { + "name": "nx::core::WritePoleFigureFilter", + "uuid": "00cbb97e-a5c2-43e6-9a35-17a0f9ce26ed" + }, + "isDisabled": false + } + ], + "version": 1 +} \ No newline at end of file diff --git a/Code_Review/vv/WritePoleFigure/pf_nx_hex.d3dpipeline b/Code_Review/vv/WritePoleFigure/pf_nx_hex.d3dpipeline new file mode 100644 index 0000000000..7e72a5d7f5 --- /dev/null +++ b/Code_Review/vv/WritePoleFigure/pf_nx_hex.d3dpipeline @@ -0,0 +1,243 @@ +{ + "name": "WritePoleFigure NX AB", + "pipeline": [ + { + "args": { + "import_data_object": { + "value": { + "file_path": "/Users/mjackson/Workspace3/simplnx/Code_Review/vv/WritePoleFigure/input/pf_input_hex.dream3d", + "data_paths": null + }, + "version": 1 + } + }, + "comments": "", + "filter": { + "name": "nx::core::ReadDREAM3DFilter", + "uuid": "0dbd31c7-19e0-4077-83ef-f4a6459a0e2d" + }, + "isDisabled": false + }, + { + "args": { + "title": { + "value": "WritePoleFigure AB", + "version": 1 + }, + "generation_algorithm_index": { + "value": 0, + "version": 1 + }, + "lambert_size": { + "value": 64, + "version": 1 + }, + "num_colors": { + "value": 32, + "version": 1 + }, + "discrete_marker_radius": { + "value": 3, + "version": 1 + }, + "image_layout_index": { + "value": 0, + "version": 1 + }, + "image_size": { + "value": 512, + "version": 1 + }, + "output_path": { + "value": "/Users/mjackson/Workspace3/simplnx/Code_Review/vv/WritePoleFigure/out_nx", + "version": 1 + }, + "image_prefix": { + "value": "nx_color_", + "version": 1 + }, + "use_mask": { + "value": false, + "version": 1 + }, + "cell_euler_angles_array_path": { + "value": "Imported Data/Eulers", + "version": 1 + }, + "cell_phases_array_path": { + "value": "Imported Data/Phases", + "version": 1 + }, + "mask_array_path": { + "value": "Imported Data/Mask", + "version": 1 + }, + "crystal_structures_array_path": { + "value": "EnsembleAttributeMatrix/CrystalStructures", + "version": 1 + }, + "material_name_array_path": { + "value": "EnsembleAttributeMatrix/PhaseNames", + "version": 1 + }, + "save_as_image_geometry": { + "value": true, + "version": 1 + }, + "write_image_to_disk": { + "value": true, + "version": 1 + }, + "output_image_geometry_path": { + "value": "PoleFigure_Color", + "version": 1 + }, + "save_intensity_plots": { + "value": false, + "version": 1 + }, + "normalize_to_mrd": { + "value": true, + "version": 1 + }, + "intensity_geometry_path": { + "value": "PoleFigure_Color Intensity", + "version": 1 + }, + "intensity_plot_1_name": { + "value": "<0001>", + "version": 1 + }, + "intensity_plot_2_name": { + "value": "<11-20>", + "version": 1 + }, + "intensity_plot_3_name": { + "value": "<10-10>", + "version": 1 + }, + "hex_convention_index": { + "value": 0, + "version": 1 + } + }, + "comments": "", + "filter": { + "name": "nx::core::WritePoleFigureFilter", + "uuid": "00cbb97e-a5c2-43e6-9a35-17a0f9ce26ed" + }, + "isDisabled": false + }, + { + "args": { + "title": { + "value": "WritePoleFigure AB", + "version": 1 + }, + "generation_algorithm_index": { + "value": 1, + "version": 1 + }, + "lambert_size": { + "value": 64, + "version": 1 + }, + "num_colors": { + "value": 32, + "version": 1 + }, + "discrete_marker_radius": { + "value": 3, + "version": 1 + }, + "image_layout_index": { + "value": 0, + "version": 1 + }, + "image_size": { + "value": 512, + "version": 1 + }, + "output_path": { + "value": "/Users/mjackson/Workspace3/simplnx/Code_Review/vv/WritePoleFigure/out_nx", + "version": 1 + }, + "image_prefix": { + "value": "nx_discrete_", + "version": 1 + }, + "use_mask": { + "value": false, + "version": 1 + }, + "cell_euler_angles_array_path": { + "value": "Imported Data/Eulers", + "version": 1 + }, + "cell_phases_array_path": { + "value": "Imported Data/Phases", + "version": 1 + }, + "mask_array_path": { + "value": "Imported Data/Mask", + "version": 1 + }, + "crystal_structures_array_path": { + "value": "EnsembleAttributeMatrix/CrystalStructures", + "version": 1 + }, + "material_name_array_path": { + "value": "EnsembleAttributeMatrix/PhaseNames", + "version": 1 + }, + "save_as_image_geometry": { + "value": true, + "version": 1 + }, + "write_image_to_disk": { + "value": true, + "version": 1 + }, + "output_image_geometry_path": { + "value": "PoleFigure_Discrete", + "version": 1 + }, + "save_intensity_plots": { + "value": false, + "version": 1 + }, + "normalize_to_mrd": { + "value": true, + "version": 1 + }, + "intensity_geometry_path": { + "value": "PoleFigure_Discrete Intensity", + "version": 1 + }, + "intensity_plot_1_name": { + "value": "<0001>", + "version": 1 + }, + "intensity_plot_2_name": { + "value": "<11-20>", + "version": 1 + }, + "intensity_plot_3_name": { + "value": "<10-10>", + "version": 1 + }, + "hex_convention_index": { + "value": 0, + "version": 1 + } + }, + "comments": "", + "filter": { + "name": "nx::core::WritePoleFigureFilter", + "uuid": "00cbb97e-a5c2-43e6-9a35-17a0f9ce26ed" + }, + "isDisabled": false + } + ], + "version": 1 +} \ No newline at end of file diff --git a/src/Plugins/OrientationAnalysis/docs/ComputeFaceIPFColoringFilter.md b/src/Plugins/OrientationAnalysis/docs/ComputeFaceIPFColoringFilter.md index 462f7f10d1..409d71c9db 100644 --- a/src/Plugins/OrientationAnalysis/docs/ComputeFaceIPFColoringFilter.md +++ b/src/Plugins/OrientationAnalysis/docs/ComputeFaceIPFColoringFilter.md @@ -14,6 +14,12 @@ This **Filter** generates a pair of colors for each **Triangle** in a **Triangle ------------ +### Crystallographic Convention (Hexagonal & Trigonal) + +The IPF color itself is **convention-independent** for hexagonal/trigonal phases (the X‖a (TSL/EDAX) and X‖a\* (MTEX/Oxford) bases differ only by a 30° rotation about the c-axis, which does not change the RGB). Only the *labeling* of the inverse-pole-figure triangle corners depends on the convention; the IPF legends shipped with DREAM3D-NX are drawn in the **X‖a (TSL/EDAX)** convention (apex `[0001]` = red, the a-axis `[2-1-10]` = green, the m-axis `[10-10]` = blue). See the **Compute IPF Colors** filter documentation for the full corner→color table. + +------------ + % Auto generated parameter table will be inserted here ## Example Pipelines diff --git a/src/Plugins/OrientationAnalysis/docs/ComputeIPFColorsFilter.md b/src/Plugins/OrientationAnalysis/docs/ComputeIPFColorsFilter.md index 5a9861f4e4..9cbe5109a5 100644 --- a/src/Plugins/OrientationAnalysis/docs/ComputeIPFColorsFilter.md +++ b/src/Plugins/OrientationAnalysis/docs/ComputeIPFColorsFilter.md @@ -26,6 +26,18 @@ IPF Legends for most all of the Laue classes can be found in the Data/Orientatio ![Example Data Set](Images/IPFColor_1.png) +### Crystallographic Convention (Hexagonal & Trigonal) + +For hexagonal and trigonal phases the **IPF color itself is convention-independent**: the X‖a (TSL/EDAX) and X‖a\* (MTEX/Oxford) bases differ only by a 30° rotation about the c-axis, which does not change the computed RGB. What depends on the convention is only how the inverse-pole-figure triangle's corners are *labeled*. The IPF legends shipped with DREAM3D-NX are drawn in the **X‖a (TSL/EDAX)** convention, where the Cartesian +X axis is the a-axis [2-1-10]: + +| Triangle corner | Crystal direction (X‖a) | Color | +|---|---|---| +| apex | [0001] (c-axis) | red | +| η = 0° edge | [2-1-10] (a-axis) | green | +| η = 30° edge | [10-10] (m-axis) | blue | + +The 6/m, -3m, and -3 Laue classes use wider/shifted triangle wedges (so their green/blue corners are different crystal directions), but the apex is always [0001] = red. Each legend image prints the convention it was generated with as a footnote along its bottom edge. + % Auto generated parameter table will be inserted here ## Example Pipelines diff --git a/src/Plugins/OrientationAnalysis/docs/WritePoleFigureFilter.md b/src/Plugins/OrientationAnalysis/docs/WritePoleFigureFilter.md index 4010cb9aac..51a66d6ece 100644 --- a/src/Plugins/OrientationAnalysis/docs/WritePoleFigureFilter.md +++ b/src/Plugins/OrientationAnalysis/docs/WritePoleFigureFilter.md @@ -6,51 +6,69 @@ IO (Output) ## Description -This **Filter** creates a standard crystallographic pole figure image for each **Ensemble** (phase) in a selected **Data Container**. The **Filter** uses Euler angles in radians and requires the crystal structures and material names for each **Ensemble** array and the corresponding **Ensemble** Ids on the **Cells**. The **Filter** also optionally can use a *mask* array to determine which angles are valid for the pole figure computation. +This filter creates standard crystallographic **pole figure** images, one set per **Ensemble** (phase) present in the data. A pole figure is a 2D circular plot that maps selected 3D crystal-orientation directions onto a flat disk, making it easy to see whether a material has a preferred orientation (texture) or is randomly oriented. This filter produces pole figures for the **<001>**, **<011>**, and **<111>** crystal directions. -In a practical sense, this means that the following information is available to the filter: +The filter reads **Euler angles** (in *radians*, Bunge Z-X-Z convention) describing the orientation of each cell, the phase each cell belongs to, and per-phase crystal-structure information. An optional **Mask** array can exclude non-indexed or invalid points from the plot. -- Cell Level +### How the Pole Figure Is Drawn - - Euler Angles (Float 32) ordered as sets of (phi1, Phi, phi2). - - Phases (Int32) This is the phase that each Euler angle belongs to - - Optional Mask(boolean or uint8) True/1 if the Euler angle should be included in the pole figure. +Two rendering methods are available through the *Pole Figure Type* parameter: -- Ensemble Level (Phase Information) +- **Color Intensity [0]**: Produces a continuous color intensity map. To do this the filter accumulates orientation counts onto a **modified Lambert square** -- an equal-area grid laid over a square that can be folded onto a hemisphere -- and interpolates that grid onto the unit circle. This is a **Lambert (equal-area) projection**. EBSD OEM software does not use this exact interpolation, so the output may look slightly different from an OEM-generated pole figure. +- **Discrete [1]**: Produces a point-based plot, drawing a small filled circle at each projected orientation. The circle size is controlled by the *Discrete Marker Radius (Pixels)* parameter so markers stay visible at any image size (legacy DREAM3D marked each hit as a single pixel). This uses a **stereographic** projection, the classic pole-figure projection that maps directions from a sphere onto a plane while preserving angles. - - Laue Class (UInt32) - - Material Names (String) +The crystal symmetry used when folding orientations into the pole figure is determined by each phase's **Laue Class** -- the point-group symmetry class of the crystal (for example, cubic m-3m or hexagonal 6/mmm). The Laue Class is looked up from the per-**Ensemble** Crystal Structures array. -### Algorithm Choice +**Only an advanced user with intimate knowledge of the modified Lambert projection should change the *Lambert Image Size (Pixels)* parameter.** This value is the height/width, in *pixels*, of the internal Lambert square used for interpolation. -1: The pole figure algorithm uses a *modified Lambert square* to perform the interpolations onto the unit circle. This is an alternate type -of interpolation that the EBSD OEMs do not perform which may make the output from DREAM3D-NX look slightly different than output -obtained from the OEM programs. +### Reference Frame Conventions (Matching EDAX OIM and MTEX) -**Only an advanced user with intimate knowledge of the modified Lambert projection should attempt to change the value for -the "Lambert Image Size (Pixels)" input parameter.** +This filter plots orientations exactly as they are stored in the **Euler Angles** array; it applies **no reference-frame correction** of its own. A pole figure depends only on the orientations, never on the physical location of the **Cell** each orientation came from. -2: Discrete Pole figure. The algorithm will simply mark each pixel that had at least 1 count as a black pixel. +This matters when comparing the output to other tools. TSL/EDAX `.ang` files record the **Euler angles** and the spatial scan coordinates in two *different* reference frames that are offset by a fixed rotation. EDAX OIM Analysis and the MTEX toolbox display pole figures in the *corrected* (spatial) frame, so their pole figures appear rotated relative to the uncorrected orientations this filter receives. With no correction applied, this filter's output is consistent with MTEX loaded *without* `convertEuler2SpatialReferenceFrame`. + +To make a pole figure match EDAX OIM or MTEX, apply the [Rotate Euler Reference Frame](RotateEulerRefFrameFilter.md) filter to the **Euler Angles** *before* this filter. The [Rotate Sample Reference Frame](../SimplnxCore/RotateSampleRefFrameFilter.md) filter only relocates **Cells** in space and leaves orientation values unchanged, so it has **no effect** on a pole figure. + +The correction is a single **180°** rotation; the axis selects the convention. Each axis below reproduces the correspondingly numbered MTEX `convertEuler2SpatialReferenceFrame` "setting", entered into [Rotate Euler Reference Frame](RotateEulerRefFrameFilter.md) as a normalized axis and an angle of **180 degrees**: + +| Rotation Axis | Angle | MTEX setting | Notes | +|---|---|---|---| +| *(0.7071067812, 0.7071067812, 0)* | 180° | setting 1 | | +| *(0.7071067812, -0.7071067812, 0)* | 180° | setting 2 | **EDAX/TSL default** -- matches EDAX OIM | +| *(1, 0, 0)* | 180° | setting 3 | | +| *(0, 1, 0)* | 180° | setting 4 | | + +For data read from [Read EDAX EBSD Data (.ang)](ReadAngDataFilter.md), use the *(0.7071067812, -0.7071067812, 0)*, 180° rotation. Apply the correction only once; do not also correct upstream if it has already been applied. + +The *Hex/Trig Cartesian Basis Convention* parameter (*x||a* or *x||a\**) is a separate setting. It selects how the hexagonal/trigonal crystal basis is aligned to Cartesian axes and shifts the prismatic **<011>** and **<111>** pole positions by 30°; it does not affect the basal **<001>** pole figure. Use *x||a* to match the EDAX/TSL and legacy DREAM3D convention. + +### Required Input Sources + +- **Euler Angles** and **Phases** -- per-cell orientation and phase data, typically read from EBSD data via [Read H5EBSD File](ReadH5EbsdFilter.md), [Read EDAX EBSD Data (.ang)](ReadAngDataFilter.md), or [Read Oxford Instr. EBSD Data (.ctf)](ReadCtfDataFilter.md). +- **Crystal Structures** (per-**Ensemble**, used to derive each phase's Laue Class) and **Material Name** -- created as part of the **Ensemble** data by the same EBSD reader. +- **Mask** (optional) -- a per-cell boolean array marking valid points, produced by [Multi-Threshold Objects](../SimplnxCore/MultiThresholdObjectsFilter.md). Enable *Use Mask Array* to apply it. ## Output Options ### Write Image to Disk -The user can select to have the combined set of pole figures written to disk as a tiff image file +When *Write Pole Figure as Image* is enabled, the combined set of pole figures is written to disk as a PNG image file, one file per phase, in the selected output directory. ### Save Pole Figure as Image Geometry -The combined pole figure image will be saved into the DataStructure as an Image Geometry +When *Save Output as Image Geometry* is enabled, the combined pole-figure image is stored in the DataStructure as an **Image Geometry** so it can be viewed inside DREAM3D-NX. + +### Save Count Data Arrays + +When *Save Count Images* is enabled, the per-direction count data for each pole figure is stored as a Data Array inside an **Image Geometry**, allowing custom color plots to be made later. A string Data Array is also stored that records the metadata used to build each plot (number of points, hemisphere, phase name, etc.). When *Normalize Count Data to MRD* is enabled, the counts are normalized to **multiples of a random distribution (MRD)**, where *1.0* means "as common as a random arrangement." -### Save Raw Intensity Data +### Image Size -The normalized count data is saved for each pole figure into a Data Array that is stored inside an Image Geometry. This allows -the user to select their own color plots. The Image Geometry will also have a string DataArray that lists the pertinent -data that went into the creation: Number of points, which hemisphere, Phase Name, etc. +The *Image Size (Square Pixels)* parameter sets the height and width, in *pixels*, of each individual pole figure in the output image. ### Image Layout -The *Image Layout* parameter controls how the pole figures are arranged in the output image. Supporting information (including the color bar legend for color pole figures) will also be printed on the image. +The *Image Layout* parameter controls how the pole figures are arranged in the output image. Supporting information (including the color bar legend for color pole figures) is also drawn on the image. | Colorized Intensity | Discrete | |--------------------|----------| @@ -62,13 +80,6 @@ The available layout choices are: - **Vertical [1]**: Pole figures are arranged in a single vertical column. - **Square [2]**: Pole figures are arranged in a square grid. -### Pole Figure Type - -The *Pole Figure Type* parameter selects the rendering method for the pole figure: - -- **Color Intensity [0]**: Generates a continuous color intensity map using a modified Lambert square interpolation onto the unit circle. -- **Discrete [1]**: Generates a discrete point-based pole figure by marking each pixel that received at least one orientation count. - % Auto generated parameter table will be inserted here ## Example Pipelines diff --git a/src/Plugins/OrientationAnalysis/pipelines/EBSD_File_Processing/TxCopper_Exposed.d3dpipeline b/src/Plugins/OrientationAnalysis/pipelines/EBSD_File_Processing/TxCopper_Exposed.d3dpipeline index 55e7c44ca7..ba4a2f6b46 100644 --- a/src/Plugins/OrientationAnalysis/pipelines/EBSD_File_Processing/TxCopper_Exposed.d3dpipeline +++ b/src/Plugins/OrientationAnalysis/pipelines/EBSD_File_Processing/TxCopper_Exposed.d3dpipeline @@ -327,10 +327,18 @@ "value": "Cugrid_after 2nd_15kv_2kx_2/Phase Data/CrystalStructures", "version": 1 }, + "discrete_marker_radius": { + "value": 3, + "version": 1 + }, "generation_algorithm_index": { "value": 0, "version": 1 }, + "hex_convention_index": { + "value": 0, + "version": 1 + }, "image_layout_index": { "value": 2, "version": 1 @@ -387,7 +395,7 @@ "value": "Data/Output/TexturedCopper", "version": 1 }, - "parameters_version": 1, + "parameters_version": 3, "save_as_image_geometry": { "value": true, "version": 1 diff --git a/src/Plugins/OrientationAnalysis/pipelines/EBSD_File_Processing/TxCopper_Unexposed.d3dpipeline b/src/Plugins/OrientationAnalysis/pipelines/EBSD_File_Processing/TxCopper_Unexposed.d3dpipeline index ffbd41b08d..fd64872252 100644 --- a/src/Plugins/OrientationAnalysis/pipelines/EBSD_File_Processing/TxCopper_Unexposed.d3dpipeline +++ b/src/Plugins/OrientationAnalysis/pipelines/EBSD_File_Processing/TxCopper_Unexposed.d3dpipeline @@ -327,10 +327,18 @@ "value": "Cugrid_after 2nd_15kv_2kx_2/Phase Data/CrystalStructures", "version": 1 }, + "discrete_marker_radius": { + "value": 3, + "version": 1 + }, "generation_algorithm_index": { "value": 0, "version": 1 }, + "hex_convention_index": { + "value": 0, + "version": 1 + }, "image_layout_index": { "value": 2, "version": 1 @@ -387,7 +395,7 @@ "value": "Data/Output/TexturedCopper", "version": 1 }, - "parameters_version": 1, + "parameters_version": 3, "save_as_image_geometry": { "value": true, "version": 1 diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/WritePoleFigure.cpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/WritePoleFigure.cpp index 740370798c..d63ac8e41b 100644 --- a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/WritePoleFigure.cpp +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/WritePoleFigure.cpp @@ -1,6 +1,5 @@ #include "WritePoleFigure.hpp" -#include "OrientationAnalysis/utilities/TiffWriter.hpp" #include "OrientationAnalysis/utilities/delaunator.h" #include "simplnx/Common/Constants.hpp" @@ -607,6 +606,8 @@ Result<> WritePoleFigure::operator()() config.discrete = (static_cast(m_InputValues->GenerationAlgorithm) == WritePoleFigure::Algorithm::Discrete); config.discreteHeatMap = k_UseDiscreteHeatMap; config.hexConvention = m_InputValues->HexConvention; + config.flipFinalImage = m_InputValues->FlipFinalImage; + config.axisNames = std::vector{"A1", "A2", "A3"}; m_MessageHandler({IFilter::Message::Type::Info, fmt::format("Generating Pole Figures for Phase {}", phase)}); if(m_InputValues->SaveIntensityData) @@ -714,11 +715,13 @@ Result<> WritePoleFigure::operator()() compositeConfig.sphereRadius = config.sphereRadius; compositeConfig.discrete = config.discrete; compositeConfig.discreteHeatMap = config.discreteHeatMap; + compositeConfig.markerStyle.radiusFraction = m_InputValues->DiscreteMarkerRadius; compositeConfig.colorMap = config.colorMap; - compositeConfig.labels = config.labels; + compositeConfig.poleFigureNames = config.labels; compositeConfig.order = config.order; - // flipFinalImage defaults to true in CompositePoleFigureConfiguration_t, - // matching the old behavior where flipAndMirror was always applied. + compositeConfig.axisNames = config.axisNames; + + compositeConfig.flipFinalImage = config.flipFinalImage; compositeConfig.layoutType = static_cast(m_InputValues->ImageLayout); compositeConfig.laueOpsIndex = crystalStructures[phase]; compositeConfig.phaseName = materialNames[phase]; @@ -726,9 +729,10 @@ Result<> WritePoleFigure::operator()() compositeConfig.title = m_InputValues->Title; compositeConfig.hexConvention = m_InputValues->HexConvention; - // Generate the composite pole figure image - ebsdlib::PoleFigureCompositor compositor; - ebsdlib::CompositePoleFigureResult compositeResult = compositor.generateCompositeImage(compositeConfig); + // Generate the composite pole figure image. GeneratePoleFigureComposite routes + // discrete (non-heatmap) figures to the vector marker renderer and everything + // else to the raster PoleFigureCompositor. + ebsdlib::CompositePoleFigureResult compositeResult = ebsdlib::GeneratePoleFigureComposite(compositeConfig); if(compositeResult.image == nullptr) { @@ -774,7 +778,7 @@ Result<> WritePoleFigure::operator()() auto result = PngWriter::WriteColorImage(filename, pageWidth, pageHeight, 4, compositeResult.image->getPointer(0)); if(result.first < 0) { - return MakeErrorResult(-53900, fmt::format("Error writing pole figure image '{}' to disk.\n Error Code from Tiff Writer: {}\n Message: {}", filename, result.first, result.second)); + return MakeErrorResult(-53900, fmt::format("Error writing pole figure image '{}' to disk.\n Error Code from PNG Writer: {}\n Message: {}", filename, result.first, result.second)); } } } diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/WritePoleFigure.hpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/WritePoleFigure.hpp index e28ba9d637..e36d157cbd 100644 --- a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/WritePoleFigure.hpp +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/WritePoleFigure.hpp @@ -29,6 +29,7 @@ struct ORIENTATIONANALYSIS_EXPORT WritePoleFigureInputValues ChoicesParameter::ValueType GenerationAlgorithm; int32 LambertSize; int32 NumColors; + float32 DiscreteMarkerRadius; ChoicesParameter::ValueType ImageFormat; ChoicesParameter::ValueType ImageLayout; FileSystemPathParameter::ValueType OutputPath; @@ -47,6 +48,8 @@ struct ORIENTATIONANALYSIS_EXPORT WritePoleFigureInputValues bool SaveIntensityData; bool NormalizeToMRD; + + bool FlipFinalImage; DataPath IntensityGeometryDataPath; std::string IntensityPlot1Name; std::string IntensityPlot2Name; diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/WritePoleFigureFilter.cpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/WritePoleFigureFilter.cpp index df5c90419f..57532f3ba6 100644 --- a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/WritePoleFigureFilter.cpp +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/WritePoleFigureFilter.cpp @@ -92,6 +92,7 @@ Parameters WritePoleFigureFilter::parameters() const params.insertSeparator(Parameters::Separator{"Input Parameter(s)"}); params.insert(std::make_unique(k_Title_Key, "Figure Title", "The title to place at the top of the Pole Figure", "Figure Title")); params.insert(std::make_unique(k_ImageSize_Key, "Image Size (Square Pixels)", "The number of pixels that define the height and width of **each** output pole figure", 512)); + params.insert(std::make_unique(k_ImageLayout_Key, "Image Layout", "How to layout the 3 pole figures. 0=Horizontal, 1=Vertical, 2=Square", 0, ChoicesParameter::Choices{"Horizontal", "Vertical", "Square"})); @@ -99,6 +100,8 @@ Parameters WritePoleFigureFilter::parameters() const ChoicesParameter::Choices{"Color Intensity", "Discrete"})); params.insert(std::make_unique(k_LambertSize_Key, "Lambert Image Size (Pixels)", "The height/width of the internal Lambert Square that is used for interpolation", 64)); params.insert(std::make_unique(k_NumColors_Key, "Number of Colors", "The number of colors to use for the Color Intensity pole figures", 32)); + params.insert( + std::make_unique(k_DiscreteMarkerRadius_Key, "Discrete Marker Radius (Pixels)", "Radius of each discrete pole marker. Only used for the Discrete pole figure type.", 3)); params.insert(std::make_unique( k_HexConvention_Key, "Hex/Trig Cartesian Basis Convention", "Cartesian basis used for hex/trigonal phases. Pole-figure positions and corner labels are rotated 30° about the c-axis between the two:\n" @@ -148,6 +151,7 @@ Parameters WritePoleFigureFilter::parameters() const params.linkParameters(k_GenerationAlgorithm_Key, k_LambertSize_Key, std::make_any(0)); params.linkParameters(k_GenerationAlgorithm_Key, k_NumColors_Key, std::make_any(0)); + params.linkParameters(k_GenerationAlgorithm_Key, k_DiscreteMarkerRadius_Key, std::make_any(1)); params.linkParameters(k_WriteImageToDisk, k_OutputPath_Key, true); params.linkParameters(k_WriteImageToDisk, k_ImagePrefix_Key, true); @@ -166,7 +170,7 @@ Parameters WritePoleFigureFilter::parameters() const //------------------------------------------------------------------------------ IFilter::VersionType WritePoleFigureFilter::parametersVersion() const { - return 2; + return 3; } //------------------------------------------------------------------------------ @@ -203,6 +207,20 @@ IFilter::PreflightResult WritePoleFigureFilter::preflightImpl(const DataStructur PreflightResult preflightResult; + if(pImageSizeValue <= 0) + { + return {MakeErrorResult(-680002, fmt::format("'Image Size (Square Pixels)' must be greater than zero. Value given: {}", pImageSizeValue)), {}}; + } + // The marker radius is only active (and only used) for the Discrete pole figure type. + if(filterArgs.value(k_GenerationAlgorithm_Key) == 1ULL) + { + const auto pDiscreteMarkerRadiusValue = filterArgs.value(k_DiscreteMarkerRadius_Key); + if(pDiscreteMarkerRadiusValue < 1) + { + return {MakeErrorResult(-680003, fmt::format("'Discrete Marker Radius (Pixels)' must be at least 1. Value given: {}", pDiscreteMarkerRadiusValue)), {}}; + } + } + const auto* materialNamePtr = dataStructure.getDataAs(pMaterialNameArrayPathValue); if(nullptr == materialNamePtr) { @@ -252,7 +270,7 @@ IFilter::PreflightResult WritePoleFigureFilter::preflightImpl(const DataStructur std::vector preflightUpdatedValues; if(pWriteImageToDisk) { - preflightUpdatedValues.push_back({"Example Output File.", fmt::format("{}/{}Phase_1.tiff", pOutputPathValue.string(), pImagePrefixValue)}); + preflightUpdatedValues.push_back({"Example Output File.", fmt::format("{}/{}Phase_1.png", pOutputPathValue.string(), pImagePrefixValue)}); } if(pSaveIntensityPlot) @@ -296,10 +314,12 @@ Result<> WritePoleFigureFilter::executeImpl(DataStructure& dataStructure, const inputValues.GenerationAlgorithm = filterArgs.value(k_GenerationAlgorithm_Key); inputValues.LambertSize = filterArgs.value(k_LambertSize_Key); inputValues.NumColors = filterArgs.value(k_NumColors_Key); + inputValues.ImageSize = filterArgs.value(k_ImageSize_Key); + inputValues.DiscreteMarkerRadius = static_cast(filterArgs.value(k_DiscreteMarkerRadius_Key)) / static_cast(inputValues.ImageSize); inputValues.ImageLayout = filterArgs.value(k_ImageLayout_Key); inputValues.OutputPath = filterArgs.value(k_OutputPath_Key); inputValues.ImagePrefix = filterArgs.value(k_ImagePrefix_Key); - inputValues.ImageSize = filterArgs.value(k_ImageSize_Key); + inputValues.UseMask = filterArgs.value(k_UseMask_Key); inputValues.CellEulerAnglesArrayPath = filterArgs.value(k_CellEulerAnglesArrayPath_Key); inputValues.CellPhasesArrayPath = filterArgs.value(k_CellPhasesArrayPath_Key); @@ -309,6 +329,9 @@ Result<> WritePoleFigureFilter::executeImpl(DataStructure& dataStructure, const inputValues.SaveAsImageGeometry = filterArgs.value(k_SaveAsImageGeometry_Key); inputValues.WriteImageToDisk = filterArgs.value(k_WriteImageToDisk); inputValues.OutputImageGeometryPath = filterArgs.value(k_ImageGeometryPath_Key); + // Always render with the +Y axis pointing up, matching the legacy behavior where flipAndMirror was + // unconditionally applied. Not user-selectable. + inputValues.FlipFinalImage = true; inputValues.SaveIntensityData = filterArgs.value(k_SaveIntensityDataArrays); inputValues.NormalizeToMRD = filterArgs.value(k_NormalizeToMRD); @@ -340,7 +363,6 @@ constexpr StringLiteral k_TitleKey = "Title"; constexpr StringLiteral k_GenerationAlgorithmKey = "GenerationAlgorithm"; constexpr StringLiteral k_LambertSizeKey = "LambertSize"; constexpr StringLiteral k_NumColorsKey = "NumColors"; -constexpr StringLiteral k_ImageFormatKey = "ImageFormat"; constexpr StringLiteral k_ImageLayoutKey = "ImageLayout"; constexpr StringLiteral k_OutputPathKey = "OutputPath"; constexpr StringLiteral k_ImagePrefixKey = "ImagePrefix"; @@ -364,12 +386,8 @@ Result WritePoleFigureFilter::FromSIMPLJson(const nlohmann::json& jso results.push_back(SIMPLConversion::ConvertParameter(args, json, SIMPL::k_GenerationAlgorithmKey, k_GenerationAlgorithm_Key)); results.push_back(SIMPLConversion::ConvertParameter>(args, json, SIMPL::k_LambertSizeKey, k_LambertSize_Key)); results.push_back(SIMPLConversion::ConvertParameter>(args, json, SIMPL::k_NumColorsKey, k_NumColors_Key)); - Result<> formatResult = SIMPLConversion::ConvertParameter(args, json, SIMPL::k_ImageFormatKey, k_ImageFormat_Key); - if(formatResult.valid()) - { - // This parameter does not appear in 6.5, thus we only include it in the output if it's valid - results.push_back(std::move(formatResult)); - } + // The legacy ImageFormat choice (tif/bmp/png/pdf) is intentionally dropped: SIMPLNX always writes PNG. + // See vv/deviations/WritePoleFigureFilter.md D5. results.push_back(SIMPLConversion::ConvertParameter(args, json, SIMPL::k_ImageLayoutKey, k_ImageLayout_Key)); results.push_back(SIMPLConversion::ConvertParameter(args, json, SIMPL::k_OutputPathKey, k_OutputPath_Key)); results.push_back(SIMPLConversion::ConvertParameter(args, json, SIMPL::k_ImagePrefixKey, k_ImagePrefix_Key)); diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/WritePoleFigureFilter.hpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/WritePoleFigureFilter.hpp index 42485d3823..6d030f4548 100644 --- a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/WritePoleFigureFilter.hpp +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/WritePoleFigureFilter.hpp @@ -28,7 +28,7 @@ class ORIENTATIONANALYSIS_EXPORT WritePoleFigureFilter : public IFilter static constexpr StringLiteral k_GenerationAlgorithm_Key = "generation_algorithm_index"; static constexpr StringLiteral k_LambertSize_Key = "lambert_size"; static constexpr StringLiteral k_NumColors_Key = "num_colors"; - static constexpr StringLiteral k_ImageFormat_Key = "image_format"; + static constexpr StringLiteral k_DiscreteMarkerRadius_Key = "discrete_marker_radius"; static constexpr StringLiteral k_ImageLayout_Key = "image_layout_index"; static constexpr StringLiteral k_OutputPath_Key = "output_path"; static constexpr StringLiteral k_ImagePrefix_Key = "image_prefix"; diff --git a/src/Plugins/OrientationAnalysis/test/WritePoleFigureTest.cpp b/src/Plugins/OrientationAnalysis/test/WritePoleFigureTest.cpp index 09d814cd6f..c7198dd2c8 100644 --- a/src/Plugins/OrientationAnalysis/test/WritePoleFigureTest.cpp +++ b/src/Plugins/OrientationAnalysis/test/WritePoleFigureTest.cpp @@ -296,6 +296,101 @@ TEST_CASE("OrientationAnalysis::WritePoleFigureFilter: HexConvention choice reac REQUIRE(diffBytes > xa.compositeRgb.size() / 100); } +// ----------------------------------------------------------------------------- +// Discrete-mode plumbing test (simplnx-unique parameter wiring). +// +// Pins two pieces of plumbing that no other test executes: +// 1. k_GenerationAlgorithm_Key = 1 routes to the discrete (vector-marker) +// renderer — the discrete composite must differ from the Color one. +// 2. k_DiscreteMarkerRadius_Key reaches CompositePoleFigureConfiguration_t:: +// markerStyle.radiusFraction (executeImpl converts pixels to a fraction +// of the image size) — a radius of 1 px vs 10 px must change the +// rendered composite. +// Pixel-exact rendering is pinned at the EbsdLib layer (see the test-pyramid +// note at the top of this file); here we only assert the parameters actually +// reach the renderer. +// ----------------------------------------------------------------------------- +TEST_CASE("OrientationAnalysis::WritePoleFigureFilter: Discrete mode and marker radius reach algorithm", "[OrientationAnalysis][WritePoleFigureFilter]") +{ + UnitTest::LoadPlugins(); + + const nx::core::UnitTest::TestFileSentinel testDataSentinel(nx::core::unit_test::k_TestFilesDir, "Pole_Figure_Exemplars_v6.tar.gz", "Pole_Figure_Exemplars_v6"); + + auto baseDataFilePath = fs::path(fmt::format("{}/Pole_Figure_Exemplars_v6/Pole_Figure_Exemplars_v6.dream3d", unit_test::k_TestFilesDir)); + + const DataPath k_Eulers({"Imported Data", "Eulers"}); + const DataPath k_Phases({"Imported Data", "Phases"}); + const DataPath k_CrystalStructures({"EnsembleAttributeMatrix", "CrystalStructures"}); + const DataPath k_MaterialNames({"EnsembleAttributeMatrix", "PhaseNames"}); + + auto runPoleFigure = [&](ChoicesParameter::ValueType generationAlgorithm, int32 markerRadius, const std::string& geomName) { + DataStructure dataStructure = UnitTest::LoadDataStructure(baseDataFilePath); + WritePoleFigureFilter filter; + Arguments args; + args.insertOrAssign(WritePoleFigureFilter::k_Title_Key, std::make_any("Discrete Test")); + args.insertOrAssign(WritePoleFigureFilter::k_LambertSize_Key, std::make_any(64)); + args.insertOrAssign(WritePoleFigureFilter::k_NumColors_Key, std::make_any(32)); + args.insertOrAssign(WritePoleFigureFilter::k_GenerationAlgorithm_Key, std::make_any(generationAlgorithm)); + args.insertOrAssign(WritePoleFigureFilter::k_DiscreteMarkerRadius_Key, std::make_any(markerRadius)); + args.insertOrAssign(WritePoleFigureFilter::k_ImageLayout_Key, std::make_any(0)); // Horizontal + args.insertOrAssign(WritePoleFigureFilter::k_OutputPath_Key, std::make_any(fs::path(fmt::format("{}/DiscreteTestDir", unit_test::k_BinaryTestOutputDir)))); + args.insertOrAssign(WritePoleFigureFilter::k_ImagePrefix_Key, std::make_any("discrete_test_")); + args.insertOrAssign(WritePoleFigureFilter::k_ImageSize_Key, std::make_any(256)); + args.insertOrAssign(WritePoleFigureFilter::k_SaveAsImageGeometry_Key, std::make_any(true)); + args.insertOrAssign(WritePoleFigureFilter::k_WriteImageToDisk, std::make_any(false)); + args.insertOrAssign(WritePoleFigureFilter::k_UseMask_Key, std::make_any(false)); + args.insertOrAssign(WritePoleFigureFilter::k_ImageGeometryPath_Key, std::make_any(DataPath({geomName}))); + args.insertOrAssign(WritePoleFigureFilter::k_SaveIntensityDataArrays, std::make_any(false)); + args.insertOrAssign(WritePoleFigureFilter::k_CellEulerAnglesArrayPath_Key, std::make_any(k_Eulers)); + args.insertOrAssign(WritePoleFigureFilter::k_CellPhasesArrayPath_Key, std::make_any(k_Phases)); + args.insertOrAssign(WritePoleFigureFilter::k_CrystalStructuresArrayPath_Key, std::make_any(k_CrystalStructures)); + args.insertOrAssign(WritePoleFigureFilter::k_MaterialNameArrayPath_Key, std::make_any(k_MaterialNames)); + args.insertOrAssign(WritePoleFigureFilter::k_HexConvention_Key, std::make_any(0)); // X||a + + auto preflightResult = filter.preflight(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions); + auto executeResult = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result); + + const DataPath compositePath = DataPath({geomName, "Cell Data", "Phase_1"}); + const auto& compositeStore = dataStructure.getDataRefAs(compositePath).getDataStoreRef(); + std::vector snapshot(compositeStore.getSize()); + for(usize i = 0; i < compositeStore.getSize(); ++i) + { + snapshot[i] = compositeStore[i]; + } + return snapshot; + }; + + auto countDiffBytes = [](const std::vector& a, const std::vector& b) { + REQUIRE(a.size() == b.size()); + REQUIRE(a.size() > 0); + usize diffBytes = 0; + for(usize i = 0; i < a.size(); ++i) + { + if(a[i] != b[i]) + { + ++diffBytes; + } + } + return diffBytes; + }; + + const auto discreteSmall = runPoleFigure(1, 1, "Discrete_R1"); + const auto discreteLarge = runPoleFigure(1, 10, "Discrete_R10"); + const auto color = runPoleFigure(0, 3, "Color_PF"); + + // Marker radius must reach the renderer: 1 px vs 10 px markers change the image. + const usize radiusDiff = countDiffBytes(discreteSmall, discreteLarge); + INFO(fmt::format("Bytes that differ between 1 px and 10 px markers: {} / {}", radiusDiff, discreteSmall.size())); + REQUIRE(radiusDiff > discreteSmall.size() / 100); + + // Generation algorithm must reach the renderer: discrete and color composites differ. + const usize modeDiff = countDiffBytes(discreteSmall, color); + INFO(fmt::format("Bytes that differ between Discrete and Color modes: {} / {}", modeDiff, discreteSmall.size())); + REQUIRE(modeDiff > discreteSmall.size() / 100); +} + // ----------------------------------------------------------------------------- // SIMPL JSON backwards compatibility. // ----------------------------------------------------------------------------- @@ -332,10 +427,7 @@ TEST_CASE("OrientationAnalysis::WritePoleFigureFilter: SIMPL Backwards Compatibi CHECK(pipelineFilter->getComments().empty()); const Arguments args = pipelineFilter->getArguments(); - if(label == "SIMPL 6.5 (UUID)") - { - CHECK(args.value(WritePoleFigureFilter::k_ImageFormat_Key) == 0); - } + // The legacy ImageFormat choice is intentionally dropped during conversion (always PNG; deviation D5). CHECK(args.value(WritePoleFigureFilter::k_Title_Key) == "TestName"); CHECK(args.value(WritePoleFigureFilter::k_GenerationAlgorithm_Key) == 0); CHECK(args.value(WritePoleFigureFilter::k_LambertSize_Key) == 5); diff --git a/src/Plugins/OrientationAnalysis/vv/WritePoleFigureFilter.md b/src/Plugins/OrientationAnalysis/vv/WritePoleFigureFilter.md new file mode 100644 index 0000000000..d1cbf3bf92 --- /dev/null +++ b/src/Plugins/OrientationAnalysis/vv/WritePoleFigureFilter.md @@ -0,0 +1,105 @@ +# V&V Report: WritePoleFigureFilter + +| | | +|--------|--------------| +| Plugin | OrientationAnalysis | +| SIMPLNX UUID | 00cbb97e-a5c2-43e6-9a35-17a0f9ce26ed | +| DREAM3D 6.5.171 equivalent | WritePoleFigure (legacy SIMPL UUID `a10bb78e-fcff-553d-97d6-830a43c85385`) | +| Verified commit | ** | +| Status | READY FOR REVIEW | +| Sign-off | ** | + +## At a glance + +| Aspect | Current state | +|------------------------|------------------------------------------------------------------------------------------------------------------------------| +| Algorithm Relationship | **Rewrite** — same intent (generate `<001>/<011>/<111>` pole figures) but a different output medium and rendering stack: legacy writes a **PDF per phase** via libharu; SIMPLNX creates an **image geometry** (+ optional intensity arrays) and optional raster image on disk, rendered by the **EbsdLib compositor**. | +| Oracle (confirmed) | **Class 5 (Expert-visual)** primary + **Class 4 (Invariant)** companions. Expert side-by-side sign-off of hex and cubic renders (6.5.171 / 6.5.172 / SIMPLNX). Invariants encoded in `WritePoleFigureTest.cpp` (mask-effectiveness, hex-convention plumbing) pass on EbsdLib 3.1.0. | +| Code paths enumerated | 10 of 13 simplnx-wrapper paths exercised in CI; the 3 uncovered are defensive/error branches noted below. (Per-Laue-class + pixel rendering is owned and byte-tested by EbsdLib upstream.) | +| Tests today | 3 test cases — mask-effectiveness (Class 4), hex-convention plumbing (Class 4, intensity + composite paths), SIMPL 6.4/6.5 backward-compat. All pass against EbsdLib 3.1.0. | +| Exemplar archive | `Pole_Figure_Exemplars_v6.tar.gz` (inputs only — 502 hex-Ti orientations + 251/251 mask). No baked image exemplar (deliberate: avoids coupling CI to EbsdLib pixel byte-identity). | +| Legacy comparison | Three-way expert-visual (6.5.171 / 6.5.172 / SIMPLNX) on hex **and** cubic. 6.5.171 == 6.5.172 (byte-identical). Data (pole positions, intensity, color mapping) visually identical; 4 cosmetic/rendering deviations. | +| Bug flags | **None.** All 4 deviations are cosmetic (axis/family labels, font) or an intentional rendering improvement (discrete vector markers). No correctness defect. | +| V&V phase | All phases complete: oracle chosen + applied before legacy comparison, expert sign-off recorded, invariants pass on EbsdLib 3.1.0. Outstanding: second-engineer review of the Class 5 acceptance criteria. | + +## Summary + +`WritePoleFigureFilter` generates `<001>/<011>/<111>` (or the hexagonal/trigonal equivalents) pole figures for each phase from per-cell Euler angles, phases, and crystal structures, optionally masked. It was verified by expert (Class 5) side-by-side comparison of hex and cubic pole figures rendered through DREAM3D 6.5.171, 6.5.172, and SIMPLNX (EbsdLib 3.1.0), backed by Class 4 invariant unit tests for the simplnx-unique wiring. The pole-figure data is visually identical across all versions; the only differences are cosmetic labeling/font and an intentional discrete-marker rendering improvement — four documented, non-defect deviations. + +## Algorithm Relationship + +**Rewrite** + +*Evidence:* SIMPLNX inherits the legacy SIMPL UUID `a10bb78e-…`, but the implementation and output are substantially different: + +1. **Output medium** — legacy writes one **PDF per phase** to disk (libharu/HPDF); nothing enters the data structure. SIMPLNX creates an **ImageGeometry** with an RGB `Phase_N` array (and optional Float64 intensity arrays) in the DataStructure, and optionally writes a raster image (TIFF/PNG) to disk. +2. **Rendering stack** — legacy draws pole-figure chrome (circle, axes, labels, color bar) directly with libharu primitives; SIMPLNX delegates to the **EbsdLib pole-figure compositor** (`GeneratePoleFigureComposite`), whose rasterized output is byte-tested upstream in `PoleFigureCompositorTest::All_Laue_Classes`. +3. **Discrete mode** — this V&V cycle's PR renders discrete figures with EbsdLib 3.1.0's **vector-marker renderer** (configurable **Discrete Marker Radius**), replacing legacy's single-black-pixel-per-orientation. +4. **New capabilities** — optional intensity Float64 arrays, MRD normalization, in-DataStructure image geometry, and the X‖a / X‖a* Hex/Trig basis convention parameter. + +The shared pole-figure projection math (modified Lambert for Color, stereographic for Discrete) descends from the same lineage, which is why the rendered figures agree visually. Being a Rewrite under the same UUID, the Deviations file defends the equivalence claim. + +## Oracle + +*Class:* **5 (Expert-visual)** primary, **4 (Invariant)** companion. + +*Justification for Class 5:* the filter's output is a rasterized pole-figure image whose correctness is inherently visual; legacy DREAM3D emits **only PDFs** (no numeric ground truth to diff), and the pixel-level rendering is owned and byte-tested by EbsdLib upstream. No Class 1–3 oracle fully specifies the rendered image. The analytically-checkable part (pole positions) and the simplnx-unique wiring are covered by the Class 4 invariants below. + +*Applied:* the same 502 hex-Ti orientations (and a cubic-relabeled variant) were rendered through DREAM3D 6.5.171, 6.5.172, and SIMPLNX (EbsdLib 3.1.0) in Color and Discrete modes; a domain expert reviewed the figures side by side. Pole positions, intensity distribution, and color-intensity mapping are visually identical across all three; the only differences are the four cosmetic/rendering items in the Deviations file. + +*Encoded:* +- **Class 4 (Invariant):** `test/WritePoleFigureTest.cpp::"OrientationAnalysis::WritePoleFigureFilter: Mask filter changes the rendered pole figure"` (masked output differs from unmasked by >1% of bytes → mask is wired) and `::"…: HexConvention choice reaches algorithm"` (X‖a vs X‖a* rotates the basal families 30° in both the intensity array and the composite RGB → both plumbing paths honor the convention). Both pass against EbsdLib 3.1.0. +- **Class 5 (Expert-visual):** the hex + cubic renders (legacy PDFs + SIMPLNX PNGs), signed off this cycle. Generator scripts + pipelines are committed under `Code_Review/vv/WritePoleFigure/`; the binary renders are archived to OneDrive — see the provenance sidecar and that folder's `README.md`. +- **Class 2 (Reference), cited not duplicated:** EbsdLib `PoleFigureCompositorTest::All_Laue_Classes` pins per-Laue-class pixel reproduction. + +*Second-engineer review:* *Pending — recommend a second OA-domain engineer confirm the Class 5 acceptance criteria (that the four documented differences are the complete set and are all non-defect).* + +## Code path coverage + +10 of 13 simplnx-wrapper paths exercised in CI. The filter is a wrapper around the EbsdLib compositor; per-Laue-class projection and pixel rendering are owned/tested by EbsdLib. Logical phases: (a) preflight validation + array creation, (b) parameter→enum translation, (c) per-phase mask filtering, (d) intensity generation, (e) composite image generation. + +Source: `src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/WritePoleFigure.cpp` + `WritePoleFigureFilter.cpp`. + +| # | Phase | Path | Test case | +|----|--------------------|--------------------------------------------------------------------------|------------------------------------------------------------------| +| 1 | (b) Param→enum | GenerationAlgorithm = Color (0) | Mask + HexConvention tests; legacy A/B (hex + cubic) | +| 2 | (b) Param→enum | GenerationAlgorithm = Discrete (1) → vector markers | `Discrete mode and marker radius reach algorithm` (CI) + legacy A/B Discrete renders (hex + cubic), expert sign-off | +| 3 | (b) Param→enum | HexConvention X‖a (0) vs X‖a* (1) | `HexConvention choice reaches algorithm` (intensity + composite) | +| 4 | (b) Param→enum | ImageLayout (Horizontal/Vertical/Square) | *Not directly asserted; Horizontal exercised in all runs. EbsdLib layout enum — low-value to sweep here.* | +| 5 | (c) Mask filter | UseMask off / on | `Mask filter changes the rendered pole figure` | +| 6 | (c) Per-phase | per-phase Euler extraction; empty phase → skip | Single-phase fixtures exercise extraction. *Empty-phase skip not directly tested — defensive guard.* | +| 7 | (d) Intensity | SaveIntensityDataArrays on + NormalizeToMRD | `HexConvention choice reaches algorithm` (SaveIntensity=true, MRD=true) | +| 8 | (d) Intensity | crystal-structure dispatch (Cubic_High, Hexagonal_High) | Hex: tests + A/B; Cubic: A/B. Other Laue classes owned by EbsdLib upstream. | +| 9 | (d) Intensity | unknown crystal structure → warning, skip | *Not directly tested — defensive warning branch.* | +| 10 | (e) Composite | SaveAsImageGeometry / WriteImageToDisk; DiscreteMarkerRadius | `Discrete mode and marker radius reach algorithm` (1 px vs 10 px composites differ); A/B renders (write-to-disk); Mask/HexConv tests (image geometry) | +| 11 | (a) Preflight | mask array wrong type → error `-53900` | *Not directly tested — low-value validation branch.* | +| 12 | (a) Preflight | ImageSize ≤ 0 → error `-680002`; Discrete mode with marker radius < 1 → error `-680003` | *Guards added during review; not directly tested — low-value validation branches.* | +| 13 | (h) SIMPL convert | 6.4 / 6.5 SIMPL JSON → Arguments (legacy ImageFormat dropped, D5) | `SIMPL Backwards Compatibility` | + +## Test inventory + +| Test case | Status | Notes | +|-----------|--------|-------| +| `Mask filter changes the rendered pole figure` | kept | Class 4 invariant: masked vs unmasked composite RGB differ by >1% of bytes. Consumes `Pole_Figure_Exemplars_v6`. Passes on EbsdLib 3.1.0. | +| `HexConvention choice reaches algorithm` | kept | Class 4 invariant: X‖a vs X‖a* differ in both the intensity array and the composite RGB (both plumbing paths). Passes on EbsdLib 3.1.0. | +| `Discrete mode and marker radius reach algorithm` | new-for-V&V | Class 4 invariant: Discrete vs Color composites differ; 1 px vs 10 px marker radii differ. Pins the GenerationAlgorithm and DiscreteMarkerRadius plumbing in CI (previously covered only by manual A/B renders). | +| `SIMPL Backwards Compatibility` | kept | 6.4 + 6.5 SIMPL JSON → Arguments round-trip. The legacy `ImageFormat` key is intentionally not converted (D5). | + +## Exemplar archive + +- **Archive:** `Pole_Figure_Exemplars_v6.tar.gz` (inputs only — 502 hex-Ti orientations + 251/251 mask + crystal structures + phase names). +- **SHA512:** *(see `test/CMakeLists.txt` `download_test_data(... Pole_Figure_Exemplars_v6.tar.gz ...)`)* +- **Provenance:** `src/Plugins/OrientationAnalysis/vv/provenance/WritePoleFigureFilter.md` +- No baked image exemplar: pixel-level reproduction is owned by EbsdLib upstream; duplicating it here couples simplnx CI to EbsdLib byte-identity (the source of prior v5 baseline drift). + +## Deviations from DREAM3D 6.5.171 + +Established by expert (Class 5) visual comparison on hex and cubic; 6.5.171 == 6.5.172. Full renders archived to OneDrive; regeneration scripts committed under `Code_Review/vv/WritePoleFigure/`. All four are cosmetic / labeling / intentional-rendering; none is a defect. + +- `WritePoleFigureFilter-D1` — axis labels `X`/`Y` (legacy) → `A1`/`A2` (SIMPLNX). Cosmetic. +- `WritePoleFigureFilter-D2` — font/text-metrics differ (libharu Helvetica → EbsdLib canvas_ity). Library, cosmetic. +- `WritePoleFigureFilter-D3` — hex/trig per-figure family labels differ but name symmetry-equivalent families. Cosmetic. +- `WritePoleFigureFilter-D4` — Discrete markers: filled circles (configurable radius) → intentional rendering improvement over legacy single black pixels. +- `WritePoleFigureFilter-D5` — legacy Image Format choice (tif/bmp/png/pdf) dropped; SIMPLNX always writes PNG. The legacy `ImageFormat` key is not converted from SIMPL JSON. + +See `vv/deviations/WritePoleFigureFilter.md`. diff --git a/src/Plugins/OrientationAnalysis/vv/deviations/WritePoleFigureFilter.md b/src/Plugins/OrientationAnalysis/vv/deviations/WritePoleFigureFilter.md new file mode 100644 index 0000000000..81ccef1c81 --- /dev/null +++ b/src/Plugins/OrientationAnalysis/vv/deviations/WritePoleFigureFilter.md @@ -0,0 +1,97 @@ +# Deviations from DREAM3D 6.5.171: WritePoleFigureFilter + +This file lists every documented behavioral difference between this SIMPLNX filter and its DREAM3D 6.5.171 equivalent. + +Entries are referenced by stable ID (`WritePoleFigureFilter-D`) from the V&V report and from public migration guidance. The ID is stable across renames; the Filter UUID field is the permanent cross-reference anchor. + +All entries below were established by an expert (Class 5) side-by-side review of pole figures rendered from the same 502 hex-Ti orientations through DREAM3D 6.5.171, DREAM3D 6.5.172, and SIMPLNX (EbsdLib 3.1.0). 6.5.171 and 6.5.172 outputs are byte-identical. The pole-figure **data** (pole positions, intensity distribution, color-intensity mapping) is visually identical across all three; the only differences are the cosmetic / labeling / rendering items below. None is a correctness defect. + +--- + +## WritePoleFigureFilter-D1 + +| Field | Value | +|---|---| +| **Deviation ID** | `WritePoleFigureFilter-D1` | +| **Filter UUID** | `00cbb97e-a5c2-43e6-9a35-17a0f9ce26ed` | +| **Status** | active | + +**Symptom:** The in-plane axis annotations on each pole figure differ: legacy DREAM3D labels them **`X` / `Y`**; SIMPLNX labels them **`A1` / `A2`**. + +**Root cause:** Algorithmic choice (cosmetic relabeling in the EbsdLib 3.1.0 pole-figure chrome). The axes denote the same sample-frame directions; only the text label changed. + +**Affected users:** Anyone visually comparing a SIMPLNX pole figure image to a legacy one. No effect on data or pole positions. + +**Recommendation:** Trust SIMPLNX. Purely a label change; the axes are the same sample directions. + +--- + +## WritePoleFigureFilter-D2 + +| Field | Value | +|---|---| +| **Deviation ID** | `WritePoleFigureFilter-D2` | +| **Filter UUID** | `00cbb97e-a5c2-43e6-9a35-17a0f9ce26ed` | +| **Status** | active | + +**Symptom:** Text in the rendered image (title, labels, legend, color-bar numbers) is drawn in a slightly different font / metrics than legacy. + +**Root cause:** Library. Legacy rasterizes text into a PDF via libharu (Helvetica); SIMPLNX rasterizes via EbsdLib's `canvas_ity` text renderer into a raster image. Different text stack → different glyph shapes and spacing. + +**Affected users:** Anyone doing a pixel-level or visual font comparison. No effect on data, pole positions, or numeric legend values. + +**Recommendation:** Either acceptable. The text content is the same; only the typeface/metrics differ. This is inherent to the PDF→raster output-medium change (see report Algorithm Relationship: Rewrite). + +--- + +## WritePoleFigureFilter-D3 + +| Field | Value | +|---|---| +| **Deviation ID** | `WritePoleFigureFilter-D3` | +| **Filter UUID** | `00cbb97e-a5c2-43e6-9a35-17a0f9ce26ed` | +| **Status** | active | + +**Symptom:** The per-pole-figure title labels (the Miller-index family printed above each of the three figures) differ between legacy and SIMPLNX for hexagonal/trigonal phases. + +**Root cause:** Algorithmic choice / library (label convention). The labels name **symmetrically equivalent** plane/direction families (e.g. the two basal families), so the labeled families are crystallographically equivalent and the rendered pole figure is the same. Only the chosen representative label string differs. + +**Affected users:** Hexagonal/trigonal users reading the family label text. No effect on the plotted poles. + +**Recommendation:** Trust SIMPLNX. The labels denote symmetry-equivalent families; the figures are identical. + +--- + +## WritePoleFigureFilter-D4 + +| Field | Value | +|---|---| +| **Deviation ID** | `WritePoleFigureFilter-D4` | +| **Filter UUID** | `00cbb97e-a5c2-43e6-9a35-17a0f9ce26ed` | +| **Status** | active | + +**Symptom:** In **Discrete** pole figures, each plotted orientation is drawn as a small filled **circle** (configurable radius) in SIMPLNX, whereas legacy DREAM3D flipped a single pixel to black per orientation. + +**Root cause:** Algorithmic choice — a deliberate rendering improvement introduced with EbsdLib 3.1.0's vector-marker renderer (`GeneratePoleFigureComposite` routes discrete, non-heatmap figures to the marker renderer; the new **Discrete Marker Radius** parameter controls the marker size). The underlying pole positions are unchanged; only the per-point glyph is larger and anti-aliased. + +**Affected users:** Users of the Discrete pole-figure mode. Markers are more visible than legacy single pixels (the legacy points were often nearly invisible at typical image sizes). Pole positions are identical. + +**Recommendation:** Trust SIMPLNX. This is an intentional, user-controllable visibility improvement; set **Discrete Marker Radius** to taste. No data change. + +--- + +## WritePoleFigureFilter-D5 + +| Field | Value | +|---|---| +| **Deviation ID** | `WritePoleFigureFilter-D5` | +| **Filter UUID** | `00cbb97e-a5c2-43e6-9a35-17a0f9ce26ed` | +| **Status** | active | + +**Symptom:** Legacy DREAM3D offered an **Image Format** choice (tif/bmp/png/pdf) for the on-disk pole figure file; SIMPLNX has no such parameter and always writes a **PNG** file. + +**Root cause:** Algorithmic choice (API simplification). The SIMPLNX rewrite renders through EbsdLib's raster compositor and writes the composite with `PngWriter`; the legacy multi-format option was not carried over. The legacy `ImageFormat` key is dropped during SIMPL pipeline conversion. + +**Affected users:** Anyone whose legacy pipeline selected a non-PNG output format, or whose downstream tooling expects a `.tif`/`.pdf` file extension. The image content is the same composite; only the container format is fixed to PNG. + +**Recommendation:** Trust SIMPLNX. PNG is lossless and universally readable; convert externally if another container is required. diff --git a/src/Plugins/OrientationAnalysis/vv/provenance/WritePoleFigureFilter.md b/src/Plugins/OrientationAnalysis/vv/provenance/WritePoleFigureFilter.md new file mode 100644 index 0000000000..219ed2d10e --- /dev/null +++ b/src/Plugins/OrientationAnalysis/vv/provenance/WritePoleFigureFilter.md @@ -0,0 +1,51 @@ +# Exemplar Archive Provenance: Pole_Figure_Exemplars_v6.tar.gz + +This sidecar records the exemplar archive and the Class 5 (expert-visual) oracle used to verify `WritePoleFigureFilter`. + +--- + +## Archive identity + +| Field | Value | +|---|---| +| **Archive** | `Pole_Figure_Exemplars_v6.tar.gz` | +| **SHA512** | `23ad8853c38c66f498c7a9c43d6152419506b2bcb6c7e4854728dd3ad81efd309541981bf703ad9b0a1e9b506a0098da918599d5209cb06353281d9cecf39223` | +| **Used by tests** | `WritePoleFigureFilter: Mask filter changes the rendered pole figure`, `WritePoleFigureFilter: HexConvention choice reaches algorithm`, `WritePoleFigureFilter: Discrete mode and marker radius reach algorithm` | +| **Generated by** | BlueQuartz (existing exemplar; inputs only) | +| **Generated on** | pre-existing (consumed as-is for this V&V) | +| **Generated at commit** | n/a (inputs-only archive) | + +## How it was generated + +The archive contains **inputs only**: a single-phase set of 502 hexagonal (Ti, Laue class 6/mmm) orientations under `Imported Data/Eulers` (radians), `Imported Data/Phases`, a 251-of-502 `Imported Data/Mask`, and `EnsembleAttributeMatrix/CrystalStructures` + `PhaseNames`. There is **no baked pole-figure image** used as an oracle — pixel-level reproduction is owned and byte-tested by EbsdLib upstream (`PoleFigureCompositorTest::All_Laue_Classes`), and baking an image here would couple simplnx CI to EbsdLib byte-identity (the prior v5 baseline-drift failure mode). + +## Canonical oracle output + +There is no gold-standard array in the archive. Correctness of the rendered pole figures is established by **Class 5 expert-visual** sign-off (below) plus **Class 4 invariants** encoded in the test source: + +| Output | Source of expected values | +|---|---| +| Composite RGB `Phase_N` image | Class 5 expert-visual sign-off vs legacy DREAM3D renders (hex + cubic); Class 2 pixel reproduction owned by EbsdLib `PoleFigureCompositorTest`. | +| Intensity arrays `Phase_N_` | Class 4 invariants (mask changes output; X‖a vs X‖a* rotates basal families 30°). | + +## Oracle provenance — Class 5 (Expert-visual) + +- **Approving expert:** Michael Jackson (OA domain expert) +- **Approval date:** 2026-07-01 +- **What was compared:** the same orientation input rendered through DREAM3D **6.5.171**, DREAM3D **6.5.172**, and **SIMPLNX (EbsdLib 3.1.0)**, in Color and Discrete modes, for **two crystal systems**: + - Hexagonal (the v6 archive's 502 Ti orientations) + - Cubic (the same 502 orientations relabeled `Cubic_High`, to exercise the `<001>/<011>/<111>` families and cubic Laue ops) +- **Signed-off outputs:** the full three-way set (6.5.171 / 6.5.172 / NX × color/discrete × hex/cubic) — hex: `out_legacy_6_5_171/{color,discrete}_Phase_1.pdf`, `out_legacy_6_5_172/…` (byte-identical to 6.5.171), `out_nx/nx_{color,discrete}_1.png`; cubic: `out_cubic_legacy_6_5_171/`, `out_cubic_legacy_6_5_172/` (color differs only in a trailing PDF metadata block; discrete byte-identical), `out_cubic_nx/`. +- **Evidence archival:** regenerated 2026-07-08. The **generator scripts, pipeline files, and a `README.md` are committed in-repo** under `Code_Review/vv/WritePoleFigure/`; the **binary evidence (input `.dream3d` files and the rendered PDFs/PNGs) is archived to OneDrive**, not committed (repo hygiene — see that folder's `.gitignore`). The comparison is fully reproducible from the committed scripts. The NX renders were produced by an `nxrunner` built from this branch (`topic/write_pole_figure_fixes`), so the discrete renders honor the `discrete_marker_radius` parameter. **Reconstruction note:** the legacy-format input (`pf_input_legacy.dream3d`) was re-minted by `build_legacy_input.py` (the original minting script was unrecoverable) from the NX inputs' 502 orientations, using the SIMPL v7.0 layout; both legacy engines read it and rendered valid pole figures, validating the reconstruction. +- **Acceptance criteria / finding:** the pole-figure **data** (pole positions, intensity distribution, color-intensity mapping) is visually identical across all three versions for both crystal systems. The only differences are the four documented, non-defect deviations (`WritePoleFigureFilter-D1..D4`: axis-label text, font, symmetry-equivalent family labels, and the intentional discrete vector-marker rendering). No correctness defect observed. +- **Class-5 justification:** legacy DREAM3D emits pole figures only as rasterized PDFs (no numeric ground truth), and per-Laue-class pixel rendering is owned/byte-tested by EbsdLib upstream, so no Class 1–3 oracle fully specifies the rendered image. The analytically-checkable wiring (mask, hex convention) is pinned by Class 4 invariants. + +## Second-engineer oracle review + +- **Reviewer:** *pending* +- **Date:** *pending* +- **Note:** recommend a second OA-domain engineer confirm the four documented differences are the complete set and all non-defect. + +## Regenerated to fix a circular-oracle situation? + +No. The archive is inputs-only and pre-existing; no baked filter output is used as an oracle, so there is no circular-oracle situation to remediate. The legacy comparison input was authored independently — minted by `build_legacy_input.py` (image geometry + `MaterialName` StringDataArray, SIMPL v7.0 layout) from the same 502 orientations — not derived from any filter output. diff --git a/vcpkg-configuration.json b/vcpkg-configuration.json index f25ec36eab..bcb0fc0334 100644 --- a/vcpkg-configuration.json +++ b/vcpkg-configuration.json @@ -8,7 +8,7 @@ { "kind": "git", "repository": "https://github.com/bluequartzsoftware/simplnx-registry", - "baseline": "a2db0689b5c44eaa1941bb6ecad02bdc437a0c02", + "baseline": "8e5010ce1bac7cb2a2a8722944913e4a56884439", "packages": [ "benchmark", "blosc", diff --git a/vcpkg.json b/vcpkg.json index 6996e287ca..9477e819d7 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -83,7 +83,7 @@ "dependencies": [ { "name": "ebsdlib", - "version>=": "3.0.0" + "version>=": "3.1.0" } ] },