diff --git a/doc/source/example/camera_calibration/config.rst b/doc/source/example/camera_calibration/config.rst new file mode 100644 index 000000000..0b529a1a8 --- /dev/null +++ b/doc/source/example/camera_calibration/config.rst @@ -0,0 +1,8 @@ +View Camera Distortion +====================== + +.. currentmodule:: example.camera_calibration.example_view_camera_distortion + +.. automodule:: example.camera_calibration.example_view_camera_distortion + :members: + :show-inheritance: diff --git a/doc/source/example/camera_calibration/index.rst b/doc/source/example/camera_calibration/index.rst new file mode 100644 index 000000000..85f588a20 --- /dev/null +++ b/doc/source/example/camera_calibration/index.rst @@ -0,0 +1,11 @@ +Camera Calibration +================== + +These are example files that provide examples on how to use OpenCSP for camera lens calibration. + + + +.. toctree:: + :maxdepth: 1 + + config.rst \ No newline at end of file diff --git a/doc/source/example/index.rst b/doc/source/example/index.rst index f2de14a32..065c1fbb4 100644 --- a/doc/source/example/index.rst +++ b/doc/source/example/index.rst @@ -6,6 +6,7 @@ This section describes the OpenCSP examples. .. toctree:: :maxdepth: 1 + camera_calibration/index.rst csp/index.rst mirror/index.rst scene_reconstruction/index.rst diff --git a/doc/source/example/sofast/config.rst b/doc/source/example/sofast/config.rst index 10529b6f8..67f1913c5 100644 --- a/doc/source/example/sofast/config.rst +++ b/doc/source/example/sofast/config.rst @@ -17,11 +17,3 @@ Undefined Facet Data Process :members: :show-inheritance: -View Camera Distortion -====================== - -.. currentmodule:: example.sofast_fringe.example_calibration_camera_pose - -.. automodule:: example.sofast_fringe.example_calibration_camera_pose - :members: - :show-inheritance: diff --git a/doc/source/example/sofast/index.rst b/doc/source/example/sofast/index.rst index 57aafcc25..bf1af56e3 100644 --- a/doc/source/example/sofast/index.rst +++ b/doc/source/example/sofast/index.rst @@ -1,7 +1,7 @@ -SoFast -====== +SOFAST Fringe +============= -These are example files that provide examples on how to use Sofast to collect data and process already collected data. +These are example files that provide examples on how to use Sofast Fringe to process collected data and perform system calibrations. diff --git a/doc/source/library_reference/app/sofast/calibration/config.rst b/doc/source/library_reference/app/sofast/calibration/config.rst deleted file mode 100644 index 6470f9364..000000000 --- a/doc/source/library_reference/app/sofast/calibration/config.rst +++ /dev/null @@ -1,19 +0,0 @@ -opencsp.app.sofast.lib.CalibrateDisplayShape -============================================================= - -.. currentmodule:: opencsp.app.sofast.lib.CalibrateDisplayShape - -.. automodule:: opencsp.app.sofast.lib.CalibrateDisplayShape - :members: - :undoc-members: - :show-inheritance: - -opencsp.app.sofast.lib.save_DisplayShape_file -=============================================================== - -.. currentmodule:: opencsp.app.sofast.lib.save_DisplayShape_file - -.. automodule:: opencsp.app.sofast.lib.save_DisplayShape_file - :members: - :undoc-members: - :show-inheritance: \ No newline at end of file diff --git a/doc/source/library_reference/app/sofast/calibration/index.rst b/doc/source/library_reference/app/sofast/calibration/index.rst deleted file mode 100644 index b5f060b40..000000000 --- a/doc/source/library_reference/app/sofast/calibration/index.rst +++ /dev/null @@ -1,10 +0,0 @@ -Calibration -=========== - -Library of photogrammetric algorithms used for SOFAST calibration. - - -.. toctree:: - :maxdepth: 1 - - config.rst \ No newline at end of file diff --git a/doc/source/library_reference/app/sofast/config.rst b/doc/source/library_reference/app/sofast/config.rst index 4ff735cb3..0eba6c6da 100644 --- a/doc/source/library_reference/app/sofast/config.rst +++ b/doc/source/library_reference/app/sofast/config.rst @@ -116,4 +116,14 @@ opencsp.app.sofast.SofastGUI .. automodule:: opencsp.app.sofast.SofastGUI :members: :undoc-members: - :show-inheritance: \ No newline at end of file + :show-inheritance: + +opencsp.app.sofast.lib.CalibrateDisplayShape +============================================================= + +.. currentmodule:: opencsp.app.sofast.lib.CalibrateDisplayShape + +.. automodule:: opencsp.app.sofast.lib.CalibrateDisplayShape + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/source/library_reference/app/sofast/index.rst b/doc/source/library_reference/app/sofast/index.rst index e389f546d..a74c1cdd9 100644 --- a/doc/source/library_reference/app/sofast/index.rst +++ b/doc/source/library_reference/app/sofast/index.rst @@ -1,4 +1,4 @@ -sofast +SOFAST ====== High-precision deflectometry measurement of CSP mirrors, including heliostat facets, full heliostats, and dishes. @@ -7,4 +7,3 @@ High-precision deflectometry measurement of CSP mirrors, including heliostat fac :maxdepth: 1 config.rst - calibration/index.rst \ No newline at end of file diff --git a/example/camera_calibration/example_view_camera_distortion.py b/example/camera_calibration/example_view_camera_distortion.py index 2476ee561..1120ffd79 100644 --- a/example/camera_calibration/example_view_camera_distortion.py +++ b/example/camera_calibration/example_view_camera_distortion.py @@ -7,7 +7,7 @@ from opencsp.common.lib.opencsp_path.opencsp_root_path import opencsp_code_dir -def example_driver(): +def example_show_camera_distortion(): """Example SOFAST script Plots visualization of camera distortion given a saved Camera HDF file @@ -38,4 +38,4 @@ def example_driver(): if __name__ == '__main__': - example_driver() + example_show_camera_distortion() diff --git a/example/sofast_fringe/example_calibration_save_DisplayShape_file.py b/example/sofast_fringe/example_calibration_save_DisplayShape_file.py deleted file mode 100644 index 30a071e62..000000000 --- a/example/sofast_fringe/example_calibration_save_DisplayShape_file.py +++ /dev/null @@ -1,45 +0,0 @@ -from os.path import join, dirname - -import numpy as np - -from opencsp.app.sofast.lib.save_DisplayShape_file import save_DisplayShape_file -from opencsp.common.lib.geometry.Vxy import Vxy -from opencsp.common.lib.geometry.Vxyz import Vxyz -from opencsp.common.lib.opencsp_path.opencsp_root_path import opencsp_code_dir -from opencsp.common.lib.tool.hdf5_tools import load_hdf5_datasets -import opencsp.common.lib.tool.file_tools as ft - - -def example_save_display_shape_file(): - """Example script that saves a DisplayShape file from its components""" - # Define save directory - save_dir = join(dirname(__file__), 'data/output/save_DisplayShape_file') - ft.create_directories_if_necessary(save_dir) - - # Load screen distortion data - file_screen_distortion_data = join( - opencsp_code_dir(), 'app/sofast/test/data/data_expected', 'screen_distortion_data_100_100.h5' - ) - datasets = ['pts_xy_screen_fraction', 'pts_xyz_screen_coords'] - data = load_hdf5_datasets(datasets, file_screen_distortion_data) - screen_distortion_data = { - 'pts_xy_screen_fraction': Vxy(data['pts_xy_screen_fraction']), - 'pts_xyz_screen_coords': Vxyz(data['pts_xyz_screen_coords']), - } - - # Load rvec and tvec - file_rvec_tvec = join( - opencsp_code_dir(), 'common/lib/deflectometry/test/data/data_expected', 'camera_rvec_tvec.csv' - ) - pose_data = np.loadtxt(file_rvec_tvec, delimiter=',') - rvec = pose_data[0] - tvec = pose_data[1] - - # Save DisplayShape file - name = 'Example physical setup file' - file_save = join(save_dir, 'example_display_shape_file.h5') - save_DisplayShape_file(screen_distortion_data, name, rvec, tvec, file_save) - - -if __name__ == '__main__': - example_save_display_shape_file() diff --git a/example/sofast_fringe/example_calibration_screen_shape.py b/example/sofast_fringe/example_calibration_screen_shape.py index 28b910939..c6fdb3b33 100644 --- a/example/sofast_fringe/example_calibration_screen_shape.py +++ b/example/sofast_fringe/example_calibration_screen_shape.py @@ -14,7 +14,9 @@ def run_screen_shape_calibration(save_dir): - """Runs screen shape calibration. Saves data to ./data/output/screen_shape""" + """Runs screen shape calibration. Saves a DisplayShape HDF5 file + to ./data/output/screen_shape/display_shape.h5 + """ # Load output data from Scene Reconstruction (Aruco marker xyz points) file_pts_data = join( opencsp_code_dir(), 'common/lib/deflectometry/test/data/data_measurement', 'point_locations.csv' @@ -56,8 +58,13 @@ def run_screen_shape_calibration(save_dir): cal.make_figures = True cal.run_calibration() - # Save screen shape data as HDF5 file - cal.save_data_as_hdf(join(save_dir, 'screen_distortion_data.h5')) + # Get screen shape data + display_shape = cal.as_DisplayShape('Example display shape') + + # Save DisplayShape file + file = join(save_dir, 'display_shape.h5') + display_shape.save_to_hdf(file) + lt.info(f'Saved DisplayShape file to {file:s}') # Save calibration figures for fig in cal.figures: diff --git a/example/sofast_fringe/example_calibration_camera_pose.py b/example/sofast_fringe/example_calibration_spatial_orientation.py similarity index 74% rename from example/sofast_fringe/example_calibration_camera_pose.py rename to example/sofast_fringe/example_calibration_spatial_orientation.py index 8ee45e296..0231f4077 100644 --- a/example/sofast_fringe/example_calibration_camera_pose.py +++ b/example/sofast_fringe/example_calibration_spatial_orientation.py @@ -1,7 +1,9 @@ from os.path import join, dirname import numpy as np +from scipy.spatial.transform import Rotation +from opencsp.app.sofast.lib.SpatialOrientation import SpatialOrientation from opencsp.common.lib.deflectometry.CalibrationCameraPosition import CalibrationCameraPosition from opencsp.common.lib.camera.Camera import Camera from opencsp.common.lib.geometry.Vxyz import Vxyz @@ -13,8 +15,8 @@ def run_camera_position_calibration(save_dir): """Calibrates the position of the Sofast camera. Saves the rvec/tvec that - define the relative pose of the camera/screen to a CSV file located - at ./data/output/camera_rvec_tvec.csv + define the relative pose of the camera/screen in a SpatialOrientation file + at ./data/output/spatial_orientation.h5 """ # Define directory where screen shape calibration data is saved base_dir_sofast_cal = join(opencsp_code_dir(), 'common/lib/deflectometry/test/data/data_measurement') @@ -38,14 +40,26 @@ def run_camera_position_calibration(save_dir): cal.make_figures = True cal.run_calibration() + # Get orientation + r_screen_cam, v_cam_screen_screen = cal.get_data() + r_screen_cam = Rotation.from_rotvec(r_screen_cam) + v_cam_screen_screen = Vxyz(v_cam_screen_screen) + + r_cam_screen = r_screen_cam.inv() + v_cam_screen_cam = v_cam_screen_screen.rotate(r_screen_cam) + + # Create spatial orientation object + orientation = SpatialOrientation(r_cam_screen, v_cam_screen_cam) + + # Save data + orientation.save_to_hdf(join(save_dir, 'spatial_orientation.h5')) + + # Save figures for fig in cal.figures: file = join(save_dir, fig.get_label() + '.png') lt.info(f'Saving figure to: {file:s}') fig.savefig(file) - # Save data - cal.save_data_as_csv(join(save_dir, 'camera_rvec_tvec.csv')) - def example_driver(): # Define save dir diff --git a/example/sofast_fringe/example_process_facet_ensemble.py b/example/sofast_fringe/example_process_facet_ensemble.py index fc6a02aa8..65bb73fe0 100644 --- a/example/sofast_fringe/example_process_facet_ensemble.py +++ b/example/sofast_fringe/example_process_facet_ensemble.py @@ -1,7 +1,5 @@ from os.path import join, dirname -import matplotlib - from opencsp.app.sofast.lib.DisplayShape import DisplayShape as Display from opencsp.app.sofast.lib.DefinitionEnsemble import DefinitionEnsemble from opencsp.app.sofast.lib.DefinitionFacet import DefinitionFacet @@ -9,7 +7,6 @@ from opencsp.app.sofast.lib.MeasurementSofastFringe import MeasurementSofastFringe from opencsp.app.sofast.lib.ProcessSofastFringe import ProcessSofastFringe as Sofast from opencsp.app.sofast.lib.SpatialOrientation import SpatialOrientation -from opencsp.app.sofast.lib.visualize_setup import visualize_setup from opencsp.common.lib.camera.Camera import Camera from opencsp.common.lib.csp.FacetEnsemble import FacetEnsemble from opencsp.common.lib.deflectometry.Surface2DParabolic import Surface2DParabolic @@ -39,6 +36,7 @@ def example(dir_save: str): file_measurement = join(sample_data_dir, 'measurement_ensemble.h5') file_camera = join(sample_data_dir, 'camera.h5') file_display = join(sample_data_dir, 'display_distorted_2d.h5') + file_orientation = join(sample_data_dir, 'spatial_orientation.h5') file_calibration = join(sample_data_dir, 'image_calibration.h5') file_facet = join(sample_data_dir, 'Facet_lab_6x4.json') file_ensemble = join(sample_data_dir, 'Ensemble_lab_6x4.json') @@ -46,6 +44,7 @@ def example(dir_save: str): # Load data camera = Camera.load_from_hdf(file_camera) display = Display.load_from_hdf(file_display) + orientation = SpatialOrientation.load_from_hdf(file_orientation) measurement = MeasurementSofastFringe.load_from_hdf(file_measurement) calibration = ImageCalibrationScaling.load_from_hdf(file_calibration) ensemble_data = DefinitionEnsemble.load_from_json(file_ensemble) @@ -62,7 +61,7 @@ def example(dir_save: str): measurement.calibrate_fringe_images(calibration) # Instantiate sofast object - sofast = Sofast(measurement, camera, display) + sofast = Sofast(measurement, orientation, camera, display) # Update search parameters sofast.params.mask_hist_thresh = 0.83 @@ -88,12 +87,6 @@ def example(dir_save: str): mirror_control = rcm.RenderControlMirror(centroid=True, surface_normals=True, norm_res=1) axis_control_m = rca.meters() - # Visualize setup - fig_record = fm.setup_figure_for_3d_data(figure_control, axis_control_m, title='') - spatial_ori: SpatialOrientation = sofast.data_geometry_facet[0].spatial_orientation - visualize_setup(display, camera, spatial_ori.v_screen_optic_screen, spatial_ori.r_optic_screen, ax=fig_record.axis) - fig_record.save(dir_save, 'physical_setup_layout', 'png') - # Plot scenario fig_record = fm.setup_figure_for_3d_data(figure_control, axis_control_m, title='Facet Ensemble') ensemble.draw(fig_record.view, mirror_control) diff --git a/example/sofast_fringe/example_process_single_facet.py b/example/sofast_fringe/example_process_single_facet.py index 18dbf7311..31a600bca 100644 --- a/example/sofast_fringe/example_process_single_facet.py +++ b/example/sofast_fringe/example_process_single_facet.py @@ -1,14 +1,11 @@ from os.path import join, dirname -import matplotlib - from opencsp.app.sofast.lib.DisplayShape import DisplayShape as Display from opencsp.app.sofast.lib.DefinitionFacet import DefinitionFacet from opencsp.app.sofast.lib.ImageCalibrationScaling import ImageCalibrationScaling from opencsp.app.sofast.lib.MeasurementSofastFringe import MeasurementSofastFringe from opencsp.app.sofast.lib.ProcessSofastFringe import ProcessSofastFringe as Sofast from opencsp.app.sofast.lib.SpatialOrientation import SpatialOrientation -from opencsp.app.sofast.lib.visualize_setup import visualize_setup from opencsp.common.lib.camera.Camera import Camera from opencsp.common.lib.csp.Facet import Facet from opencsp.common.lib.deflectometry.Surface2DParabolic import Surface2DParabolic @@ -36,12 +33,14 @@ def example(dir_save: str): file_measurement = join(sample_data_dir, 'measurement_facet.h5') file_camera = join(sample_data_dir, 'camera.h5') file_display = join(sample_data_dir, 'display_distorted_2d.h5') + file_orientation = join(sample_data_dir, 'spatial_orientation.h5') file_calibration = join(sample_data_dir, 'image_calibration.h5') file_facet = join(sample_data_dir, 'Facet_NSTTF.json') # Load data camera = Camera.load_from_hdf(file_camera) display = Display.load_from_hdf(file_display) + orientation = SpatialOrientation.load_from_hdf(file_orientation) measurement = MeasurementSofastFringe.load_from_hdf(file_measurement) calibration = ImageCalibrationScaling.load_from_hdf(file_calibration) facet_data = DefinitionFacet.load_from_json(file_facet) @@ -53,7 +52,7 @@ def example(dir_save: str): measurement.calibrate_fringe_images(calibration) # Instantiate sofast object - sofast = Sofast(measurement, camera, display) + sofast = Sofast(measurement, orientation, camera, display) # Process sofast.process_optic_singlefacet(facet_data, surface) @@ -70,12 +69,6 @@ def example(dir_save: str): figure_control = rcfg.RenderControlFigure(tile_array=(1, 1), tile_square=True) axis_control_m = rca.meters() - # Visualize setup - fig_record = fm.setup_figure_for_3d_data(figure_control, axis_control_m, title='') - spatial_ori: SpatialOrientation = sofast.data_geometry_facet[0].spatial_orientation - visualize_setup(display, camera, spatial_ori.v_screen_optic_screen, spatial_ori.r_optic_screen, ax=fig_record.axis) - fig_record.save(dir_save, 'physical_setup_layout', 'png') - # Plot slope map fig_record = fm.setup_figure(figure_control, axis_control_m, title='') facet.plot_orthorectified_slope(res=0.002, clim=7, axis=fig_record.axis) diff --git a/example/sofast_fringe/example_process_undefined_shape.py b/example/sofast_fringe/example_process_undefined_shape.py index 45c7310fd..10a3ea3ac 100644 --- a/example/sofast_fringe/example_process_undefined_shape.py +++ b/example/sofast_fringe/example_process_undefined_shape.py @@ -1,8 +1,5 @@ from os.path import join, dirname -import matplotlib - -from opencsp.app.sofast.lib.visualize_setup import visualize_setup from opencsp.app.sofast.lib.ImageCalibrationScaling import ImageCalibrationScaling from opencsp.app.sofast.lib.MeasurementSofastFringe import MeasurementSofastFringe from opencsp.app.sofast.lib.ProcessSofastFringe import ProcessSofastFringe as Sofast @@ -35,11 +32,13 @@ def example(dir_save): file_measurement = join(sample_data_dir, 'measurement_facet.h5') file_camera = join(sample_data_dir, 'camera.h5') file_display = join(sample_data_dir, 'display_distorted_2d.h5') + file_orientation = join(sample_data_dir, 'spatial_orientation.h5') file_calibration = join(sample_data_dir, 'image_calibration.h5') # Load data camera = Camera.load_from_hdf(file_camera) display = Display.load_from_hdf(file_display) + orientation = SpatialOrientation.load_from_hdf(file_orientation) measurement = MeasurementSofastFringe.load_from_hdf(file_measurement) calibration = ImageCalibrationScaling.load_from_hdf(file_calibration) @@ -50,7 +49,7 @@ def example(dir_save): measurement.calibrate_fringe_images(calibration) # Instantiate sofast object - sofast = Sofast(measurement, camera, display) + sofast = Sofast(measurement, orientation, camera, display) sofast.params.mask_keep_largest_area = True # Process @@ -68,12 +67,6 @@ def example(dir_save): figure_control = rcfg.RenderControlFigure(tile_array=(1, 1), tile_square=True) axis_control_m = rca.meters() - # Visualize setup - fig_record = fm.setup_figure_for_3d_data(figure_control, axis_control_m, title='') - spatial_ori: SpatialOrientation = sofast.data_geometry_facet[0].spatial_orientation - visualize_setup(display, camera, spatial_ori.v_screen_optic_screen, spatial_ori.r_optic_screen, ax=fig_record.axis) - fig_record.save(dir_save, 'physical_setup_layout', 'png') - # Plot slope map fig_record = fm.setup_figure(figure_control, axis_control_m, title='') facet.plot_orthorectified_slope(res=0.002, clim=7, axis=fig_record.axis) @@ -85,7 +78,7 @@ def example(dir_save): def example_driver(): # Define save dir - save_path = join(dirname(__file__), 'data/output/single_facet') + save_path = join(dirname(__file__), 'data/output/undefined_shape') ft.create_directories_if_necessary(save_path) # Set up logger diff --git a/opencsp/app/scene_reconstruction/lib/SceneReconstruction.py b/opencsp/app/scene_reconstruction/lib/SceneReconstruction.py index 3c9a9bcbf..5610e7dcc 100644 --- a/opencsp/app/scene_reconstruction/lib/SceneReconstruction.py +++ b/opencsp/app/scene_reconstruction/lib/SceneReconstruction.py @@ -54,6 +54,7 @@ def __init__(self, camera: Camera, known_point_locations: ndarray, image_filter_ self.camera = camera self.known_point_locations = known_point_locations self.image_paths = glob(image_filter_path) + self.image_paths.sort() # Declare attributes self.images: list[ImageMarker] # Loaded image marker objects diff --git a/opencsp/app/sofast/lib/CalibrateDisplayShape.py b/opencsp/app/sofast/lib/CalibrateDisplayShape.py index 5a07650a7..0b58fe93f 100644 --- a/opencsp/app/sofast/lib/CalibrateDisplayShape.py +++ b/opencsp/app/sofast/lib/CalibrateDisplayShape.py @@ -15,8 +15,9 @@ from scipy.spatial.transform import Rotation from tqdm import tqdm +from opencsp.app.sofast.lib.DisplayShape import DisplayShape import opencsp.app.sofast.lib.image_processing as ip -from opencsp.app.sofast.lib.MeasurementSofastFringe import MeasurementSofastFringe as Measurement +from opencsp.app.sofast.lib.MeasurementSofastFringe import MeasurementSofastFringe from opencsp.common.lib.camera.Camera import Camera from opencsp.common.lib.deflectometry.ImageProjection import CalParams from opencsp.common.lib.geometry.Vxy import Vxy @@ -44,7 +45,7 @@ class DataInput: Camera object used to capture screen distortion data image_projection_data : dict Image projection parameters - measurements_screen : list[Measurement] + measurements_screen : list[MeasurementSofastFringe] Screen shape Sofast measurement objects assume_located_points : bool To assume that points are located accuratly, does not optimize point location, by default True. @@ -58,7 +59,7 @@ class DataInput: pts_xyz_marker: Vxyz camera: Camera image_projection_data: dict - measurements_screen: list[Measurement] + measurements_screen: list[MeasurementSofastFringe] assume_located_points: bool = True ray_intersection_threshold: float = 0.001 @@ -335,18 +336,19 @@ def get_data(self) -> dict: self.data_calculation.pts_xyz_screen_aligned.data[:, self.data_calculation.intersection_points_mask] ) - return {'pts_xy_screen_fraction': pts_xy_screen_fraction, 'pts_xyz_screen_coords': pts_xyz_screen} + return {'xy_screen_fraction': pts_xy_screen_fraction, 'xyz_screen_coords': pts_xyz_screen} - def save_data_as_hdf(self, file: str) -> None: - """Saves distortion data to given HDF file""" - # Get data - data = self.get_data() + def as_DisplayShape(self, name: str) -> DisplayShape: + """Returns calibrated DisplayShape object. - # Save distortion data - with h5py.File(file, 'w') as f: - f.create_dataset('pts_xy_screen_fraction', data=data['pts_xy_screen_fraction'].data) - f.create_dataset('pts_xyz_screen_coords', data=data['pts_xyz_screen_coords'].data) - lt.info(f'Saved distortion data to: {os.path.abspath(file):s}') + Parameters + ---------- + name : str + Name of DisplayShape. + """ + grid_data = self.get_data() + grid_data.update({'screen_model': 'distorted3D'}) + return DisplayShape(grid_data, name) def visualize_located_cameras(self) -> None: """Plots cameras and alignment points""" diff --git a/opencsp/app/sofast/lib/DisplayShape.py b/opencsp/app/sofast/lib/DisplayShape.py index 503293387..e07a2056b 100644 --- a/opencsp/app/sofast/lib/DisplayShape.py +++ b/opencsp/app/sofast/lib/DisplayShape.py @@ -1,77 +1,63 @@ import numpy as np from scipy.interpolate import LinearNDInterpolator -from scipy.spatial.transform import Rotation from opencsp.common.lib.geometry.Vxy import Vxy from opencsp.common.lib.geometry.Vxyz import Vxyz -import opencsp.common.lib.tool.hdf5_tools as hdf5_tools +import opencsp.common.lib.tool.hdf5_tools as h5 -class DisplayShape: +class DisplayShape(h5.HDF5_IO_Abstract): """Representation of a screen/projector for deflectometry.""" - def __init__( - self, v_cam_screen_screen: Vxyz, r_screen_cam: Rotation, grid_data: dict, name: str = '' - ) -> 'DisplayShape': + def __init__(self, grid_data: dict, name: str = '') -> 'DisplayShape': """ Instantiates deflectometry display representation. Parameters ---------- - v_cam_screen_screen : Vxyz - Translation vector from camera to screen in screen coordinates. - r_screen_cam : Rotation - Rotation vector from camera to screen coordinates. grid_data : dict - DisplayShape distortion data. Must contain the field "screen_model" - that defines distortion model and necessary input data. + Contains different data depending on the model being used. - 1) Rectangular 2D + - Rectangular 2D - Description: Model with no distortion (useful for LCD screens, etc.). - Needs the following fields. - "screen_model" : str - 'rectangular2D' - - 2) Distorted 2D + - "screen_model" : str + - 'rectangular2D' + - "screen_x" : float + - Screen dimension in x + - "screen_y" : float + - Screen dimension in y + + - Distorted 2D - Description: Model that assumes screen ia perfectly flat 2D surface (useful for projector system with very flat wall). - Needs the following fields. - "screen_model" : str - 'distorted2D' - "xy_screen_fraction" : Vxy - XY screen points in fractional screens. - "xy_screen_coords" : Vxy - XY screen points in meters (screen coordinates). - - 3) Distorted 3D + - "screen_model" : str + - 'distorted2D' + - "xy_screen_fraction" : Vxy + - XY screen points in fractional screens. + - "xy_screen_coords" : Vxy + - XY screen points in meters (screen coordinates). + + - Distorted 3D - Description: Model that can completely define the 3D shape of a distorted screen in 3D. - Needs the following fields. - "screen_model" : str - 'distorted3D' - "xy_screen_fraction" : Vxy - XY screen points in fractional screens. - "xyz_screen_coords" : Vxyz - XYZ screen points in meters (screen coordinates). + - "screen_model" : str + - 'distorted3D' + - "xy_screen_fraction" : Vxy + - XY screen points in fractional screens. + - "xyz_screen_coords" : Vxyz + - XYZ screen points in meters (screen coordinates). name : str, optional The name of the calibrated display. - """ - - # Rotation matrices - self.r_screen_cam = r_screen_cam - self.r_cam_screen = self.r_screen_cam.inv() - - # Translation vectors - self.v_cam_screen_screen = v_cam_screen_screen - self.v_cam_screen_cam = self.v_cam_screen_screen.rotate(self.r_screen_cam) - # Save display model name self.name = name @@ -134,7 +120,6 @@ def _interp_func_rectangular2D(self, uv_display_pts: Vxy) -> Vxyz: ------- Vxyz XYZ points in display coordinates. - """ xm = (uv_display_pts.x - 0.5) * self.grid_data['screen_x'] # meters ym = (uv_display_pts.y - 0.5) * self.grid_data['screen_y'] # meters @@ -156,7 +141,6 @@ def _interp_func_2D(self, uv_display_pts: Vxy, func_xy) -> Vxyz: ------- Vxyz XYZ points in display coordinates. - """ xy = func_xy(uv_display_pts.x, uv_display_pts.y).T # (2, N) ndarray meters zm = np.zeros((1, len(uv_display_pts))) # (1, N) ndarray meters @@ -177,84 +161,75 @@ def _interp_func_3D(self, uv_display_pts: Vxy, func_xyz) -> Vxyz: ------- Vxyz XYZ points in display coordinates. - """ xyz = func_xyz(uv_display_pts.x, uv_display_pts.y).T # (3, N) ndarray meters return Vxyz(xyz) # meters, display coordinates @classmethod - def load_from_hdf(cls, file: str): - """ - Loads from HDF file + def load_from_hdf(cls, file: str, prefix: str = '') -> 'DisplayShape': + """Loads data from given file. Assumes data is stored as: PREFIX + DisplayShape/Field_1 Parameters ---------- - file : string - HDF5 file to load - + file : str + HDF file to save to + prefix : str + Prefix to append to folder path within HDF file (folders must be separated by "/") """ # Load grid data - grid_data = hdf5_tools.load_hdf5_datasets(['DisplayShape/screen_model'], file) + datasets = [prefix + 'DisplayShape/screen_model', prefix + 'DisplayShape/name'] + data = h5.load_hdf5_datasets(datasets, file) # Rectangular - if grid_data['screen_model'] == 'rectangular2D': - datasets = ['DisplayShape/screen_x', 'DisplayShape/screen_y'] - grid_data.update(hdf5_tools.load_hdf5_datasets(datasets, file)) + if data['screen_model'] == 'rectangular2D': + datasets = [prefix + 'DisplayShape/screen_x', prefix + 'DisplayShape/screen_y'] + grid_data = h5.load_hdf5_datasets(datasets, file) # Distorted 2D - elif grid_data['screen_model'] == 'distorted2D': - datasets = ['DisplayShape/xy_screen_fraction', 'DisplayShape/xy_screen_coords'] - grid_data.update(hdf5_tools.load_hdf5_datasets(datasets, file)) + elif data['screen_model'] == 'distorted2D': + datasets = [prefix + 'DisplayShape/xy_screen_fraction', prefix + 'DisplayShape/xy_screen_coords'] + grid_data = h5.load_hdf5_datasets(datasets, file) grid_data['xy_screen_fraction'] = Vxy(grid_data['xy_screen_fraction']) grid_data['xy_screen_coords'] = Vxy(grid_data['xy_screen_coords']) # Distorted 3D - elif grid_data['screen_model'] == 'distorted3D': - datasets = ['DisplayShape/xy_screen_fraction', 'DisplayShape/xyz_screen_coords'] - grid_data.update(hdf5_tools.load_hdf5_datasets(datasets, file)) + elif data['screen_model'] == 'distorted3D': + datasets = [prefix + 'DisplayShape/xy_screen_fraction', prefix + 'DisplayShape/xyz_screen_coords'] + grid_data = h5.load_hdf5_datasets(datasets, file) grid_data['xy_screen_fraction'] = Vxy(grid_data['xy_screen_fraction']) grid_data['xyz_screen_coords'] = Vxyz(grid_data['xyz_screen_coords']) else: - raise ValueError(f'Model, {grid_data["screen_model"]}, not supported.') - - # Load display parameters - datasets = ['DisplayShape/rvec_screen_cam', 'DisplayShape/tvec_cam_screen_screen', 'DisplayShape/name'] - data = hdf5_tools.load_hdf5_datasets(datasets, file) + raise ValueError(f'Model, {data["screen_model"]}, not supported.') + grid_data.update({'screen_model': data['screen_model']}) # Return display object - kwargs = { - 'r_screen_cam': Rotation.from_rotvec(data['rvec_screen_cam']), - 'v_cam_screen_screen': Vxyz(data['tvec_cam_screen_screen']), - 'name': data['name'], - 'grid_data': grid_data, - } + kwargs = {'name': data['name'], 'grid_data': grid_data} return cls(**kwargs) - def save_to_hdf(self, file: str): - """ - Saves to HDF file + def save_to_hdf(self, file: str, prefix: str = '') -> None: + """Saves data to given file. Data is stored as: PREFIX + DisplayShape/Field_1 Parameters ---------- - file : string - HDF5 file to save - + file : str + HDF file to save to + prefix : str + Prefix to append to folder path within HDF file (folders must be separated by "/") """ - # Get "grid data" datset names and data datasets = [] data = [] for dataset in self.grid_data.keys(): - datasets.append('DisplayShape/' + dataset) - if isinstance(self.grid_data[dataset], Vxy) or isinstance(self.grid_data[dataset], Vxyz): + datasets.append(prefix + 'DisplayShape/' + dataset) + if isinstance(self.grid_data[dataset], (Vxy, Vxyz)): data.append(self.grid_data[dataset].data) else: data.append(self.grid_data[dataset]) - # Screen data - datasets += ['DisplayShape/rvec_screen_cam', 'DisplayShape/tvec_cam_screen_screen', 'DisplayShape/name'] - data += [self.r_screen_cam.as_rotvec(), self.v_cam_screen_screen.data, self.name] + # Add name + datasets.append(prefix + 'DisplayShape/name') + data.append(self.name) # Save data - hdf5_tools.save_hdf5_datasets(data, datasets, file) + h5.save_hdf5_datasets(data, datasets, file) diff --git a/opencsp/app/sofast/lib/ProcessSofastFringe.py b/opencsp/app/sofast/lib/ProcessSofastFringe.py index 271f3d2ea..56cac5c54 100644 --- a/opencsp/app/sofast/lib/ProcessSofastFringe.py +++ b/opencsp/app/sofast/lib/ProcessSofastFringe.py @@ -11,7 +11,7 @@ from opencsp.app.sofast.lib.DefinitionFacet import DefinitionFacet from opencsp.app.sofast.lib.DisplayShape import DisplayShape as Display import opencsp.app.sofast.lib.image_processing as ip -from opencsp.app.sofast.lib.MeasurementSofastFringe import MeasurementSofastFringe as Measurement +from opencsp.app.sofast.lib.MeasurementSofastFringe import MeasurementSofastFringe from opencsp.app.sofast.lib.ParamsSofastFringe import ParamsSofastFringe import opencsp.app.sofast.lib.process_optics_geometry as po from opencsp.app.sofast.lib.SpatialOrientation import SpatialOrientation @@ -158,25 +158,28 @@ class ProcessSofastFringe(HDF5_SaveAbstract): - v_mask_centroid_image """ - def __init__(self, measurement: Measurement, camera: Camera, display: Display) -> 'ProcessSofastFringe': + def __init__( + self, measurement: MeasurementSofastFringe, orientation: SpatialOrientation, camera: Camera, display: Display + ) -> 'ProcessSofastFringe': """ SOFAST processing class. Parameters ---------- - measurement : Measurement - Measurement class to process. + measurement : MeasurementSofastFringe + MeasurementSofastFringe class to process. + orientation : SpatialOrientation + SpatialOrientation object camera : Camera Camera object used to capture data. display : Display Display object used to capture data. - """ # Store data self.measurement = measurement self.display = display self.camera = camera - self.orientation = SpatialOrientation(display.r_cam_screen, display.v_cam_screen_cam) + self.orientation = orientation # Define default calculation parameters self.params = ParamsSofastFringe() diff --git a/opencsp/app/sofast/lib/SpatialOrientation.py b/opencsp/app/sofast/lib/SpatialOrientation.py index 373d70c1d..1b451313f 100644 --- a/opencsp/app/sofast/lib/SpatialOrientation.py +++ b/opencsp/app/sofast/lib/SpatialOrientation.py @@ -2,10 +2,10 @@ from opencsp.common.lib.geometry.TransformXYZ import TransformXYZ from opencsp.common.lib.geometry.Vxyz import Vxyz -from opencsp.common.lib.tool import hdf5_tools +import opencsp.common.lib.tool.hdf5_tools as h5 -class SpatialOrientation: +class SpatialOrientation(h5.HDF5_IO_Abstract): """Holds relative orientations of camera, screen, and optic for deflectometry systems""" def __init__(self, r_cam_screen: Rotation, v_cam_screen_cam: Vxyz) -> 'SpatialOrientation': @@ -139,16 +139,35 @@ def save_to_hdf(self, file: str, prefix: str = '') -> None: HDF file to save to prefix : str Prefix to append to folder path within HDF file (folders must be separated by "/") - """ - datasets = [prefix + 'SpatialOrientation/r_cam_screen', prefix + 'SpatialOrientation/v_cam_screen_cam'] - - data = [self.r_cam_screen.as_rotvec(), self.v_cam_screen_cam.data] - - hdf5_tools.save_hdf5_datasets(data, datasets, file) + if self.optic_oriented: + datasets = [ + prefix + 'SpatialOrientation/r_cam_screen', + prefix + 'SpatialOrientation/v_cam_screen_cam', + prefix + 'SpatialOrientation/optic_oriented', + prefix + 'SpatialOrientation/r_cam_optic', + prefix + 'SpatialOrientation/v_cam_optic_cam', + ] + data = [ + self.r_cam_screen.as_rotvec(), + self.v_cam_screen_cam.data, + self.optic_oriented, + self.r_cam_optic.as_rotvec(), + self.v_cam_optic_cam.data, + ] + else: + datasets = [ + prefix + 'SpatialOrientation/r_cam_screen', + prefix + 'SpatialOrientation/v_cam_screen_cam', + prefix + 'SpatialOrientation/optic_oriented', + ] + data = [self.r_cam_screen.as_rotvec(), self.v_cam_screen_cam.data, self.optic_oriented] + + h5.save_hdf5_datasets(data, datasets, file) - def save_all_to_hdf(self, file: str, prefix: str = '') -> None: - """Saves all data to HDF file. Data is stored as prefix + SpatialOrientation/... + @classmethod + def load_from_hdf(cls, file: str, prefix: str = '') -> 'SpatialOrientation': + """Loads data from given file. Assumes data is stored as: PREFIX + SpatialOrientation/... Parameters ---------- @@ -156,50 +175,24 @@ def save_all_to_hdf(self, file: str, prefix: str = '') -> None: HDF file to save to prefix : str Prefix to append to folder path within HDF file (folders must be separated by "/") - """ + # Load screen-camera orientation information datasets = [ prefix + 'SpatialOrientation/r_cam_screen', prefix + 'SpatialOrientation/v_cam_screen_cam', - prefix + 'SpatialOrientation/r_cam_optic', - prefix + 'SpatialOrientation/v_cam_optic_cam', + prefix + 'SpatialOrientation/optic_oriented', ] - - data = [ - self.r_cam_screen.as_rotvec(), - self.v_cam_screen_cam.data, - self.r_cam_optic.as_rotvec(), - self.v_cam_optic_cam.data, - ] - - hdf5_tools.save_hdf5_datasets(data, datasets, file) - - @classmethod - def load_from_hdf(cls, file: str) -> 'SpatialOrientation': - """Loads camera-screen orientation data from HDF file""" - datasets = ['SpatialOrientation/r_cam_screen', 'SpatialOrientation/v_cam_screen_cam'] - data = hdf5_tools.load_hdf5_datasets(datasets, file) + data = h5.load_hdf5_datasets(datasets, file) r_cam_screen = Rotation.from_rotvec(data['r_cam_screen']) v_cam_screen_cam = Vxyz(data['v_cam_screen_cam']) - return cls(r_cam_screen, v_cam_screen_cam) - - @classmethod - def load_all_from_hdf(cls, file: str) -> 'SpatialOrientation': - """Loads all data from HDF file""" - datasets = [ - 'SpatialOrientation/r_cam_screen', - 'SpatialOrientation/v_cam_screen_cam', - 'SpatialOrientation/r_cam_optic', - 'SpatialOrientation/v_cam_optic_cam', - ] - data = hdf5_tools.load_hdf5_datasets(datasets, file) - - r_cam_screen = Rotation.from_rotvec(data['r_cam_screen']) - v_cam_screen_cam = Vxyz(data['v_cam_screen_cam']) - r_cam_optic = Rotation.from_rotvec(data['r_cam_optic']) - v_cam_optic_cam = Vxyz(data['v_cam_optic_cam']) - ori = cls(r_cam_screen, v_cam_screen_cam) - ori.orient_optic_cam(r_cam_optic, v_cam_optic_cam) + + # If optic is oriented, load optic orientation information + if data['optic_oriented']: + datasets = [prefix + 'SpatialOrientation/r_cam_optic', prefix + 'SpatialOrientation/v_cam_optic_cam'] + data = h5.load_hdf5_datasets(datasets, file) + r_cam_optic = Rotation.from_rotvec(data['r_cam_optic']) + v_cam_optic_cam = Vxyz(data['v_cam_optic_cam']) + ori.orient_optic_cam(r_cam_optic, v_cam_optic_cam) return ori diff --git a/opencsp/app/sofast/lib/calculation_data_classes.py b/opencsp/app/sofast/lib/calculation_data_classes.py index f657f4432..855c452f7 100644 --- a/opencsp/app/sofast/lib/calculation_data_classes.py +++ b/opencsp/app/sofast/lib/calculation_data_classes.py @@ -105,7 +105,7 @@ def save_to_hdf(self, file: str, prefix: str = ''): prefix + 'CalculationDataGeometryFacet/u_pixel_pointing_facet', prefix + 'CalculationDataGeometryFacet/v_screen_points_facet', ] - self.spatial_orientation.save_all_to_hdf(file, prefix + 'CalculationDataGeometryFacet/') + self.spatial_orientation.save_to_hdf(file, prefix + 'CalculationDataGeometryFacet/') _save_data_in_file(data, datasets, file) diff --git a/opencsp/app/sofast/lib/save_DisplayShape_file.py b/opencsp/app/sofast/lib/save_DisplayShape_file.py deleted file mode 100644 index 509ecfa49..000000000 --- a/opencsp/app/sofast/lib/save_DisplayShape_file.py +++ /dev/null @@ -1,47 +0,0 @@ -"""Script that saves a Sofast physical setup file from previously processed data -""" - -from numpy import ndarray -from scipy.spatial.transform import Rotation - -from opencsp.app.sofast.lib.DisplayShape import DisplayShape -from opencsp.common.lib.geometry.Vxy import Vxy -from opencsp.common.lib.geometry.Vxyz import Vxyz - - -def save_DisplayShape_file( - screen_distortion_data: dict, name: str, rvec: ndarray, tvec: ndarray, file_save: str -) -> None: - """Constructs and saves DisplayShape file - - Parameters - ---------- - screen_distortion_data : dict - Dict with following fields: 1) pts_xy_screen_fraction: Vxy, 2) pts_xyz_screen_coords: Vxyz - name : str - DisplayShape name - rvec : ndarray - Screen to camera rotation vector - tvec : ndarray - Camera to screen (in screen coordinates) translation vector - file_save : str - Output display file name to save to - """ - # Load screen distortion data - pts_xy_screen_fraction: Vxy = screen_distortion_data['pts_xy_screen_fraction'] - pts_xyz_screen_coords: Vxyz = screen_distortion_data['pts_xyz_screen_coords'] - - # Gather rvec and tvec - rot_screen_cam = Rotation.from_rotvec(rvec) - v_cam_screen_screen = Vxyz(tvec) - - # Gather display grid data - grid_data = dict( - screen_model='distorted3D', xy_screen_fraction=pts_xy_screen_fraction, xyz_screen_coords=pts_xyz_screen_coords - ) - - # Create display object - display = DisplayShape(v_cam_screen_screen, rot_screen_cam, grid_data, name) - - # Save to HDF file - display.save_to_hdf(file_save) diff --git a/opencsp/app/sofast/test/test_CalibrateDisplayShape.py b/opencsp/app/sofast/test/test_CalibrateDisplayShape.py index c2d7c16c1..e8e979a98 100644 --- a/opencsp/app/sofast/test/test_CalibrateDisplayShape.py +++ b/opencsp/app/sofast/test/test_CalibrateDisplayShape.py @@ -2,31 +2,29 @@ """ import os -from os.path import join, dirname +from os.path import join import unittest from glob import glob import numpy as np import pytest -from opencsp.common.lib.opencsp_path.opencsp_root_path import opencsp_code_dir from opencsp.app.sofast.lib.CalibrateDisplayShape import CalibrateDisplayShape, DataInput -from opencsp.app.sofast.lib.MeasurementSofastFringe import MeasurementSofastFringe as Measurement +from opencsp.app.sofast.lib.MeasurementSofastFringe import MeasurementSofastFringe from opencsp.common.lib.camera.Camera import Camera from opencsp.common.lib.deflectometry.ImageProjection import ImageProjection from opencsp.common.lib.geometry.Vxyz import Vxyz +from opencsp.common.lib.opencsp_path.opencsp_root_path import opencsp_code_dir from opencsp.common.lib.tool.hdf5_tools import load_hdf5_datasets import opencsp.common.lib.tool.file_tools as ft import opencsp.common.lib.tool.log_tools as lt class TestCalibrateDisplayShape(unittest.TestCase): + """Tests CalibrateDisplayShape""" + @classmethod def setUpClass(cls): - """Tests the CalibrateDisplayShape process. If directories are None, - uses default test data directory. All input files listed below must be - on the dir_input path. - """ # Define default data directories dir_input_sofast = join(opencsp_code_dir(), 'app/sofast/test/data/data_measurement') dir_input_def = join(opencsp_code_dir(), 'common/lib/deflectometry/test/data/data_measurement') @@ -39,6 +37,7 @@ def setUpClass(cls): file_camera_distortion = join(dir_input_sofast, 'camera_screen_shape.h5') file_image_projection = join(dir_input_sofast, 'image_projection.h5') files_screen_shape_measurement = glob(join(dir_input_sofast, 'screen_shape_sofast_measurements/pose_*.h5')) + files_screen_shape_measurement.sort() # Load input data pts_marker_data = np.loadtxt(file_point_locations, delimiter=',', dtype=float, skiprows=1) @@ -56,35 +55,51 @@ def setUpClass(cls): pts_xyz_marker, camera, image_projection_data, - [Measurement.load_from_hdf(f) for f in files_screen_shape_measurement], + [MeasurementSofastFringe.load_from_hdf(f) for f in files_screen_shape_measurement], ) # Perform screen position calibration cal_screen_position = CalibrateDisplayShape(data_input) cal_screen_position.run_calibration() - - # Get distortion data - dist_data = cal_screen_position.get_data() + cls.cal = cal_screen_position # Test screen distortion information cls.data_exp = load_hdf5_datasets( ['pts_xy_screen_fraction', 'pts_xyz_screen_coords'], join(dir_output, 'screen_distortion_data_100_100.h5') ) - cls.data_meas = dist_data + + cls.save_dir_local = join(opencsp_code_dir(), 'app/sofast/test/data/output') + ft.create_directories_if_necessary(cls.save_dir_local) @pytest.mark.skipif(os.name != 'nt', reason='Does not pass in Linux environment for unkonwn reason.') - def test_screen_distortion_data(self): - """Tests screen calibration data""" + def test_xy_screen_fraction(self): + """Tests xy points""" + data_meas = self.cal.get_data() np.testing.assert_allclose( - self.data_meas['pts_xy_screen_fraction'].data, self.data_exp['pts_xy_screen_fraction'], rtol=0, atol=1e-6 + data_meas['xy_screen_fraction'].data, self.data_exp['pts_xy_screen_fraction'], rtol=0, atol=1e-6 ) + + @pytest.mark.skipif(os.name != 'nt', reason='Does not pass in Linux environment for unkonwn reason.') + def test_xyz_screen_coords(self): + """Tests xyz points""" + data_meas = self.cal.get_data() np.testing.assert_allclose( - self.data_meas['pts_xyz_screen_coords'].data, self.data_exp['pts_xyz_screen_coords'], rtol=0, atol=1e-6 + data_meas['xyz_screen_coords'].data, self.data_exp['pts_xyz_screen_coords'], rtol=0, atol=1e-6 ) + def test_save_display_object(self): + """Tests saving DisplayShape object""" + display_shape = self.cal.as_DisplayShape('Test display') + file = join(self.save_dir_local, 'test_calibration_display.h5') + display_shape.save_to_hdf(file) + if __name__ == '__main__': - save_dir = join(dirname(__file__), 'data/output') + # Set up save dir + save_dir = join(opencsp_code_dir(), 'app/sofast/test/data/output') ft.create_directories_if_necessary(save_dir) + + # Set up logger lt.logger(join(save_dir, 'log_display_shape.txt'), lt.log.WARN) + unittest.main() diff --git a/opencsp/app/sofast/test/test_Display.py b/opencsp/app/sofast/test/test_DisplayShape.py similarity index 50% rename from opencsp/app/sofast/test/test_Display.py rename to opencsp/app/sofast/test/test_DisplayShape.py index f6fbefbb6..a43f0d808 100644 --- a/opencsp/app/sofast/test/test_Display.py +++ b/opencsp/app/sofast/test/test_DisplayShape.py @@ -2,16 +2,17 @@ """ import unittest +from os.path import dirname, join import numpy as np -from scipy.spatial.transform import Rotation from opencsp.app.sofast.lib.DisplayShape import DisplayShape from opencsp.common.lib.geometry.Vxy import Vxy from opencsp.common.lib.geometry.Vxyz import Vxyz +import opencsp.common.lib.tool.file_tools as ft -class TestDisplay(unittest.TestCase): +class TestDisplayShape(unittest.TestCase): @classmethod def setUpClass(cls): # Define screen X and Y extent @@ -59,12 +60,14 @@ def setUpClass(cls): ) ) + # Set up save path + cls.save_dir = join(dirname(__file__), 'data/output') + ft.create_directories_if_necessary(cls.save_dir) + def test_rectangular2D(self): # Instantiate display object - v_cam_screen_screen = Vxyz((0, 0, 1)) - r_screen_cam = Rotation.from_rotvec(np.array([0.0, 0.0, 0.0])) name = 'Test DisplayShape' - disp = DisplayShape(v_cam_screen_screen, r_screen_cam, self.grid_data_rect2D, name) + disp = DisplayShape(self.grid_data_rect2D, name) # Perform calculation calc = disp.interp_func(self.test_Vxy_pts) @@ -74,10 +77,8 @@ def test_rectangular2D(self): def test_distorted2D(self): # Instantiate display object - v_cam_screen_screen = Vxyz((0, 0, 1)) - r_screen_cam = Rotation.from_rotvec(np.array([0.0, 0.0, 0.0])) name = 'Test DisplayShape' - disp = DisplayShape(v_cam_screen_screen, r_screen_cam, self.grid_data_2D, name) + disp = DisplayShape(self.grid_data_2D, name) # Perform calculation calc = disp.interp_func(self.test_Vxy_pts) @@ -87,10 +88,8 @@ def test_distorted2D(self): def test_distorted3D(self): # Instantiate display object - v_cam_screen_screen = Vxyz((0, 0, 1)) - r_screen_cam = Rotation.from_rotvec(np.array([0.0, 0.0, 0.0])) name = 'Test DisplayShape' - disp = DisplayShape(v_cam_screen_screen, r_screen_cam, self.grid_data_3D, name) + disp = DisplayShape(self.grid_data_3D, name) # Perform calculation calc = disp.interp_func(self.test_Vxy_pts) @@ -98,6 +97,64 @@ def test_distorted3D(self): # Test np.testing.assert_allclose(calc.data, self.exp_Vxyz_disp_pts.data, rtol=0, atol=1e-7) + def test_save_load_hdf_dist_3d(self): + # Instantiate display object + name = 'Test DisplayShape 3D' + disp = DisplayShape(self.grid_data_3D, name) + file = join(self.save_dir, 'test_display_shape_dist_3d.h5') + + # Save + disp.save_to_hdf(file) + + # Load + disp_load = DisplayShape.load_from_hdf(file) + + # Compare + self.assertEqual(disp.grid_data['screen_model'], disp_load.grid_data['screen_model']) + self.assertEqual(disp.name, disp_load.name) + np.testing.assert_equal( + disp.grid_data['xy_screen_fraction'].data, disp_load.grid_data['xy_screen_fraction'].data + ) + np.testing.assert_equal(disp.grid_data['xyz_screen_coords'].data, disp_load.grid_data['xyz_screen_coords'].data) + + def test_save_load_hdf_dist_2d(self): + # Instantiate display object + name = 'Test DisplayShape 2D' + disp = DisplayShape(self.grid_data_2D, name) + file = join(self.save_dir, 'test_display_shape_dist_2d.h5') + + # Save + disp.save_to_hdf(file) + + # Load + disp_load = DisplayShape.load_from_hdf(file) + + # Compare + self.assertEqual(disp.grid_data['screen_model'], disp_load.grid_data['screen_model']) + self.assertEqual(disp.name, disp_load.name) + np.testing.assert_equal( + disp.grid_data['xy_screen_fraction'].data, disp_load.grid_data['xy_screen_fraction'].data + ) + np.testing.assert_equal(disp.grid_data['xy_screen_coords'].data, disp_load.grid_data['xy_screen_coords'].data) + + def test_save_load_hdf_rectangular(self): + # Instantiate display object + name = 'Test DisplayShape Rectangular' + disp = DisplayShape(self.grid_data_rect2D, name) + file = join(self.save_dir, 'test_display_shape_rect.h5') + + # Save + disp.save_to_hdf(file) + + # Load + disp_load = DisplayShape.load_from_hdf(file) + + # Compare + self.assertEqual(disp.grid_data['screen_model'], disp_load.grid_data['screen_model']) + self.assertEqual(disp.name, disp_load.name) + self.assertEqual(disp.grid_data['screen_x'], disp_load.grid_data['screen_x']) + self.assertEqual(disp.grid_data['screen_y'], disp_load.grid_data['screen_y']) + if __name__ == '__main__': unittest.main() diff --git a/opencsp/app/sofast/test/test_ProcessSofastFixed.py b/opencsp/app/sofast/test/test_ProcessSofastFixed.py index 79e3355fd..23d4f11ee 100644 --- a/opencsp/app/sofast/test/test_ProcessSofastFixed.py +++ b/opencsp/app/sofast/test/test_ProcessSofastFixed.py @@ -18,7 +18,7 @@ import opencsp.common.lib.render.figure_management as fm import opencsp.common.lib.render_control.RenderControlAxis as rca import opencsp.common.lib.render_control.RenderControlFigure as rcfg -import opencsp.common.lib.tool.hdf5_tools as ht +import opencsp.common.lib.tool.hdf5_tools as h5 import opencsp.common.lib.tool.file_tools as ft import opencsp.common.lib.tool.log_tools as lt @@ -53,7 +53,7 @@ def setUpClass(cls): # Load expected data datasets = ['CalculationsFixedPattern/Facet_000/SlopeSolverData/slopes_facet_xy'] - data = ht.load_hdf5_datasets(datasets, file_exp) + data = h5.load_hdf5_datasets(datasets, file_exp) cls.exp_slopes_xy = data['slopes_facet_xy'] # Instantiate class diff --git a/opencsp/app/sofast/test/test_SpatialOrientation.py b/opencsp/app/sofast/test/test_SpatialOrientation.py index 85b3480d6..2e705512a 100644 --- a/opencsp/app/sofast/test/test_SpatialOrientation.py +++ b/opencsp/app/sofast/test/test_SpatialOrientation.py @@ -1,45 +1,48 @@ -"""Unit test suite to test the SpatialOrientation class -""" +"""Unit test suite to test the SpatialOrientation class""" -import os +from os.path import join import unittest import numpy as np from scipy.spatial.transform import Rotation -from opencsp.app.sofast.lib.DisplayShape import DisplayShape as Display from opencsp.app.sofast.lib.SpatialOrientation import SpatialOrientation from opencsp.common.lib.geometry.Vxyz import Vxyz from opencsp.common.lib.opencsp_path.opencsp_root_path import opencsp_code_dir from opencsp.common.lib.tool.hdf5_tools import load_hdf5_datasets +import opencsp.common.lib.tool.file_tools as ft class TestSpatialOrientation(unittest.TestCase): @classmethod def setUpClass(cls): # Get test data location - base_dir = os.path.join(opencsp_code_dir(), 'test/data/measurements_sofast_fringe') + base_dir = join(opencsp_code_dir(), 'test/data/measurements_sofast_fringe') # Define test data files for single facet processing - data_file_facet = os.path.join(base_dir, 'calculations_facet/data.h5') + data_file_facet = join(base_dir, 'calculations_facet/data.h5') - # Create spatial orientation objects + # Load data datasets = [ 'DataSofastCalculation/geometry/general/r_optic_cam_refine_1', 'DataSofastCalculation/geometry/general/v_cam_optic_cam_refine_2', ] - # Load data - data = load_hdf5_datasets(datasets, data_file_facet) - display = Display.load_from_hdf(data_file_facet) + ori = SpatialOrientation.load_from_hdf(data_file_facet) + + data = load_hdf5_datasets(datasets, data_file_facet) r_cam_optic = Rotation.from_rotvec(data['r_optic_cam_refine_1']).inv() v_cam_optic_cam = Vxyz(data['v_cam_optic_cam_refine_2']) - ori = SpatialOrientation(display.r_cam_screen, display.v_cam_screen_cam) + ori.orient_optic_cam(r_cam_optic, v_cam_optic_cam) # Save spatial orientation cls.so = ori + # Set up save path + cls.save_dir = join(opencsp_code_dir(), 'app/sofast/test/data/output') + ft.create_directories_if_necessary(cls.save_dir) + def test_translation_ring_1(self): v_exp = np.zeros(3) v_calc = ( @@ -68,6 +71,28 @@ def test_rotation_ring_2(self): I_calc = self.so.r_cam_screen * self.so.r_optic_cam * self.so.r_screen_optic np.testing.assert_allclose(I_exp, I_calc.as_matrix(), atol=1e-9, rtol=0) + def test_io_oriented_optic(self): + file = join(self.save_dir, 'test_spatial_orientation_oriented_optic.h5') + # Save + self.so.save_to_hdf(file) + # Load + ori = SpatialOrientation.load_from_hdf(file) + # Check optic is oriented + self.assertEqual(ori.optic_oriented, True) + + def test_io_unoriented_optic(self): + file = join(self.save_dir, 'test_spatial_orientation_unoriented_optic.h5') + # Save + r_cam_screen = self.so.r_cam_screen + v_cam_screen_cam = self.so.v_cam_screen_cam + ori_1 = SpatialOrientation(r_cam_screen, v_cam_screen_cam) + ori_1.save_to_hdf(file) + # Load + ori_2 = SpatialOrientation.load_from_hdf(file) + # Check optic not oriented + self.assertEqual(ori_1.optic_oriented, False) + self.assertEqual(ori_2.optic_oriented, False) + if __name__ == '__main__': unittest.main() diff --git a/opencsp/app/sofast/test/test_integration_multi_facet.py b/opencsp/app/sofast/test/test_integration_multi_facet.py index f5c47bf58..79362fe11 100644 --- a/opencsp/app/sofast/test/test_integration_multi_facet.py +++ b/opencsp/app/sofast/test/test_integration_multi_facet.py @@ -7,12 +7,13 @@ from scipy.spatial.transform import Rotation import numpy as np -from opencsp.app.sofast.lib.DisplayShape import DisplayShape as Display +from opencsp.app.sofast.lib.DisplayShape import DisplayShape from opencsp.app.sofast.lib.DefinitionEnsemble import DefinitionEnsemble from opencsp.app.sofast.lib.DefinitionFacet import DefinitionFacet from opencsp.app.sofast.lib.ImageCalibrationScaling import ImageCalibrationScaling -from opencsp.app.sofast.lib.ProcessSofastFringe import ProcessSofastFringe as Sofast -from opencsp.app.sofast.lib.MeasurementSofastFringe import MeasurementSofastFringe as Measurement +from opencsp.app.sofast.lib.MeasurementSofastFringe import MeasurementSofastFringe +from opencsp.app.sofast.lib.ProcessSofastFringe import ProcessSofastFringe +from opencsp.app.sofast.lib.SpatialOrientation import SpatialOrientation from opencsp.common.lib.camera.Camera import Camera from opencsp.common.lib.deflectometry.Surface2DParabolic import Surface2DParabolic from opencsp.common.lib.geometry.Vxyz import Vxyz @@ -41,8 +42,9 @@ def setUpClass(cls, base_dir: str | None = None): # Load data camera = Camera.load_from_hdf(file_dataset) - display = Display.load_from_hdf(file_dataset) - measurement = Measurement.load_from_hdf(file_measurement) + display = DisplayShape.load_from_hdf(file_dataset) + orientation = SpatialOrientation.load_from_hdf(file_dataset) + measurement = MeasurementSofastFringe.load_from_hdf(file_measurement) calibration = ImageCalibrationScaling.load_from_hdf(file_dataset) # Load sofast params @@ -64,7 +66,7 @@ def setUpClass(cls, base_dir: str | None = None): measurement.calibrate_fringe_images(calibration) # Instantiate sofast object - sofast = Sofast(measurement, camera, display) + sofast = ProcessSofastFringe(measurement, orientation, camera, display) # Update parameters sofast.params.mask_hist_thresh = params['mask_hist_thresh'] diff --git a/opencsp/app/sofast/test/test_integration_single_facet.py b/opencsp/app/sofast/test/test_integration_single_facet.py index 11c792162..93a474b35 100644 --- a/opencsp/app/sofast/test/test_integration_single_facet.py +++ b/opencsp/app/sofast/test/test_integration_single_facet.py @@ -7,11 +7,12 @@ import numpy as np -from opencsp.app.sofast.lib.DisplayShape import DisplayShape as Display +from opencsp.app.sofast.lib.DisplayShape import DisplayShape from opencsp.app.sofast.lib.DefinitionFacet import DefinitionFacet from opencsp.app.sofast.lib.ImageCalibrationScaling import ImageCalibrationScaling -from opencsp.app.sofast.lib.MeasurementSofastFringe import MeasurementSofastFringe as Measurement -from opencsp.app.sofast.lib.ProcessSofastFringe import ProcessSofastFringe as Sofast +from opencsp.app.sofast.lib.MeasurementSofastFringe import MeasurementSofastFringe +from opencsp.app.sofast.lib.ProcessSofastFringe import ProcessSofastFringe +from opencsp.app.sofast.lib.SpatialOrientation import SpatialOrientation from opencsp.common.lib.camera.Camera import Camera from opencsp.common.lib.deflectometry.Surface2DPlano import Surface2DPlano from opencsp.common.lib.deflectometry.Surface2DParabolic import Surface2DParabolic @@ -37,6 +38,7 @@ def setUpClass(cls, base_dir: str | None = None): # Find all test files cls.files_dataset = glob.glob(os.path.join(base_dir, 'calculations_facet/data*.h5')) + cls.files_dataset.sort() if len(cls.files_dataset) == 0: raise ValueError('No single-facet datsets found.') @@ -44,7 +46,7 @@ def setUpClass(cls, base_dir: str | None = None): file_measurement = os.path.join(base_dir, 'measurement_facet.h5') # Load components - measurement = Measurement.load_from_hdf(file_measurement) + measurement = MeasurementSofastFringe.load_from_hdf(file_measurement) # Initialize data containers cls.slopes = [] @@ -55,8 +57,9 @@ def setUpClass(cls, base_dir: str | None = None): for file_dataset in cls.files_dataset: # Load display camera = Camera.load_from_hdf(file_dataset) + orientation = SpatialOrientation.load_from_hdf(file_dataset) calibration = ImageCalibrationScaling.load_from_hdf(file_dataset) - display = Display.load_from_hdf(file_dataset) + display = DisplayShape.load_from_hdf(file_dataset) # Calibrate measurement measurement.calibrate_fringe_images(calibration) @@ -111,7 +114,7 @@ def setUpClass(cls, base_dir: str | None = None): params = load_hdf5_datasets(datasets, file_dataset) # Instantiate sofast object - sofast = Sofast(measurement, camera, display) + sofast = ProcessSofastFringe(measurement, orientation, camera, display) # Update parameters sofast.params.mask_hist_thresh = params['mask_hist_thresh'] diff --git a/opencsp/app/sofast/test/test_integration_undefined.py b/opencsp/app/sofast/test/test_integration_undefined.py index 95a464cf1..f62b0f731 100644 --- a/opencsp/app/sofast/test/test_integration_undefined.py +++ b/opencsp/app/sofast/test/test_integration_undefined.py @@ -8,8 +8,9 @@ from opencsp.app.sofast.lib.DisplayShape import DisplayShape as Display from opencsp.app.sofast.lib.ImageCalibrationScaling import ImageCalibrationScaling -from opencsp.app.sofast.lib.MeasurementSofastFringe import MeasurementSofastFringe as Measurement -from opencsp.app.sofast.lib.ProcessSofastFringe import ProcessSofastFringe as Sofast +from opencsp.app.sofast.lib.MeasurementSofastFringe import MeasurementSofastFringe +from opencsp.app.sofast.lib.ProcessSofastFringe import ProcessSofastFringe +from opencsp.app.sofast.lib.SpatialOrientation import SpatialOrientation from opencsp.common.lib.camera.Camera import Camera from opencsp.common.lib.deflectometry.Surface2DParabolic import Surface2DParabolic from opencsp.common.lib.opencsp_path.opencsp_root_path import opencsp_code_dir @@ -28,7 +29,8 @@ def test_undefined(self): # Load data camera = Camera.load_from_hdf(file_dataset) display = Display.load_from_hdf(file_dataset) - measurement = Measurement.load_from_hdf(file_measurement) + orientation = SpatialOrientation.load_from_hdf(file_dataset) + measurement = MeasurementSofastFringe.load_from_hdf(file_measurement) calibration = ImageCalibrationScaling.load_from_hdf(file_dataset) # Calibrate measurement @@ -60,7 +62,7 @@ def test_undefined(self): params = load_hdf5_datasets(datasets, file_dataset) # Instantiate sofast object - sofast = Sofast(measurement, camera, display) + sofast = ProcessSofastFringe(measurement, orientation, camera, display) # Update parameters sofast.params.mask_hist_thresh = params['mask_hist_thresh'] diff --git a/opencsp/app/sofast/test/test_save_DisplayShape_file.py b/opencsp/app/sofast/test/test_save_DisplayShape_file.py deleted file mode 100644 index 71d77bdc7..000000000 --- a/opencsp/app/sofast/test/test_save_DisplayShape_file.py +++ /dev/null @@ -1,45 +0,0 @@ -from os.path import join -import unittest - -import numpy as np - -from opencsp.app.sofast.lib.save_DisplayShape_file import save_DisplayShape_file -from opencsp.common.lib.tool.hdf5_tools import load_hdf5_datasets -from opencsp.common.lib.geometry.Vxy import Vxy -from opencsp.common.lib.geometry.Vxyz import Vxyz -from opencsp.common.lib.opencsp_path.opencsp_root_path import opencsp_code_dir -import opencsp.common.lib.tool.file_tools as ft - - -class test_save_physical_setup_file(unittest.TestCase): - def test_save_physical_setup_file(self): - """Loads data and saves test Display file""" - # Define input file directory - dir_input_sofast = join(opencsp_code_dir(), 'app/sofast/test/data/data_expected') - dir_input_def = join(opencsp_code_dir(), 'common/lib/deflectometry/test/data/data_expected') - dir_output = join(opencsp_code_dir(), 'app/sofast/test/data/output') - file_save = join(dir_output, 'test_physical_setup_file.h5') - - ft.create_directories_if_necessary(dir_output) - - # Define data files - file_screen_distortion_data = join(dir_input_sofast, 'screen_distortion_data_100_100.h5') - file_cam = join(dir_input_def, 'camera_rvec_tvec.csv') - - # Load data - name = 'Test Physical Setup File' - data_dist = load_hdf5_datasets(['pts_xy_screen_fraction', 'pts_xyz_screen_coords'], file_screen_distortion_data) - screen_distortion_data = { - 'pts_xy_screen_fraction': Vxy(data_dist['pts_xy_screen_fraction']), - 'pts_xyz_screen_coords': Vxyz(data_dist['pts_xyz_screen_coords']), - } - data_cam = np.loadtxt(file_cam, delimiter=',') - rvec = data_cam[0] - tvec = data_cam[1] - - # Save physical setup file - save_DisplayShape_file(screen_distortion_data, name, rvec, tvec, file_save) - - -if __name__ == '__main__': - unittest.main() diff --git a/opencsp/app/sofast/test/test_spatial_processing.py b/opencsp/app/sofast/test/test_spatial_processing.py index 23ba9115b..13fc8a9b4 100644 --- a/opencsp/app/sofast/test/test_spatial_processing.py +++ b/opencsp/app/sofast/test/test_spatial_processing.py @@ -7,9 +7,9 @@ import numpy as np from scipy.spatial.transform import Rotation -from opencsp.app.sofast.lib.DisplayShape import DisplayShape as Display -from opencsp.app.sofast.lib.MeasurementSofastFringe import MeasurementSofastFringe as Measurement +from opencsp.app.sofast.lib.MeasurementSofastFringe import MeasurementSofastFringe import opencsp.app.sofast.lib.spatial_processing as sp +from opencsp.app.sofast.lib.SpatialOrientation import SpatialOrientation from opencsp.common.lib.camera.Camera import Camera from opencsp.common.lib.geometry.Vxy import Vxy from opencsp.common.lib.geometry.Vxyz import Vxyz @@ -27,7 +27,7 @@ def setUpClass(cls): cls.data_file_facet = os.path.join(base_dir, 'calculations_facet/data.h5') cls.data_file_measurement = os.path.join(base_dir, 'measurement_facet.h5') - cls.display = Display.load_from_hdf(cls.data_file_facet) + cls.orientation = SpatialOrientation.load_from_hdf(cls.data_file_facet) cls.camera = Camera.load_from_hdf(cls.data_file_facet) def test_t_from_distance(self): @@ -38,14 +38,14 @@ def test_t_from_distance(self): # Load test data data = load_hdf5_datasets(datasets, self.data_file_facet) - measurement = Measurement.load_from_hdf(self.data_file_measurement) + measurement = MeasurementSofastFringe.load_from_hdf(self.data_file_measurement) # Perform calculation v_cam_optic_cam_exp = sp.t_from_distance( Vxy(data['v_mask_centroid_image']), measurement.optic_screen_dist, self.camera, - self.display.v_cam_screen_cam, + self.orientation.v_cam_screen_cam, ).data.squeeze() # Test @@ -62,7 +62,7 @@ def test_r_from_position(self): # Perform calculation r_optic_cam_exp = ( - sp.r_from_position(Vxyz(data['v_cam_optic_cam_exp']), self.display.v_cam_screen_cam).inv().as_rotvec() + sp.r_from_position(Vxyz(data['v_cam_optic_cam_exp']), self.orientation.v_cam_screen_cam).inv().as_rotvec() ) # Test @@ -100,11 +100,11 @@ def test_distance_error(self): # Load test data data = load_hdf5_datasets(datasets, self.data_file_facet) - measurement = Measurement.load_from_hdf(self.data_file_measurement) + measurement = MeasurementSofastFringe.load_from_hdf(self.data_file_measurement) # Perform calculation error_optic_screen_dist_2 = sp.distance_error( - self.display.v_cam_screen_cam, Vxyz(data['v_cam_optic_cam_refine_2']), measurement.optic_screen_dist + self.orientation.v_cam_screen_cam, Vxyz(data['v_cam_optic_cam_refine_2']), measurement.optic_screen_dist ) # Test @@ -143,7 +143,7 @@ def test_refine_v_distance(self): # Load test data data = load_hdf5_datasets(datasets, self.data_file_facet) - measurement = Measurement.load_from_hdf(self.data_file_measurement) + measurement = MeasurementSofastFringe.load_from_hdf(self.data_file_measurement) # Perform calculation r_cam_optic = Rotation.from_rotvec(data['r_optic_cam_refine_1']) @@ -151,7 +151,7 @@ def test_refine_v_distance(self): v_cam_optic_cam_refine_2 = sp.refine_v_distance( Vxyz(data['v_cam_optic_cam_refine_1']), measurement.optic_screen_dist, - self.display.v_cam_screen_cam, + self.orientation.v_cam_screen_cam, v_meas_pt_optic_cam, ).data.squeeze() diff --git a/opencsp/common/lib/deflectometry/test/test_SlopeSolver.py b/opencsp/common/lib/deflectometry/test/test_SlopeSolver.py index e7d5f72c5..42420001f 100644 --- a/opencsp/common/lib/deflectometry/test/test_SlopeSolver.py +++ b/opencsp/common/lib/deflectometry/test/test_SlopeSolver.py @@ -7,8 +7,7 @@ import numpy as np from scipy.spatial.transform import Rotation -from opencsp.app.sofast.lib.DisplayShape import DisplayShape as Display -from opencsp.app.sofast.lib.MeasurementSofastFringe import MeasurementSofastFringe as Measurement +from opencsp.app.sofast.lib.MeasurementSofastFringe import MeasurementSofastFringe from opencsp.app.sofast.lib.SpatialOrientation import SpatialOrientation from opencsp.common.lib.deflectometry.SlopeSolver import SlopeSolver from opencsp.common.lib.deflectometry.Surface2DParabolic import Surface2DParabolic @@ -42,13 +41,12 @@ def setUpClass(cls): ] # Load data data = load_hdf5_datasets(datasets, cls.data_file_facet) - display = Display.load_from_hdf(cls.data_file_facet) - measurement = Measurement.load_from_hdf(data_file_measurement) + measurement = MeasurementSofastFringe.load_from_hdf(data_file_measurement) + ori = SpatialOrientation.load_from_hdf(cls.data_file_facet) # Create spatial orientation object r_cam_optic = Rotation.from_rotvec(data['r_optic_cam_refine_1']).inv() v_cam_optic_cam = Vxyz(data['v_cam_optic_cam_refine_2']) - ori = SpatialOrientation(display.r_cam_screen, display.v_cam_screen_cam) ori.orient_optic_cam(r_cam_optic, v_cam_optic_cam) # Perform calculations diff --git a/opencsp/test/data/measurements_sofast_fixed/spatial_orientation.h5 b/opencsp/test/data/measurements_sofast_fixed/spatial_orientation.h5 index 61859cc1e..b7147e918 100644 Binary files a/opencsp/test/data/measurements_sofast_fixed/spatial_orientation.h5 and b/opencsp/test/data/measurements_sofast_fixed/spatial_orientation.h5 differ diff --git a/opencsp/test/data/measurements_sofast_fringe/calculations_facet/data.h5 b/opencsp/test/data/measurements_sofast_fringe/calculations_facet/data.h5 index e91bf8322..f39e5f9a0 100644 Binary files a/opencsp/test/data/measurements_sofast_fringe/calculations_facet/data.h5 and b/opencsp/test/data/measurements_sofast_fringe/calculations_facet/data.h5 differ diff --git a/opencsp/test/data/measurements_sofast_fringe/calculations_facet/data_distorted_3d.h5 b/opencsp/test/data/measurements_sofast_fringe/calculations_facet/data_distorted_3d.h5 index 97f2687a5..06af13cb9 100644 Binary files a/opencsp/test/data/measurements_sofast_fringe/calculations_facet/data_distorted_3d.h5 and b/opencsp/test/data/measurements_sofast_fringe/calculations_facet/data_distorted_3d.h5 differ diff --git a/opencsp/test/data/measurements_sofast_fringe/calculations_facet/data_no_ls.h5 b/opencsp/test/data/measurements_sofast_fringe/calculations_facet/data_no_ls.h5 index 4825f831f..8dcf1b17a 100644 Binary files a/opencsp/test/data/measurements_sofast_fringe/calculations_facet/data_no_ls.h5 and b/opencsp/test/data/measurements_sofast_fringe/calculations_facet/data_no_ls.h5 differ diff --git a/opencsp/test/data/measurements_sofast_fringe/calculations_facet/data_plano.h5 b/opencsp/test/data/measurements_sofast_fringe/calculations_facet/data_plano.h5 index 1dab36b3b..30ad264fc 100644 Binary files a/opencsp/test/data/measurements_sofast_fringe/calculations_facet/data_plano.h5 and b/opencsp/test/data/measurements_sofast_fringe/calculations_facet/data_plano.h5 differ diff --git a/opencsp/test/data/measurements_sofast_fringe/calculations_facet/data_rectangular.h5 b/opencsp/test/data/measurements_sofast_fringe/calculations_facet/data_rectangular.h5 index 7671ef56f..4a565a681 100644 Binary files a/opencsp/test/data/measurements_sofast_fringe/calculations_facet/data_rectangular.h5 and b/opencsp/test/data/measurements_sofast_fringe/calculations_facet/data_rectangular.h5 differ diff --git a/opencsp/test/data/measurements_sofast_fringe/calculations_facet_ensemble/data.h5 b/opencsp/test/data/measurements_sofast_fringe/calculations_facet_ensemble/data.h5 index f2e1e88e1..3d120ceec 100644 Binary files a/opencsp/test/data/measurements_sofast_fringe/calculations_facet_ensemble/data.h5 and b/opencsp/test/data/measurements_sofast_fringe/calculations_facet_ensemble/data.h5 differ diff --git a/opencsp/test/data/measurements_sofast_fringe/calculations_undefined_mirror/data.h5 b/opencsp/test/data/measurements_sofast_fringe/calculations_undefined_mirror/data.h5 index 941c48f81..7fc5b8108 100644 Binary files a/opencsp/test/data/measurements_sofast_fringe/calculations_undefined_mirror/data.h5 and b/opencsp/test/data/measurements_sofast_fringe/calculations_undefined_mirror/data.h5 differ diff --git a/opencsp/test/data/measurements_sofast_fringe/display_distorted_2d.h5 b/opencsp/test/data/measurements_sofast_fringe/display_distorted_2d.h5 index 2c35e447a..7b91d5937 100644 Binary files a/opencsp/test/data/measurements_sofast_fringe/display_distorted_2d.h5 and b/opencsp/test/data/measurements_sofast_fringe/display_distorted_2d.h5 differ diff --git a/opencsp/test/data/measurements_sofast_fringe/display_distorted_3d.h5 b/opencsp/test/data/measurements_sofast_fringe/display_distorted_3d.h5 index 5b2321a54..7ade1407e 100644 Binary files a/opencsp/test/data/measurements_sofast_fringe/display_distorted_3d.h5 and b/opencsp/test/data/measurements_sofast_fringe/display_distorted_3d.h5 differ diff --git a/opencsp/test/data/measurements_sofast_fringe/display_rectangular.h5 b/opencsp/test/data/measurements_sofast_fringe/display_rectangular.h5 index a5ef6e976..d7066023d 100644 Binary files a/opencsp/test/data/measurements_sofast_fringe/display_rectangular.h5 and b/opencsp/test/data/measurements_sofast_fringe/display_rectangular.h5 differ diff --git a/opencsp/test/data/measurements_sofast_fringe/spatial_orientation.h5 b/opencsp/test/data/measurements_sofast_fringe/spatial_orientation.h5 new file mode 100644 index 000000000..4bae84596 Binary files /dev/null and b/opencsp/test/data/measurements_sofast_fringe/spatial_orientation.h5 differ