diff --git a/doc/source/example/sofast_fringe/config.rst b/doc/source/example/sofast_fringe/config.rst index 45a2ceb4a..851326e35 100644 --- a/doc/source/example/sofast_fringe/config.rst +++ b/doc/source/example/sofast_fringe/config.rst @@ -37,6 +37,15 @@ Generating Standard Mirror Output Plots :members: :show-inheritance: + +Running SOFAST in Debug Mode +============================ + +.. currentmodule:: example.sofast_fringe.example_process_in_debug_mode + +.. automodule:: example.sofast_fringe.example_process_in_debug_mode + + Generate Rectangular Screen Definition ====================================== @@ -46,6 +55,7 @@ Generate Rectangular Screen Definition :members: :show-inheritance: + Process SOFAST Temperature Experiment Data ========================================== diff --git a/example/sofast_fringe/data/input/incorrect_facet_definition.json b/example/sofast_fringe/data/input/incorrect_facet_definition.json new file mode 100644 index 000000000..01a3d829b --- /dev/null +++ b/example/sofast_fringe/data/input/incorrect_facet_definition.json @@ -0,0 +1,12 @@ +{ + "v_facet_corners": { + "x": [0.606, -0.606, -0.606, 0.606], + "y": [-0.606, -0.606, 1.606, 1.606], + "z": [0.0, 0.0, 0.0, 0.0] + }, + "v_centroid_facet": { + "x": [0.0], + "y": [0.0], + "z": [0.0] + } +} diff --git a/example/sofast_fringe/example_process_in_debug_mode.py b/example/sofast_fringe/example_process_in_debug_mode.py new file mode 100644 index 000000000..044a18c98 --- /dev/null +++ b/example/sofast_fringe/example_process_in_debug_mode.py @@ -0,0 +1,127 @@ +""" +Processes SOFAST data with debug mode enabled. + +This module provides a function to process optical measurements using the SOFAST +framework while enabling debug mode. The function performs the following tasks: +loading necessary data files, calibrating fringe images, and processing the +measurements. If an error occurs during processing, debug figures are saved for +further analysis. + +Usage: + To execute the processing with debug mode, run the module directly. The output + figures and log files will be saved in a specified directory structure. +""" + +# ChatGPT 4o-mini assisted with docstring creation + +from os.path import join, dirname + +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.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 +import opencsp.common.lib.tool.file_tools as ft +import opencsp.common.lib.tool.log_tools as lt + + +def example_process_in_debug_mode(): + """ + Processes SOFAST data with debug mode enabled. This example intentionally runs SOFAST + on a single facet with erroneous facet corner location information. When SOFAST is in + debug mode, the cause of the most common errors can easily be diagnosed by looking at + the produced figures from debug mode. Run this script as-is, the look through the output + figures produced (location is printed in terminal) to see the cause of the error (the + erroneous facet corner definition file). + + This function performs the following steps: + + 1. Sets up the necessary directories and logging for output. + 2. Loads required data files, including camera, display, spatial orientation, + measurement, calibration, and facet definition data. + 3. Calibrates the fringe images from the measurement data. + 4. Instantiates a SOFAST processing object and enables debug mode. + 5. Processes the optical data for a single facet. If an error occurs during + processing, all debug figures are saved to the specified output directory. + + Notes + ----- + The function needs the following files to run: + + - Measurement data (HDF5 format) + - Camera calibration data (HDF5 format) + - Display shape data (HDF5 format) + - Spatial orientation data (HDF5 format) + - Image calibration data (HDF5 format) + - Facet definition data (JSON format) + + Example + ------- + To process the SOFAST data with debug mode enabled, simply call the function: + + >>> example_process_in_debug_mode() + + The resulting debug figures will be saved in the output directory if an error + occurs during processing. + """ + # 1. General setup + # ================ + + # Define save dir + dir_save = join(dirname(__file__), "data/output/sofast_with_debug_mode_on") + ft.create_directories_if_necessary(dir_save) + + # Set up logger + lt.logger(join(dir_save, "log.txt"), lt.log.INFO) + + # Define sample data directory + dir_data_sofast = join(opencsp_code_dir(), "test/data/sofast_fringe") + dir_data_common = join(opencsp_code_dir(), "test/data/sofast_common") + + # Directory Setup + file_measurement = join(dir_data_sofast, "data_measurement/measurement_facet.h5") + file_camera = join(dir_data_common, "camera_sofast_downsampled.h5") + file_display = join(dir_data_common, "display_distorted_2d.h5") + file_orientation = join(dir_data_common, "spatial_orientation.h5") + file_calibration = join(dir_data_sofast, "data_measurement/image_calibration.h5") + file_facet = join(dirname(__file__), "data/input/incorrect_facet_definition.json") + + # 2. Load saved single facet Sofast collection 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) + + # 3. Processes data with SOFAST and handle errors + # =============================================== + # Define surface definition (parabolic surface), this is the mirror + surface = Surface2DParabolic(initial_focal_lengths_xy=(300.0, 300.0), robust_least_squares=True, downsample=10) + + # Calibrate fringes - (aka sinosoidal image) + measurement.calibrate_fringe_images(calibration) + + # Instantiate sofast object + sofast = Sofast(measurement, orientation, camera, display) + + # Turn on debug mode + sofast.params.debug_geometry.debug_active = True + + # Process + try: + sofast.process_optic_singlefacet(facet_data, surface) + except ValueError: + # Save all debug figures + lt.info(f'An error occured when processing SOFAST data. Saving all debug figures to {dir_save}.') + for idx, fig in enumerate(sofast.params.debug_geometry.figures): + fig.savefig(join(dir_save, f'{idx:02d}.png')) + + +if __name__ == "__main__": + example_process_in_debug_mode() diff --git a/opencsp/app/sofast/lib/process_optics_geometry.py b/opencsp/app/sofast/lib/process_optics_geometry.py index 92a94ed03..1bfe76625 100644 --- a/opencsp/app/sofast/lib/process_optics_geometry.py +++ b/opencsp/app/sofast/lib/process_optics_geometry.py @@ -159,9 +159,13 @@ def process_singlefacet_geometry( plt.title("Expected Optic Corners") # Refine locations of optic corners with mask - prs = [params.perimeter_refine_axial_search_dist, params.perimeter_refine_perpendicular_search_dist] - loop_facet_image_refine = ip.refine_mask_perimeter(loop_optic_image_exp, v_edges_image, *prs) - data_image_processing_facet.loop_facet_image_refine = loop_facet_image_refine + try: + prs = [params.perimeter_refine_axial_search_dist, params.perimeter_refine_perpendicular_search_dist] + loop_facet_image_refine = ip.refine_mask_perimeter(loop_optic_image_exp, v_edges_image, *prs) + data_image_processing_facet.loop_facet_image_refine = loop_facet_image_refine + except ValueError as er: + lt.critical(repr(er)) + lt.error_and_raise(ValueError, "SOFAST failed to find the corners of the optic.") # Plot refined optic corners if debug.debug_active: @@ -516,12 +520,16 @@ def process_multifacet_geometry( plt.title("Expected Perimeter Points") # Refine perimeter points - args = [ - params_geometry.perimeter_refine_axial_search_dist, - params_geometry.perimeter_refine_perpendicular_search_dist, - ] - loop_ensemble_image_refine = ip.refine_mask_perimeter(loop_ensemble_exp, v_edges_image, *args) - data_image_processing_general.loop_optic_image_refine = loop_ensemble_image_refine + try: + args = [ + params_geometry.perimeter_refine_axial_search_dist, + params_geometry.perimeter_refine_perpendicular_search_dist, + ] + loop_ensemble_image_refine = ip.refine_mask_perimeter(loop_ensemble_exp, v_edges_image, *args) + data_image_processing_general.loop_optic_image_refine = loop_ensemble_image_refine + except ValueError as er: + lt.critical(repr(er)) + lt.error_and_raise(ValueError, "SOFAST failed to find the corners of the optic.") # Plot refined perimeter points if debug.debug_active: @@ -558,9 +566,15 @@ def process_multifacet_geometry( ] loops_facets_refined: list[LoopXY] = [] for idx in range(num_facets): - loop = ip.refine_facet_corners(v_facet_corners_image_exp[idx], v_uv_facet_cent_exp[idx], v_edges_image, *args) - loops_facets_refined.append(loop) - data_image_processing_facet[idx].loop_facet_image_refine = loop + try: + loop = ip.refine_facet_corners( + v_facet_corners_image_exp[idx], v_uv_facet_cent_exp[idx], v_edges_image, *args + ) + loops_facets_refined.append(loop) + data_image_processing_facet[idx].loop_facet_image_refine = loop + except ValueError as er: + lt.critical(repr(er)) + lt.error_and_raise(ValueError, "SOFAST failed to find the corners of the optic.") # Plot refined perimeter points if debug.debug_active: diff --git a/opencsp/test/test_DocStringsExist.py b/opencsp/test/test_DocStringsExist.py index 777a8de94..0661261cf 100644 --- a/opencsp/test/test_DocStringsExist.py +++ b/opencsp/test/test_DocStringsExist.py @@ -3,12 +3,14 @@ # Assume opencsp is in PYTHONPATH import opencsp as opencsp +import example.sofast_fringe.example_process_in_debug_mode import example.sofast_calibration.example_calibration_screen_shape # Examples import example.sofast_fringe.example_make_rectangular_screen_definition import example.sofast_fringe.sofast_temperature_analysis + # TODO: why aren't these imported from import opencsp as opencsp above from opencsp.app.camera_calibration import CameraCalibration from opencsp.app.camera_calibration.lib.ViewAnnotatedImages import ViewAnnotatedImages @@ -183,6 +185,7 @@ class test_Docstrings(unittest.TestCase): # TODO: example_sofast_fringe_list # TODO: example_targetcolor_list example_list = [ + example.sofast_fringe.example_process_in_debug_mode, example.sofast_fringe.sofast_temperature_analysis, example.sofast_fringe.example_make_rectangular_screen_definition, example.sofast_calibration.example_calibration_screen_shape,