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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions hexrdgui/hexrd_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -1429,10 +1429,15 @@ def recursive_key_check(d: dict[str, Any], c: dict[str, Any]) -> None:
'reset_exclusions': False,
}

if self.instrument_has_roi:
names = self.detector_group_names
else:
names = self.detector_names

data = []
for det in self.detector_names:
for name in names:
data.append(
{'file': f'{det}.npz', 'args': {'path': 'imageseries'}, 'panel': det}
{'file': f'{name}.npz', 'args': {'path': 'imageseries'}, 'panel': name}
)

image_series = {'format': 'frame-cache', 'data': data}
Expand Down
92 changes: 88 additions & 4 deletions hexrdgui/indexing/fit_grains_results_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
if TYPE_CHECKING:
from matplotlib.backend_bases import PickEvent
from matplotlib.colors import Colormap
from hexrd.core.imageseries.imageseriesabc import ImageSeriesABC
from hexrd.material import Material

from hexrd.matrixutil import vecMVToSymm
Expand Down Expand Up @@ -726,20 +727,29 @@ def full_path(file_name: str) -> str:
# Convenience function to generate full path via pathlib
return str(Path(selected_directory) / file_name)

has_roi = HexrdConfig().instrument_has_roi

HexrdConfig().working_dir = selected_directory

HexrdConfig().save_indexing_config(full_path('workflow.yml'))
HexrdConfig().save_materials_hdf5(full_path('materials.h5'))
HexrdConfig().save_instrument_config(
full_path('instrument.hexrd'),
# Remove ROIs, since we are saving the imageseries without them
remove_rois=True,
# Keep ROIs when groups exist so exported workflow can be
# re-used with original monolithic frame-cache files
remove_rois=not has_roi,
)

if has_roi:
self._save_group_images(selected_directory)
else:
self._save_detector_images(selected_directory)

def _save_detector_images(self, selected_directory: str) -> None:
ims_dict = HexrdConfig().unagg_images
assert ims_dict is not None
for det in HexrdConfig().detector_names:
path = full_path(f'{det}.npz')
path = str(Path(selected_directory) / f'{det}.npz')
kwargs: dict[str, Any] = {
'ims': ims_dict.get(det),
'name': det,
Expand All @@ -750,6 +760,27 @@ def full_path(file_name: str) -> str:
}
HexrdConfig().save_imageseries(**kwargs)

def _save_group_images(self, selected_directory: str) -> None:
ims_dict = HexrdConfig().unagg_images
assert ims_dict is not None
for group in HexrdConfig().detector_group_names:
# Get first subpanel in group to access its underlying image
first_det = HexrdConfig().detectors_in_group(group)[0]
subpanel_ims = ims_dict[first_det]

# Get the monolithic image (unwrap rectangle op)
monolithic_ims = _get_monolithic_ims(subpanel_ims)

path = str(Path(selected_directory) / f'{group}.npz')
HexrdConfig().save_imageseries(
ims=monolithic_ims,
name=group,
write_file=path,
selected_format='frame-cache',
cache_file=path,
threshold=0,
)

def on_export_workflow_clicked(self) -> None:
selected_directory = QFileDialog.getExistingDirectory(
self.ui, 'Select Directory', HexrdConfig().working_dir
Expand All @@ -760,11 +791,16 @@ def on_export_workflow_clicked(self) -> None:
return

# Warn the user if any files will be over-written
if HexrdConfig().instrument_has_roi:
image_names = HexrdConfig().detector_group_names
else:
image_names = HexrdConfig().detector_names

write_files = [
'workflow.yml',
'materials.h5',
'instrument.hexrd',
] + [f'{det}.npz' for det in HexrdConfig().detector_names]
] + [f'{name}.npz' for name in image_names]

overwrite_files = []
for f in write_files:
Expand All @@ -784,6 +820,54 @@ def on_export_workflow_clicked(self) -> None:
self.async_runner.run(self._save_workflow_files, selected_directory)


def _get_monolithic_ims(
subpanel_ims: ImageSeriesABC,
) -> ImageSeriesABC:
"""Get monolithic image from a subpanel's image series.

Recursively unwraps the image series chain, collecting all
non-rectangle operations and frame_list selections along the way.
The result is a single image series rooted at the base adapter
with all non-rectangle processing preserved.

The chain may contain arbitrary nesting of:
- ``ImageSeries`` / ``OmegaImageSeries`` (use ``_adapter``)
- ``ProcessedImageSeries`` (use ``_imser``, hold ``_oplist``)
"""
from hexrd.core.imageseries.process import ProcessedImageSeries

# Walk the full chain, collecting non-rectangle ops and frame_lists.
non_rect_ops: list = []
frame_list: list[int] | None = None
ims: ImageSeriesABC = subpanel_ims

while True:
if isinstance(ims, ProcessedImageSeries):
# Collect ops (excluding rectangle) and frame_list
non_rect_ops.extend(
op for op in ims._oplist if op[0] != 'rectangle'
)
if frame_list is None and ims._hasframelist:
frame_list = list(ims._frames)
ims = ims._imser
elif hasattr(ims, '_adapter'):
ims = ims._adapter
else:
# Reached the base adapter — stop.
break

# ims is now the root image series (e.g. FrameCacheImageSeriesAdapter
# wrapped in ImageSeries). Rebuild with only the collected ops.
if not non_rect_ops and frame_list is None:
return ims

kwargs: dict = {}
if frame_list is not None:
kwargs['frame_list'] = frame_list

return ProcessedImageSeries(ims, non_rect_ops, **kwargs)


if __name__ == '__main__':
from PySide6.QtCore import QCoreApplication
from PySide6.QtWidgets import QApplication
Expand Down
Loading