diff --git a/contrib/app/sofast/README.md b/contrib/app/sofast/README.md index 350e2e2df..a64880cc9 100644 --- a/contrib/app/sofast/README.md +++ b/contrib/app/sofast/README.md @@ -2,12 +2,12 @@ Code under this directory is not actively maintained by the OpenCSP team. -These examples require additional hardware and data. +This is experimental code, still in early stages of exploration and development. -The Sofast Contributed example suite contains examples on how to use Sofast to collect data. +These examples require additional hardware and data. ## Example file description: | File | Description | | :--- | :--- | -| run_and_characterize_sofast_1_cam.py | Runs Sofast (requires camera/projector/etc.) with a single camera and characterizes data as a single facet data collection. | -| run_and_characterize_sofast_2_cam.py | Runs Sofast (requires two cameras/projector/etc.) with two cameras simultaneously and characterizes data as a single facet data collection | +| load_saved_data.py | Loads saved SofastFringe data from HDF5 file. | +| sofast_command_line_interface.py | Sofast Fixed and Fringe command line interface. | diff --git a/contrib/app/sofast/run_and_characterize_sofast_1_cam.py b/contrib/app/sofast/run_and_characterize_sofast_1_cam.py deleted file mode 100644 index 53f44bcc1..000000000 --- a/contrib/app/sofast/run_and_characterize_sofast_1_cam.py +++ /dev/null @@ -1,153 +0,0 @@ -"""Example script that runs SOFAST on a single facet with one camera - -- Runs SOFAST collecion -- Characterizes optic -- Plots slope map - -NOTE: must be run with a computer connected to a working SOFAST system. This includes a camera, -mirror, screen, and system layout calibration files. -""" - -import os - -import numpy as np - -from opencsp.app.sofast.lib.DisplayShape import DisplayShape as Display -from opencsp.app.sofast.lib.DefinitionFacet import DefinitionFacet -from opencsp.app.sofast.lib.Fringes import Fringes -from opencsp.app.sofast.lib.ImageCalibrationScaling import ImageCalibrationScaling -from opencsp.app.sofast.lib.ProcessSofastFringe import ProcessSofastFringe as Sofast -from opencsp.app.sofast.lib.SystemSofastFringe import SystemSofastFringe -from opencsp.common.lib.camera.Camera import Camera -from opencsp.common.lib.deflectometry.ImageProjection import ImageProjection -from opencsp.common.lib.camera.ImageAcquisition_DCAM_mono import ImageAcquisition -from opencsp.common.lib.geometry.Pxyz import Pxyz - - -def main(): - # Define file locations - data_dir = '../../sofast_2_system_calibration_files/' - - file_image_projection = os.path.join(data_dir, 'Image_Projection_optics_lab_landscape_rectangular.h5') - file_display = os.path.join(data_dir, 'Display_optics_lab_landscape_distorted2D.h5') - file_camera = os.path.join(data_dir, 'Camera_optics_lab_landscape.h5') - file_facet = os.path.join(data_dir, 'Facet_NSTTF.json') - output_file = None - - # Define measurement parameters - dist_optic_screen = 10.516 # meter - optic_name = 'NSTTF Facet' - optic_measure_point = Pxyz((0, 0, 0)) # meter - - # Load data - camera = Camera.load_from_hdf(file_camera) - display = Display.load_from_hdf(file_display) - facet_data = DefinitionFacet.load_from_json(file_facet) - - # Define surface fitting parameters - surface_data = dict( - surface_type='parabolic', initial_focal_lengths_xy=(100.0, 100.0), robust_least_squares=False, downsample=10 - ) - - # Create fringe object - periods_x = [0.9, 4.0, 16.0, 64.0] - periods_y = [0.9, 4.0, 16.0, 64.0] - fringes = Fringes(periods_x, periods_y) - - # Load ImageProjection - im_proj = ImageProjection.load_from_hdf_and_display(file_image_projection) - - # Initialize variables - calibrations: list[ImageCalibrationScaling] = [] - - # Load ImageAcquisition - im_acqu = ImageAcquisition(0) - im_acqu.frame_size = (1626, 1236) - im_acqu.gain = 230 - im_acqu.exposure_time = 230025 - im_acqu.frame_rate = np.max([0.014902, 1.05 / im_acqu.exposure_time]) - - # Create System - system = SystemSofastFringe(im_proj, im_acqu) - - # Calibrate camera exposure - def func_calibrate_exposure(): - print('Calibrating camera exposure') - system.run_camera_exposure_calibration(run_next=system.run_next_in_queue) - - # Capture calibration data - def func_capture_calibration_frames(): - print('Capturing display-camera response calibration data') - system.run_display_camera_response_calibration(res=10, run_next=system.run_next_in_queue) - - # Process calibration data - def func_process_calibration_data(): - print('Processing calibration data') - calibration_images = system.get_calibration_images() - calibrations.append(ImageCalibrationScaling.from_data(calibration_images[0], system.calibration_display_values)) - system.run_next_in_queue() - - # Load fringe object - def func_load_fringes(): - print('Loading fringes') - # Use the largest minimum display values - min_disp_value = calibrations[0].calculate_min_display_camera_values()[0] - - # Load fringe objects with min display value - system.set_fringes(fringes, min_disp_value) - - system.run_next_in_queue() - - # Capture mask and fringe images - def func_capture_fringes(): - print('Capturing mask and fringe images') - system.capture_mask_and_fringe_images(run_next=system.run_next_in_queue) - - # Close all windows and cameras - def func_close_all(): - print('Closing all.') - system.close_all() - - # Set process queue - funcs = [ - func_calibrate_exposure, - func_capture_calibration_frames, - func_process_calibration_data, - func_load_fringes, - func_capture_fringes, - func_close_all, - ] - system.set_queue(funcs) - - # Run system - system.run() - - # Save data - print('Saving Data') - - # Get Measurement object - measurement = system.get_measurements(optic_measure_point, dist_optic_screen, optic_name)[0] - calibration = calibrations[0] - - # Process data - print('Processing data') - - # Calibrate measurements - measurement.calibrate_fringe_images(calibration) - - # Instantiate SOFAST objects - sofast = Sofast(measurement, camera, display) - - # Process using optic data - sofast.process_optic_singlefacet(facet_data, surface_data) - - # Save processed data, calibration, and measurement data - if output_file is not None: - calibration.save_to_hdf(output_file) - measurement.save_to_hdf(output_file) - sofast.save_to_hdf(output_file) - - -# Start program -if __name__ == '__main__': - main() diff --git a/contrib/app/sofast/run_and_characterize_sofast_2_cam.py b/contrib/app/sofast/run_and_characterize_sofast_2_cam.py deleted file mode 100644 index 93404b8a8..000000000 --- a/contrib/app/sofast/run_and_characterize_sofast_2_cam.py +++ /dev/null @@ -1,185 +0,0 @@ -"""Example script that runs SOFAST on a single facet with two cameras simultaneously - -- Runs SOFAST collecion -- Characterizes optic -- Plots slope map - -NOTE: must be run with a computer connected to a working SOFAST system. This includes a camera, -mirror, screen, and system layout calibration files. -""" - -import os - -import numpy as np - -from opencsp.app.sofast.lib.DisplayShape import DisplayShape as Display -from opencsp.app.sofast.lib.DefinitionFacet import DefinitionFacet -from opencsp.app.sofast.lib.Fringes import Fringes -from opencsp.app.sofast.lib.ImageCalibrationScaling import ImageCalibrationScaling -from opencsp.app.sofast.lib.ProcessSofastFringe import ProcessSofastFringe as Sofast -from opencsp.app.sofast.lib.SystemSofastFringe import SystemSofastFringe -from opencsp.common.lib.camera.Camera import Camera -from opencsp.common.lib.deflectometry.ImageProjection import ImageProjection -from opencsp.common.lib.camera.ImageAcquisition_DCAM_mono import ImageAcquisition -from opencsp.common.lib.geometry.Pxyz import Pxyz - - -def main(): - # Define file locations - data_dir = '../../sofast_2_system_calibration_files/' - - file_image_projection = os.path.join(data_dir, 'Image_Projection_optics_lab_landscape_rectangular.h5') - - file_display_0 = os.path.join(data_dir, 'Display_optics_lab_landscape_distorted2D.h5') - file_camera_0 = os.path.join(data_dir, 'Camera_optics_lab_landscape.h5') - - file_display_1 = os.path.join(data_dir, 'Display_optics_lab_landscape_distorted2D.h5') - file_camera_1 = os.path.join(data_dir, 'Camera_optics_lab_landscape.h5') - - file_facet = os.path.join(data_dir, 'Facet_NSTTF.json') - - output_file_base = None - - # Define measurement parameters - dist_optic_screen = 10.516 # meter - optic_name = 'NSTTF Facet' - optic_measure_point = Pxyz((0, 0, 0)) # meter - - # Load data - cameras = [] - cameras.append(Camera.load_from_hdf(file_camera_0)) - cameras.append(Camera.load_from_hdf(file_camera_1)) - - displays = [] - displays.append(Display.load_from_hdf(file_display_0)) - displays.append(Display.load_from_hdf(file_display_1)) - - # Load facet JSON - facet_data = DefinitionFacet.load_from_json(file_facet) - - # Define surface fitting parameters - surface_data = [ - dict( - surface_type='parabolic', initial_focal_lengths_xy=(100.0, 100.0), robust_least_squares=False, downsample=10 - ) - ] - - # Create fringe object - periods_x = [0.9, 4.0, 16.0, 64.0] - periods_y = [0.9, 4.0, 16.0, 64.0] - fringes = Fringes(periods_x, periods_y) - - # Load ImageProjection - im_proj = ImageProjection.load_from_hdf_and_display(file_image_projection) - - # Process calibration images - calibrations: list[ImageCalibrationScaling] = [] - - # Load ImageAcquisition (optics lab) - im_acqu_0 = ImageAcquisition(0) - im_acqu_0.frame_size = (1626, 1236) - im_acqu_0.gain = 230 - im_acqu_0.exposure_time = 230025 - im_acqu_0.frame_rate = np.max([0.014902, 1.05 / im_acqu_0.exposure_time]) - - im_acqu_1 = ImageAcquisition(1) - im_acqu_1.frame_size = (1626, 1236) - im_acqu_1.gain = 230 - im_acqu_1.exposure_time = 230025 - im_acqu_1.frame_rate = np.max([0.014902, 1.05 / im_acqu_1.exposure_time]) - - # Create System - system = SystemSofastFringe(im_proj, [im_acqu_0, im_acqu_1]) - - # Calibrate camera exposure - def func_calibrate_exposure(): - print('Calibrating camera exposure') - system.run_camera_exposure_calibration(run_next=system.run_next_in_queue) - - # Capture calibration data - def func_capture_calibration_frames(): - print('Capturing display-camera response calibration data') - system.run_display_camera_response_calibration(res=10, run_next=system.run_next_in_queue) - - # Process calibration data - def func_process_calibration_data(): - print('Processing calibration data') - calibration_images = system.get_calibration_images() - for ims in calibration_images: - calibrations.append(ImageCalibrationScaling.from_data(ims, system.calibration_display_values)) - system.run_next_in_queue() - - # Load fringe object - def func_load_fringes(): - print('Loading fringes') - # Use the largest minimum display values - min_disp_vals = [] - for c in calibrations: - min_disp_vals.append(c.calculate_min_display_camera_values()[0]) - min_disp_value = np.max(min_disp_vals) - - # Load fringe objects - system.set_fringes(fringes, min_disp_value) - - system.run_next_in_queue() - - # Capture mask and fringe images - def func_capture_fringes(): - print('Capturing mask and fringe images') - system.capture_mask_and_fringe_images(run_next=system.run_next_in_queue) - - # Close all windows and cameras - def func_close_all(): - print('Closing all.') - system.close_all() - - # Set process queue - funcs = [ - func_calibrate_exposure, - func_capture_calibration_frames, - func_process_calibration_data, - func_load_fringes, - func_capture_fringes, - func_close_all, - ] - # system.set_queue(funcs) - system.set_queue(funcs) - - # Run system - system.run() - - # Save data - print('Saving Data') - - # Get Measurement object - measurements = system.get_measurements(optic_measure_point, dist_optic_screen, optic_name) - - # Process data - print('Processing data') - - # Calibrate measurements - for m, c in zip(measurements, calibrations): - m.calibrate_fringe_images(c) - - # Instantiate SOFAST objects - sofasts: list[Sofast] = [] - for m, c, d in zip(measurements, cameras, displays): - sofasts.append(Sofast(m, c, d)) - - # Process using optic data - for s in sofasts: - s.process_optic_singlefacet(facet_data, surface_data) - - # Save processed data, calibration, and measurement data - if output_file_base is not None: - output_files = [output_file_base + f'_{idx_meas:d}.h5' for idx_meas in [0, 1]] - for idx_meas, (c, m) in enumerate(zip(calibrations, measurements)): - c.save_to_hdf() - m.save_to_hdf(output_files[idx_meas]) - for idx_meas, s in enumerate(sofasts): - s.save_to_hdf(output_files[idx_meas]) - - -# Start program -if __name__ == '__main__': - main() diff --git a/contrib/app/sofast/run_fixed_pattern_projection.py b/contrib/app/sofast/run_fixed_pattern_projection.py index f597b4300..8592fc082 100644 --- a/contrib/app/sofast/run_fixed_pattern_projection.py +++ b/contrib/app/sofast/run_fixed_pattern_projection.py @@ -4,7 +4,7 @@ from os.path import join -from opencsp.app.sofast.lib.SystemSofastFixed import SystemSofastFixed +from opencsp.app.sofast.lib.PatternSofastFixed import PatternSofastFixed from opencsp.common.lib.opencsp_path.opencsp_root_path import opencsp_code_dir from opencsp.common.lib.deflectometry.ImageProjection import ImageProjection @@ -21,10 +21,10 @@ def run_fixed_pattern_projection(): # Define SofastFixedPattern object width_pattern = 3 spacing_pattern = 6 - fixed_pattern = SystemSofastFixed(im_proj.size_x, im_proj.size_y, width_pattern, spacing_pattern) + fixed_pattern = PatternSofastFixed(im_proj.size_x, im_proj.size_y, width_pattern, spacing_pattern) + image = fixed_pattern.get_image('uint8', 255, 'square') # Project image (press escape to close window) - image = fixed_pattern.get_image('uint8', 255, 'circle') im_proj.display_image_in_active_area(image) im_proj.run() diff --git a/contrib/app/sofast/sofast_command_line_interface.py b/contrib/app/sofast/sofast_command_line_interface.py new file mode 100644 index 000000000..6eb00100d --- /dev/null +++ b/contrib/app/sofast/sofast_command_line_interface.py @@ -0,0 +1,372 @@ +import glob +from os.path import join, dirname, abspath + +import matplotlib.pyplot as plt + +from opencsp.app.sofast.lib.DefinitionFacet import DefinitionFacet +from opencsp.app.sofast.lib.DisplayShape import DisplayShape +from opencsp.app.sofast.lib.DotLocationsFixedPattern import DotLocationsFixedPattern +from opencsp.app.sofast.lib.Fringes import Fringes +from opencsp.app.sofast.lib.ImageCalibrationScaling import ImageCalibrationScaling +from opencsp.app.sofast.lib.ProcessSofastFixed import ProcessSofastFixed +from opencsp.app.sofast.lib.ProcessSofastFringe import ProcessSofastFringe +from opencsp.app.sofast.lib.SpatialOrientation import SpatialOrientation +from opencsp.app.sofast.lib.SystemSofastFringe import SystemSofastFringe +from opencsp.app.sofast.lib.SystemSofastFixed import SystemSofastFixed +from opencsp.common.lib.camera.Camera import Camera +from opencsp.common.lib.camera.ImageAcquisition_DCAM_mono import ImageAcquisition +from opencsp.common.lib.deflectometry.ImageProjection import ImageProjection +from opencsp.common.lib.deflectometry.Surface2DParabolic import Surface2DParabolic +from opencsp.common.lib.geometry.Vxy import Vxy +from opencsp.common.lib.geometry.Vxyz import Vxyz +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.log_tools as lt +from opencsp.common.lib.tool.time_date_tools import current_date_time_string_forfile as timestamp + + +def main( + image_acquisition: ImageAcquisition, # Common imputs + image_projection: ImageProjection, + spatial_orientation: SpatialOrientation, + camera: Camera, + facet_definition: DefinitionFacet, + measure_point_optic: Vxyz, + dist_optic_screen: float, + name_optic: str, + display_shape: DisplayShape, # Sofast Fringe inputs + fringes: Fringes, + surface_fringe: Surface2DParabolic, + fixed_pattern_dot_locs: DotLocationsFixedPattern, # Sofast fixed inputs + origin: Vxy, + surface_fixed: Surface2DParabolic, + dir_save_fringe_calibration: str, # Saving inputs + dir_save_fixed: str, + dir_save_fringe: str, + res_plot: float, +) -> None: + """Main system_fringe runner + + Parameters + ---------- + image_acquisition : ImageAcquisition + Image acquisition object + spatial_orientation : SpatialOrientation + Spatial orientation object + camera : Camera + Camera object + facet_definition : DefinitionFacet + Facet definition + measure_point_optic : Vxyz + Measure point on optic + dist_optic_screen : float + Distance from measure point to screen + name_optic : str + Name of optic + display_shape : DisplayShape + Display shape object + surface_fringe : Surface2DParabolic + Surface definition for Sofast fringe + fixed_pattern_dot_locs : DotLocationsFixedPattern + Dot location definition object + surface_fixed : Surface2DParabolic + Surface definition of Sofast Fixed + dir_save_fringe_calibration : str + Save path for Sofast Fringe calibration files + dir_save_fringe : str + Save path for Sofast Fringe measurement files + res_plot : float + Save path for Sofast Fixed measurement files + """ + + system_fringe = SystemSofastFringe(image_acquisition) + + system_fixed = SystemSofastFixed(image_acquisition) + system_fixed.set_pattern_parameters(3, 6) + + sofast_fixed = ProcessSofastFixed(spatial_orientation, camera, fixed_pattern_dot_locs, facet_definition) + + def func_process_gray_levels_cal(): + """Processes the grey levels calibration data""" + calibration_images = system_fringe.get_calibration_images()[0] + calibration = ImageCalibrationScaling.from_data(calibration_images, system_fringe._calibration_display_values) + calibration.save_to_hdf(join(dir_save_fringe_calibration, f'image_calibration_scaling_{timestamp():s}.h5')) + system_fringe.calibration = calibration + system_fringe.set_fringes(fringes) + lt.info(f'{timestamp()} ImageCalibration data loaded into SystemSofastFringe') + system_fringe.run_next_in_queue() + + def func_show_crosshairs(): + """Shows crosshairs""" + im_proj_instance = image_projection.instance() + im_proj_instance.show_crosshairs() + system_fringe.run_next_in_queue() + + def func_process_sofast_fringe_data(): + """Processes Sofast Fringe data""" + lt.debug(f'{timestamp():s} Processing Sofast Fringe data') + + # Get Measurement object + measurement = system_fringe.get_measurements(measure_point_optic, dist_optic_screen, name_optic)[0] + + # Calibrate fringe images + measurement.calibrate_fringe_images(system_fringe.calibration) + + # Instantiate ProcessSofastFringe + sofast = ProcessSofastFringe(measurement, spatial_orientation, camera, display_shape) + + # Process + sofast.process_optic_singlefacet(facet_definition, surface_fringe) + + # Plot optic + mirror = sofast.get_optic().mirror + + lt.debug(f'{timestamp():s} Plotting Sofast Fringe data') + figure_control = rcfg.RenderControlFigure(tile_array=(1, 1), tile_square=True) + axis_control_m = rca.meters() + fig_record = fm.setup_figure(figure_control, axis_control_m, title='') + mirror.plot_orthorectified_slope(res_plot, clim=7, axis=fig_record.axis) + fig_record.save(dir_save_fringe, f'{timestamp():s}_slope_magnitude_fringe', 'png') + fig_record.close() + + # Save processed sofast data + sofast.save_to_hdf(f'{dir_save_fringe:s}/{timestamp():s}_data_sofast_fringe.h5') + lt.debug(f'{timestamp():s} Sofast Fringe data saved to HDF5') + + # Continue + system_fringe.run_next_in_queue() + + def func_process_sofast_fixed_data(): + """Process Sofast Fixed data""" + # Get Measurement object + measurement = system_fixed.get_measurement(measure_point_optic, dist_optic_screen, origin, name=name_optic) + sofast_fixed.load_measurement_data(measurement) + + # Process + sofast_fixed.process_single_facet_optic(surface_fixed) + + # Plot optic + mirror = sofast_fixed.get_mirror() + + lt.debug(f'{timestamp():s} Plotting Sofast Fixed data') + figure_control = rcfg.RenderControlFigure(tile_array=(1, 1), tile_square=True) + axis_control_m = rca.meters() + fig_record = fm.setup_figure(figure_control, axis_control_m, title='') + mirror.plot_orthorectified_slope(res_plot, clim=7, axis=fig_record.axis) + fig_record.save(dir_save_fixed, f'{timestamp():s}_slope_magnitude_fixed', 'png') + + # Save processed sofast data + sofast_fixed.save_to_hdf(f'{dir_save_fixed:s}/{timestamp():s}_data_sofast_fixed.h5') + lt.debug(f'{timestamp():s} Sofast Fixed data saved to HDF5') + + # Continue + system_fixed.run_next_in_queue() + + def func_save_measurement_fixed(): + """Save fixed measurement files""" + measurement = system_fixed.get_measurement(measure_point_optic, dist_optic_screen, origin, name=name_optic) + measurement.save_to_hdf(f'{dir_save_fixed:s}/{timestamp():s}_measurement_fixed.h5') + system_fixed.run_next_in_queue() + + def func_save_measurement_fringe(): + """Saves measurement to HDF file""" + measurement = system_fringe.get_measurements(measure_point_optic, dist_optic_screen, name_optic)[0] + measurement.save_to_hdf(f'{dir_save_fringe:s}/{timestamp():s}_measurement_fringe.h5') + system_fringe.run_next_in_queue() + + def func_pause(): + """Pauses for 200 ms""" + system_fringe.root.after(200, system_fringe.run_next_in_queue) + + def func_load_last_sofast_fringe_image_cal(): + """Loads last ImageCalibration object""" + files = glob.glob(join(dir_save_fringe_calibration, 'image_calibration_scaling*.h5')) + files.sort() + file = files[-1] + image_calibration = ImageCalibrationScaling.load_from_hdf(file) + system_fringe.calibration = image_calibration + system_fringe.set_fringes(fringes) + lt.info(f'{timestamp()} Loaded image calibration file: {file}') + system_fringe.run_next_in_queue() + + def func_gray_levels_cal(): + """Runs gray level calibration sequence""" + system_fringe.run_display_camera_response_calibration(res=10, run_next=system_fringe.run_next_in_queue) + + def func_show_cam_image(): + """Shows a camera image""" + image = image_acquisition.get_frame() + plt.imshow(image) + plt.show() + + def func_user_input(): + print('\n') + print('Value Command') + print('------------------') + print('mrp run Sofast Fringe measurement and process/save') + print('mrs run Sofast Fringe measurement and save only') + print('mip run Sofast Fixed measurement and process/save') + print('mis run Sofast Fixed measurement and save only') + print('ce calibrate camera exposure') + print('cr calibrate camera-projector response') + print('lr load most recent camera-projector response calibration file') + print('q quit and close all') + print('im show image from camera.') + print('cross show crosshairs') + retval = input('Input: ') + + lt.debug(f'{timestamp():s} user input: {retval:s}') + + if retval == 'mrp': + lt.info(f'{timestamp()} Running Sofast Fringe measurement and processing/saving data') + funcs = [ + lambda: system_fringe.run_measurement(system_fringe.run_next_in_queue), + func_show_crosshairs, + func_pause, + func_process_sofast_fringe_data, + func_save_measurement_fringe, + func_user_input, + ] + system_fringe.set_queue(funcs) + system_fringe.run() + elif retval == 'mrs': + lt.info(f'{timestamp()} Running Sofast Fringe measurement and saving data') + funcs = [ + lambda: system_fringe.run_measurement(system_fringe.run_next_in_queue), + func_show_crosshairs, + func_pause, + func_save_measurement_fringe, + func_user_input, + ] + system_fringe.set_queue(funcs) + system_fringe.run() + elif retval == 'mip': + lt.info(f'{timestamp()} Running Sofast Fixed measurement and processing/saving data') + funcs = [ + system_fixed.run_measurement, + func_process_sofast_fixed_data, + func_save_measurement_fixed, + func_user_input, + ] + system_fixed.set_queue(funcs) + system_fixed.run() + elif retval == 'mis': + lt.info(f'{timestamp()} Running Sofast Fixed measurement and saving data') + funcs = [system_fixed.run_measurement, func_save_measurement_fixed, func_user_input] + system_fixed.set_queue(funcs) + system_fixed.run() + elif retval == 'ce': + lt.info(f'{timestamp()} Calibrating camera exposure') + image_acquisition.calibrate_exposure() + func_user_input() + elif retval == 'cr': + lt.info(f'{timestamp()} Calibrating camera-projector response') + funcs = [func_gray_levels_cal, func_show_crosshairs, func_process_gray_levels_cal, func_user_input] + system_fringe.set_queue(funcs) + system_fringe.run() + elif retval == 'lr': + lt.info(f'{timestamp()} Loading response calibration') + funcs = [func_load_last_sofast_fringe_image_cal, func_user_input] + system_fringe.set_queue(funcs) + system_fringe.run() + elif retval == 'q': + lt.info(f'{timestamp():s} quitting') + system_fringe.close_all() + system_fixed.close_all() + return + elif retval == 'im': + func_show_cam_image() + system_fringe.set_queue([func_user_input]) + system_fringe.run() + elif retval == 'cross': + image_projection.show_crosshairs() + func_user_input() + else: + lt.error(f'{timestamp()} Command, {retval}, not recognized') + funcs = [func_user_input] + system_fringe.set_queue(funcs) + system_fringe.run() + + # Run system_fringe + system_fringe.set_queue([func_user_input]) + system_fringe.run() + + +# Start program +if __name__ == '__main__': + # Define upper level save direcory + dir_save = abspath(join(dirname(__file__), '../../../../sofast_cli')) + + # Define logger directory and set up logger + dir_log = join(dir_save, 'logs') + lt.logger(join(dir_log, f'log_{timestamp():s}.txt'), lt.log.INFO) + + # Define sofast and calibration file save directories + dir_save_fringe_in = join(dir_save, 'sofast_fringe') + dir_save_fringe_calibration_in = join(dir_save_fringe_in, 'calibration') + dir_save_fixed_in = join(dir_save, 'sofast_fixed') + + # Define directory containing Sofast calibration files + dir_cal = abspath(join(dirname(__file__), '../../../../sofast_calibration_files')) + + # Define common values + file_facet_definition_json = join(dir_cal, 'facet_NSTTF.json') + file_spatial_orientation = join(dir_cal, 'spatial_orientation_optics_lab_landscape.h5') + file_display = join(dir_cal, 'display_shape_optics_lab_landscape_square_distorted_3d_100x100.h5') + file_camera = join(dir_cal, 'camera_sofast_optics_lab_landscape.h5') + file_image_projection = join(dir_cal, 'image_projection_optics_lab_landscape_square.h5') + file_dot_locs = join(dir_cal, 'dot_locations_optics_lab_landscape_square_width3_space6.h5') + + # Load common data + facet_definition_in = DefinitionFacet.load_from_json(file_facet_definition_json) + spatial_orientation_in = SpatialOrientation.load_from_hdf(file_spatial_orientation) + display_shape_in = DisplayShape.load_from_hdf(file_display) + camera_in = Camera.load_from_hdf(file_camera) + fixed_pattern_dot_locs_in = DotLocationsFixedPattern.load_from_hdf(file_dot_locs) + + # Define common parameters + name_optic_in = 'Test optic' + res_plot_in = 0.002 # meters + measure_point_optic_in = Vxyz((0, 0, 0)) # meters + dist_optic_screen_in = 10.263 # meters + + # Define Sofast Fringe parameters + fringes_in = Fringes.from_num_periods(4, 4) + surface_fringe_in = Surface2DParabolic((100.0, 100.0), False, 10) + + # Define Sofast Fixed parameters + surface_fixed_in = Surface2DParabolic((100.0, 100.0), False, 1) + origin_in = Vxy((993, 644)) # pixels + + # Define image projection + image_projection_in = ImageProjection.load_from_hdf_and_display(file_image_projection) + image_projection_in.display_data['image_delay'] = 200 + + # Setup image acquisition + image_acquisition_in = ImageAcquisition(0) + image_acquisition_in.frame_size = (1626, 1236) + image_acquisition_in.gain = 230 + + kwargs = { + 'image_acquisition': image_acquisition_in, + 'image_projection': image_projection_in, + 'spatial_orientation': spatial_orientation_in, + 'camera': camera_in, + 'facet_definition': facet_definition_in, + 'measure_point_optic': measure_point_optic_in, + 'dist_optic_screen': dist_optic_screen_in, + 'name_optic': name_optic_in, + 'display_shape': display_shape_in, + 'fringes': fringes_in, + 'surface_fringe': surface_fringe_in, + 'fixed_pattern_dot_locs': fixed_pattern_dot_locs_in, + 'origin': origin_in, + 'surface_fixed': surface_fixed_in, + 'dir_save_fringe_calibration': dir_save_fringe_calibration_in, + 'dir_save_fixed': dir_save_fixed_in, + 'dir_save_fringe': dir_save_fringe_in, + 'res_plot': res_plot_in, + } + + main(**kwargs) diff --git a/doc/source/library_reference/app/sofast/config.rst b/doc/source/library_reference/app/sofast/config.rst index 0eba6c6da..91a370a8d 100644 --- a/doc/source/library_reference/app/sofast/config.rst +++ b/doc/source/library_reference/app/sofast/config.rst @@ -88,12 +88,12 @@ opencsp.app.sofast.lib.SystemSofastFringe :undoc-members: :show-inheritance: -opencsp.app.sofast.lib.SystemSofastFixed -======================================== +opencsp.app.sofast.lib.PatternSofastFixed +========================================= -.. currentmodule:: opencsp.app.sofast.lib.SystemSofastFixed +.. currentmodule:: opencsp.app.sofast.lib.PatternSofastFixed -.. automodule:: opencsp.app.sofast.lib.SystemSofastFixed +.. automodule:: opencsp.app.sofast.lib.PatternSofastFixed :members: :undoc-members: :show-inheritance: diff --git a/opencsp/app/sofast/SofastGUI.py b/opencsp/app/sofast/SofastGUI.py index 881130418..c3e04a806 100644 --- a/opencsp/app/sofast/SofastGUI.py +++ b/opencsp/app/sofast/SofastGUI.py @@ -37,7 +37,7 @@ def __init__(self) -> 'SofastGUI': Instantiates GUI in new window """ # Placeholder for system instances - fringe_sys: ssf.SystemSofastFringe = None + self.sys_fringe: ssf.SystemSofastFringe = None # Create tkinter object self.root = tkt.window() diff --git a/opencsp/app/sofast/lib/DotLocationsFixedPattern.py b/opencsp/app/sofast/lib/DotLocationsFixedPattern.py index a629bb088..2040b21ff 100644 --- a/opencsp/app/sofast/lib/DotLocationsFixedPattern.py +++ b/opencsp/app/sofast/lib/DotLocationsFixedPattern.py @@ -1,7 +1,7 @@ import numpy as np from numpy import ndarray -from opencsp.app.sofast.lib.SystemSofastFixed import SystemSofastFixed +from opencsp.app.sofast.lib.PatternSofastFixed import PatternSofastFixed from opencsp.app.sofast.lib.DisplayShape import DisplayShape as Display from opencsp.common.lib.geometry.Vxy import Vxy from opencsp.common.lib.geometry.Vxyz import Vxyz @@ -49,9 +49,9 @@ def __init__(self, x_dot_index: ndarray, y_dot_index: ndarray, xyz_dot_loc: ndar @classmethod def from_projection_and_display( - cls, fixed_pattern_projection: SystemSofastFixed, display: Display + cls, fixed_pattern_projection: PatternSofastFixed, display: Display ) -> 'DotLocationsFixedPattern': - """Instantiates a DotLocationsFixedPattern from a SystemSofastFixed object + """Instantiates a DotLocationsFixedPattern from a PatternSofastFixed object and a display object. This is used as a convenience if a Display calibration has already been done for a screen setup.""" # Calculate xy points in screen fractions diff --git a/opencsp/app/sofast/lib/PatternSofastFixed.py b/opencsp/app/sofast/lib/PatternSofastFixed.py new file mode 100644 index 000000000..4e2ebd929 --- /dev/null +++ b/opencsp/app/sofast/lib/PatternSofastFixed.py @@ -0,0 +1,122 @@ +from typing import Literal + +import numpy as np +from numpy import ndarray +from scipy.signal import convolve2d + + +class PatternSofastFixed: + """Class that holds parameters for displaying a Fixed Pattern for use + in fixed pattern deflectometry. + """ + + def __init__(self, size_x: int, size_y: int, width_pattern: int, spacing_pattern: int) -> 'PatternSofastFixed': + """Instantiates PatternSofastFixed class from screen geometry parameters + + Parameters + ---------- + size_x/size_y : int + Size of image x/y dimension + width_pattern : int + Width of each Fixed Pattern marker in the image, pixels + spacing_pattern : int + Spacing between (not center-to-center) pattern markers, pixels + + Attributes + ---------- + - size_x + - size_y + - width_pattern + - spacing_pattern + - x_locs_pixel + - y_locs_pixel + - nx + - ny + - x_locs_frac + - y_locs_frac + - x_indices + - y_indices + """ + # Store data + self.size_x = size_x + self.size_y = size_y + self.width_pattern = width_pattern + self.spacing_pattern = spacing_pattern + + # Create location vectors + x_locs_pixel = np.arange(0, size_x, width_pattern + spacing_pattern) + y_locs_pixel = np.arange(0, size_y, width_pattern + spacing_pattern) + + # Make vectors odd + if x_locs_pixel.size % 2 == 0: + x_locs_pixel = x_locs_pixel[:-1] + if y_locs_pixel.size % 2 == 0: + y_locs_pixel = y_locs_pixel[:-1] + + # Center in image + dx = int(float((size_x - 1) - x_locs_pixel[-1]) / 2) + dy = int(float((size_y - 1) - y_locs_pixel[-1]) / 2) + x_locs_pixel += dx + y_locs_pixel += dy + + # Save calculations + self.x_locs_pixel = x_locs_pixel + self.y_locs_pixel = y_locs_pixel + self.nx = x_locs_pixel.size + self.ny = y_locs_pixel.size + + # Calculate point indices + self.x_indices = np.arange(self.nx) - int(np.floor(float(self.nx) / 2)) + self.y_indices = np.arange(self.ny) - int(np.floor(float(self.ny) / 2)) + + # Calculate fractional screen points + self.x_locs_frac = x_locs_pixel.astype(float) / float(size_x) + self.y_locs_frac = y_locs_pixel.astype(float) / float(size_y) + + def _get_dot_image(self, dot_shape: str) -> ndarray[float]: + """Returns 2d image of individual pattern element. Active area is 1 + and inactive area is 0, dtype float. + """ + if dot_shape not in ['circle', 'square']: + raise ValueError(f'pattern_type must be one of ["circle", "square"], not {dot_shape:s}') + + if dot_shape == 'square': + return np.ones((self.width_pattern, self.width_pattern), dtype=float) + elif dot_shape == 'circle': + x, y = np.meshgrid(np.arange(self.width_pattern, dtype=float), np.arange(self.width_pattern, dtype=float)) + x -= x.mean() + y -= y.mean() + r = np.sqrt(x**2 + y**2) + return (r < float(self.width_pattern) / 2).astype(float) + + def get_image(self, dtype: str, max_int: int, dot_shape: Literal['circle', 'square'] = 'circle') -> ndarray: + """Creates a NxMx3 fixed pattern image + + Parameters + ---------- + dtype : str + Output datatype of image + max_int : int + Integer value corresponding to "white." Zero corresponds to black. + dot_shape : str + 'circle' or 'square' + + Returns + ------- + ndarray + (size_y, size_x, 3) ndarray + """ + # Create image with point locations + image = np.zeros((self.size_y, self.size_x), dtype=dtype) + locs_x, locs_y = np.meshgrid(self.x_locs_pixel, self.y_locs_pixel) + image[locs_y, locs_x] = 1 + + # Add patterns (active=1) + pattern = self._get_dot_image(dot_shape).astype(dtype) + image = convolve2d(image, pattern, mode='same') + + # Convert image to white background and black patterns + image = (1 - image) * max_int + + # Add RGB channels + return np.concatenate([image[..., None]] * 3, axis=2) diff --git a/opencsp/app/sofast/lib/SystemSofastFixed.py b/opencsp/app/sofast/lib/SystemSofastFixed.py index c46fb59de..443d30769 100644 --- a/opencsp/app/sofast/lib/SystemSofastFixed.py +++ b/opencsp/app/sofast/lib/SystemSofastFixed.py @@ -1,122 +1,186 @@ -from typing import Literal +import datetime as dt +from typing import Callable, Literal -import numpy as np -from numpy import ndarray -from scipy.signal import convolve2d +from numpy.typing import NDArray + +import opencsp.app.sofast.lib.DistanceOpticScreen as dos +from opencsp.app.sofast.lib.MeasurementSofastFixed import MeasurementSofastFixed +from opencsp.app.sofast.lib.PatternSofastFixed import PatternSofastFixed +from opencsp.common.lib.camera.ImageAcquisitionAbstract import ImageAcquisitionAbstract +from opencsp.common.lib.deflectometry.ImageProjection import ImageProjection +from opencsp.common.lib.geometry.Vxy import Vxy +from opencsp.common.lib.geometry.Vxyz import Vxyz +import opencsp.common.lib.tool.exception_tools as et +import opencsp.common.lib.tool.log_tools as lt class SystemSofastFixed: - """Class that holds parameters for displaying a Fixed Pattern for use - in fixed pattern deflectometry. - """ + """Class that orchestrates the running of a SofastFixed system""" + + def __init__(self, image_acquisition: ImageAcquisitionAbstract) -> 'SystemSofastFixed': + """ + Instantiates SystemSofastFixed class. - def __init__(self, size_x: int, size_y: int, width_pattern: int, spacing_pattern: int) -> 'SystemSofastFixed': - """Instantiates SystemSofastFixed class from screen geometry parameters + Parameters + ---------- + image_acquisition : ImageAcquisition + Loaded ImageAcquisition object. + """ + # Import here to avoid circular dependencies + import opencsp.app.sofast.lib.sofast_common_functions as scf + + # Get defaults + image_acquisition, image_projection = scf.get_default_or_global_instances( + image_acquisition_default=image_acquisition + ) + # Validate input + if image_projection is None or image_acquisition is None: + lt.error_and_raise(RuntimeError, 'Both ImageAcquisiton and ImageProjection must both be loaded.') + self.image_acquisition = image_acquisition + + # Show crosshairs + image_projection.show_crosshairs() + + # Initialize queue + self._queue_funcs = [] + + # Instantiate attributes + self.image_measurement: NDArray = None + """Measurement image, 2d numpy array""" + self.pattern_sofast_fixed: PatternSofastFixed = None + """The PatternSofastFixed object used to define the fixed pattern image""" + self.pattern_image: NDArray = None + """The fixed pattern image being projected. NxMx3 numpy array""" + + self.dtype = 'uint8' + """The data type of the image being projected""" + self.max_int = 255 + """The value that corresponds to pure white""" + self.dot_shape: Literal['circle', 'square'] = 'circle' + """The shape of the fixed pattern dots""" + self.image_delay = image_projection.display_data['image_delay'] # ms + """The delay after displaying the image to capturing an image, ms""" + + def set_pattern_parameters(self, width_pattern: int, spacing_pattern: int): + """Sets the parameters of pattern_sofast_fixed + + Upon completion, runs self.run_next_in_queue() Parameters ---------- - size_x/size_y : int - Size of image x/y dimension width_pattern : int Width of each Fixed Pattern marker in the image, pixels spacing_pattern : int Spacing between (not center-to-center) pattern markers, pixels - - Attributes - ---------- - - size_x - - size_y - - width_pattern - - spacing_pattern - - x_locs_pixel - - y_locs_pixel - - nx - - ny - - x_locs_frac - - y_locs_frac - - x_indices - - y_indices """ - # Store data - self.size_x = size_x - self.size_y = size_y - self.width_pattern = width_pattern - self.spacing_pattern = spacing_pattern - - # Create location vectors - x_locs_pixel = np.arange(0, size_x, width_pattern + spacing_pattern) - y_locs_pixel = np.arange(0, size_y, width_pattern + spacing_pattern) - - # Make vectors odd - if x_locs_pixel.size % 2 == 0: - x_locs_pixel = x_locs_pixel[:-1] - if y_locs_pixel.size % 2 == 0: - y_locs_pixel = y_locs_pixel[:-1] - - # Center in image - dx = int(float((size_x - 1) - x_locs_pixel[-1]) / 2) - dy = int(float((size_y - 1) - y_locs_pixel[-1]) / 2) - x_locs_pixel += dx - y_locs_pixel += dy - - # Save calculations - self.x_locs_pixel = x_locs_pixel - self.y_locs_pixel = y_locs_pixel - self.nx = x_locs_pixel.size - self.ny = y_locs_pixel.size - - # Calculate point indices - self.x_indices = np.arange(self.nx) - int(np.floor(float(self.nx) / 2)) - self.y_indices = np.arange(self.ny) - int(np.floor(float(self.ny) / 2)) - - # Calculate fractional screen points - self.x_locs_frac = x_locs_pixel.astype(float) / float(size_x) - self.y_locs_frac = y_locs_pixel.astype(float) / float(size_y) - - def _get_dot_image(self, dot_shape: str) -> ndarray[float]: - """Returns 2d image of individual pattern element. Active area is 1 - and inactive area is 0, dtype float. - """ - if dot_shape not in ['circle', 'square']: - raise ValueError(f'pattern_type must be one of ["circle", "square"], not {dot_shape:s}') + lt.debug(f'SystemSofastFixed fixed pattern dot width set to {width_pattern:d} pixels') + lt.debug(f'SystemSofastFixed fixed pattern dot spacing set to {spacing_pattern:d} pixels') + image_projection = ImageProjection.instance() + size_x = image_projection.size_x + size_y = image_projection.size_y + self.pattern_sofast_fixed = PatternSofastFixed(size_x, size_y, width_pattern, spacing_pattern) + self.pattern_image = self.pattern_sofast_fixed.get_image(self.dtype, self.max_int, self.dot_shape) - if dot_shape == 'square': - return np.ones((self.width_pattern, self.width_pattern), dtype=float) - elif dot_shape == 'circle': - x, y = np.meshgrid(np.arange(self.width_pattern, dtype=float), np.arange(self.width_pattern, dtype=float)) - x -= x.mean() - y -= y.mean() - r = np.sqrt(x**2 + y**2) - return (r < float(self.width_pattern) / 2).astype(float) + self.run_next_in_queue() - def get_image(self, dtype: str, max_int: int, dot_shape: Literal['circle', 'square'] = 'circle') -> ndarray: - """Creates a NxMx3 fixed pattern image + def project_pattern(self) -> None: + """Projects the fixed dot pattern - Parameters - ---------- - dtype : str - Output datatype of image - max_int : int - Integer value corresponding to "white." Zero corresponds to black. - dot_shape : str - 'circle' or 'square' - - Returns - ------- - ndarray - (size_y, size_x, 3) ndarray + Upon completion, runs self.run_next_in_queue() """ - # Create image with point locations - image = np.zeros((self.size_y, self.size_x), dtype=dtype) - locs_x, locs_y = np.meshgrid(self.x_locs_pixel, self.y_locs_pixel) - image[locs_y, locs_x] = 1 + lt.debug('SystemSofastFixed pattern projected') + + # Check pattern has been defined + if self.pattern_image is None: + lt.error_and_raise( + ValueError, 'The SofastFixed pattern has not been set. Run self.set_pattern_parameters() first.' + ) + return + + # Project pattern + image_projection = ImageProjection.instance() + image_projection.display_image_in_active_area(self.pattern_image) + self.run_next_in_queue() - # Add patterns (active=1) - pattern = self._get_dot_image(dot_shape).astype(dtype) - image = convolve2d(image, pattern, mode='same') + def capture_image(self) -> None: + """Captures the measurement image - # Convert image to white background and black patterns - image = (1 - image) * max_int + Upon completion, runs self.run_next_in_queue() + """ + lt.debug('SystemSofastFixed capturing camera image') + self.image_measurement = self.image_acquisition.get_frame() + + self.run_next_in_queue() + + def get_measurement( + self, + v_measure_point_facet: Vxyz, + dist_optic_screen: float, + origin: Vxy, + date: dt.datetime = None, + name: str = '', + ) -> MeasurementSofastFixed: + """Returns the SofastFixedMeasurement object""" + dist_optic_screen_measure = dos.DistanceOpticScreen(v_measure_point_facet, dist_optic_screen) + return MeasurementSofastFixed(self.image_measurement, dist_optic_screen_measure, origin, date, name) + + def pause(self) -> None: + """Pause by the given amount defined by self.image_delay (in ms) + + Upon completion, runs self.run_next_in_queue() + """ + lt.debug(f'SystemSofastFixed pausing by {self.image_delay:.0f} ms') + image_projection = ImageProjection.instance() + image_projection.root.after(self.image_delay, self.run_next_in_queue) - # Add RGB channels - return np.concatenate([image[..., None]] * 3, axis=2) + def run_measurement(self) -> None: + """Runs the measurement sequence + - projects the fixed pattern image + - captures an image. + + If the fixed pattern image is already projected, use self.capture_image() instead. + Upon completion, runs self.run_next_in_queue() + """ + lt.debug('SystemSofastFixed starting measurement sequence') + + funcs = [self.project_pattern, self.pause, self.capture_image] + self.prepend_to_queue(funcs) + self.run_next_in_queue() + + def close_all(self): + """Closes all windows and cameras""" + # Close image acquisition + with et.ignored(Exception): + self.image_acquisition.close() + + # Close window + with et.ignored(Exception): + image_projection = ImageProjection.instance() + image_projection.close() + + def prepend_to_queue(self, funcs: list[Callable]) -> None: + """Prepends the current list of functions to the queue""" + self._queue_funcs = funcs + self._queue_funcs + + def set_queue(self, funcs: list[Callable]) -> None: + """Sets the queue to given list of Callables""" + self._queue_funcs = funcs + + def run_next_in_queue(self) -> None: + """Runs the next funtion in the queue and removes from queue""" + if len(self._queue_funcs) > 0: + func = self._queue_funcs.pop(0) + + try: + func() + except Exception as error: + lt.error(repr(error)) + self.run_next_in_queue() + + def run(self) -> None: + """ + Instantiates the system by starting the mainloop of the ImageProjection object. + """ + self.run_next_in_queue() + image_projection = ImageProjection.instance() + image_projection.root.mainloop() diff --git a/opencsp/app/sofast/test/ImageAcquisition_no_camera.py b/opencsp/app/sofast/test/ImageAcquisition_no_camera.py index 2c616f20a..dd530e9b1 100644 --- a/opencsp/app/sofast/test/ImageAcquisition_no_camera.py +++ b/opencsp/app/sofast/test/ImageAcquisition_no_camera.py @@ -1,6 +1,6 @@ """Representation of a notional camera for image acquisition""" -from typing import Callable +import time import numpy as np @@ -34,6 +34,7 @@ def instance_matches(self, possible_matches: list[ImageAcquisitionAbstract]) -> return False def get_frame(self) -> np.ndarray: + time.sleep(self._shutter / 1e6) # Return test image return np.zeros(self._frame_size, dtype=np.uint8) diff --git a/opencsp/app/sofast/test/test_DotLocationsFixedPattern.py b/opencsp/app/sofast/test/test_DotLocationsFixedPattern.py index e8e7b179d..ea76b0d23 100644 --- a/opencsp/app/sofast/test/test_DotLocationsFixedPattern.py +++ b/opencsp/app/sofast/test/test_DotLocationsFixedPattern.py @@ -4,7 +4,7 @@ import numpy as np from opencsp.app.sofast.lib.DotLocationsFixedPattern import DotLocationsFixedPattern -from opencsp.app.sofast.lib.SystemSofastFixed import SystemSofastFixed +from opencsp.app.sofast.lib.PatternSofastFixed import PatternSofastFixed from opencsp.app.sofast.lib.DisplayShape import DisplayShape as Display from opencsp.common.lib.geometry.Vxy import Vxy from opencsp.common.lib.opencsp_path.opencsp_root_path import opencsp_code_dir @@ -57,7 +57,7 @@ def test_from_Display(self): # Load display file_disp = os.path.join(opencsp_code_dir(), 'test/data/sofast_common/display_distorted_3d.h5') display = Display.load_from_hdf(file_disp) - fp_proj = SystemSofastFixed(30, 30, 5, 5) + fp_proj = PatternSofastFixed(30, 30, 5, 5) fp = DotLocationsFixedPattern.from_projection_and_display(fp_proj, display) diff --git a/opencsp/app/sofast/test/test_PatternSofastFixed.py b/opencsp/app/sofast/test/test_PatternSofastFixed.py new file mode 100644 index 000000000..7a1b699b0 --- /dev/null +++ b/opencsp/app/sofast/test/test_PatternSofastFixed.py @@ -0,0 +1,27 @@ +import unittest + +import numpy as np + +from opencsp.app.sofast.lib.PatternSofastFixed import PatternSofastFixed + + +class TestPatternSofastFixed(unittest.TestCase): + def test_FixedPatternDisplay(self): + # Instantiate + pattern = PatternSofastFixed(100, 100, 10, 10) + + # Test screen fractions + np.testing.assert_allclose(pattern.x_locs_frac, np.array([0.09, 0.29, 0.49, 0.69, 0.89]), rtol=0, atol=1e-6) + np.testing.assert_allclose(pattern.y_locs_frac, np.array([0.09, 0.29, 0.49, 0.69, 0.89]), rtol=0, atol=1e-6) + + # Test indices + np.testing.assert_equal(pattern.x_indices, np.array([-2, -1, 0, 1, 2])) + np.testing.assert_equal(pattern.y_indices, np.array([-2, -1, 0, 1, 2])) + + # Calculate image + im = pattern.get_image('uint8', 255) + np.testing.assert_array_equal([100, 100, 3], im.shape) + + +if __name__ == '__main__': + unittest.main() diff --git a/opencsp/app/sofast/test/test_SystemSofastFixed.py b/opencsp/app/sofast/test/test_SystemSofastFixed.py index 8822685e2..7b30b220f 100644 --- a/opencsp/app/sofast/test/test_SystemSofastFixed.py +++ b/opencsp/app/sofast/test/test_SystemSofastFixed.py @@ -1,27 +1,63 @@ import unittest +from os.path import join, dirname -import numpy as np +import pytest from opencsp.app.sofast.lib.SystemSofastFixed import SystemSofastFixed +from opencsp.app.sofast.test.ImageAcquisition_no_camera import ImageAcquisition +from opencsp.common.lib.deflectometry.ImageProjection import ImageProjection +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 +import opencsp.common.lib.tool.log_tools as lt class TestSystemSofastFixed(unittest.TestCase): - def test_FixedPatternDisplay(self): - # Instantiate - pattern = SystemSofastFixed(100, 100, 10, 10) + @pytest.mark.no_xvfb + def test_system(self): + # Get test data location + file_im_proj = join(opencsp_code_dir(), 'test/data/sofast_common/image_projection_test.h5') - # Test screen fractions - np.testing.assert_allclose(pattern.x_locs_frac, np.array([0.09, 0.29, 0.49, 0.69, 0.89]), rtol=0, atol=1e-6) - np.testing.assert_allclose(pattern.y_locs_frac, np.array([0.09, 0.29, 0.49, 0.69, 0.89]), rtol=0, atol=1e-6) + # Instantiate image projection class + im_proj = ImageProjection.load_from_hdf_and_display(file_im_proj) - # Test indices - np.testing.assert_equal(pattern.x_indices, np.array([-2, -1, 0, 1, 2])) - np.testing.assert_equal(pattern.y_indices, np.array([-2, -1, 0, 1, 2])) + # Instantiate image acquisition class + im_aq = ImageAcquisition() - # Calculate image - im = pattern.get_image('uint8', 255) - np.testing.assert_array_equal([100, 100, 3], im.shape) + # Set camera settings + im_aq.frame_size = (100, 80) + im_aq.frame_rate = 7 + im_aq.exposure_time = 300000 + im_aq.gain = 230 + + # Create system class + system = SystemSofastFixed(im_aq) + system.image_delay = 300 + system.image_acquisition.exposure_time = 0.1 # seconds + + # Define pattern parameters + system.set_pattern_parameters(5, 5) + + # Define functions to put in system queue + funcs = [system.run_measurement, system.close_all] + + # Load function in queue + system.set_queue(funcs) + + # Run + system.run() + + # Get measurement + v_measure_point_facet = Vxyz((0.0, 0.0, 0.0)) + dist_optic_screen = 10.0 + origin = Vxy((10, 200)) + measurement = system.get_measurement(v_measure_point_facet, dist_optic_screen, origin) if __name__ == '__main__': + save_dir = join(dirname(__file__), 'data/output/system_fixed') + ft.create_directories_if_necessary(save_dir) + lt.logger(join(save_dir, 'log.txt'), level=lt.log.DEBUG) + unittest.main() diff --git a/opencsp/app/sofast/test/test_project_fixed_pattern_target.py b/opencsp/app/sofast/test/test_project_fixed_pattern_target.py index c17ce0e0d..f32d0b0fc 100644 --- a/opencsp/app/sofast/test/test_project_fixed_pattern_target.py +++ b/opencsp/app/sofast/test/test_project_fixed_pattern_target.py @@ -7,7 +7,7 @@ import pytest from opencsp.common.lib.opencsp_path.opencsp_root_path import opencsp_code_dir -from opencsp.app.sofast.lib.SystemSofastFixed import SystemSofastFixed +from opencsp.app.sofast.lib.PatternSofastFixed import PatternSofastFixed from opencsp.common.lib.deflectometry.ImageProjection import ImageProjection @@ -20,7 +20,7 @@ def test_project_fixed_pattern_target(self): # Load ImageProjection im_proj = ImageProjection.load_from_hdf_and_display(file_image_projection) - fixed_pattern = SystemSofastFixed(im_proj.size_x, im_proj.size_y, width_pattern=3, spacing_pattern=6) + fixed_pattern = PatternSofastFixed(im_proj.size_x, im_proj.size_y, width_pattern=3, spacing_pattern=6) image = fixed_pattern.get_image('uint8', 255, 'square') # Project image