diff --git a/opencsp/app/sofast/lib/ProcessSofastFixed.py b/opencsp/app/sofast/lib/ProcessSofastFixed.py index 061b957b..4f815226 100644 --- a/opencsp/app/sofast/lib/ProcessSofastFixed.py +++ b/opencsp/app/sofast/lib/ProcessSofastFixed.py @@ -105,15 +105,6 @@ def _calculate_mask(self) -> ndarray: ] mask = ip.calc_mask_raw(images, *params) - if (self.optic_type == 'multi') and self.params.mask.keep_largest_area: - lt.warn( - '"keep_largest_area" mask processing option cannot be used ' - 'for multifacet ensembles. This will be turned off.' - ) - self.params.mask.keep_largest_area = False - elif self.params.mask.keep_largest_area: - mask = ip.keep_largest_mask_area(mask) - return mask def load_measurement_data(self, measurement: MeasurementSofastFixed) -> None: @@ -266,6 +257,10 @@ def process_single_facet_optic( # Calculate mask mask_raw = self._calculate_mask() + # If enabled, fill in holes in mask area + if self.params.mask.keep_largest_area: + mask_raw = ip.keep_largest_mask_area(mask_raw) + # Process optic geometry (find mask corners, etc.) ( self.data_geometry_general, @@ -357,6 +352,7 @@ def process_multi_facet_optic( self.camera, self.measurement.dist_optic_screen, self.params.geometry, + self.params.mask, self.params.debug_geometry, ) diff --git a/opencsp/app/sofast/lib/ProcessSofastFringe.py b/opencsp/app/sofast/lib/ProcessSofastFringe.py index ef406597..81cbe32c 100644 --- a/opencsp/app/sofast/lib/ProcessSofastFringe.py +++ b/opencsp/app/sofast/lib/ProcessSofastFringe.py @@ -2,6 +2,8 @@ to calculate surface slopes. """ +import matplotlib.pyplot as plt +from matplotlib import colormaps import numpy as np from opencsp.app.sofast.lib.DefinitionEnsemble import DefinitionEnsemble @@ -377,13 +379,6 @@ def _process_optic_multifacet_geometry( ] mask_raw = ip.calc_mask_raw(self.measurement.mask_images, *params) - if self.params.mask.keep_largest_area: - lt.warn( - '"keep_largest_area" mask processing option cannot be used ' - 'for multifacet ensembles. This will be turned off.' - ) - self.params.mask.keep_largest_area = False - ( self.data_geometry_general, self.data_image_processing_general, @@ -399,6 +394,7 @@ def _process_optic_multifacet_geometry( self.camera, self.measurement.dist_optic_screen, self.params.geometry, + self.params.mask, self.params.debug_geometry, ) @@ -417,6 +413,17 @@ def _process_display(self) -> None: x_periods = self.measurement.fringe_periods_x y_periods = self.measurement.fringe_periods_y + # Prepare for plotting unwrapped phase images + if self.params.debug_geometry.debug_active: + # X phase RGB image + im_phase_x = self.measurement.mask_images[..., 1].copy() + im_phase_x = np.stack((im_phase_x,) * 3, 2) + im_phase_x = im_phase_x / im_phase_x.max() + # Y phase RGB image + im_phase_y = self.measurement.mask_images[..., 1].copy() + im_phase_y = np.stack((im_phase_y,) * 3, 2) + im_phase_y = im_phase_y / im_phase_y.max() + for idx_facet in range(self.num_facets): # Get current processed mask layer mask_processed = self.data_image_processing_facet[idx_facet].mask_processed @@ -431,6 +438,25 @@ def _process_display(self) -> None: v_screen_points_fractional_screens = Vxy((screen_xs, screen_ys)) self.data_geometry_facet[idx_facet].v_screen_points_fractional_screens = v_screen_points_fractional_screens + # Create plot of unwrapped phase (if enabled) + if self.params.debug_geometry.debug_active: + # Add active pixels as colored pixels + cm = colormaps.get_cmap('jet') + vals_x_jet = cm(screen_xs)[:, :3] # remove alpha channel + im_phase_x[mask_processed, :] = vals_x_jet + vals_y_jet = cm(screen_ys)[:, :3] # remove alpha channel + im_phase_y[mask_processed, :] = vals_y_jet + # Plot x image + fig = plt.figure(f'ProcessSofastFringe_unwrapped_phase_x_facet_{idx_facet:d}') + plt.imshow(im_phase_x) + plt.title(f'Unwrapped X Phase Facet {idx_facet:d}') + self.params.debug_geometry.figures.append(fig) + # Plot y image + fig = plt.figure(f'ProcessSofastFringe_unwrapped_phase_y_facet_{idx_facet:d}') + plt.imshow(im_phase_y) + plt.title(f'Unwrapped Y Phase Facet {idx_facet:d}') + self.params.debug_geometry.figures.append(fig) + # Undistort screen points (display coordinates) v_screen_points_screen = self.display.interp_func( v_screen_points_fractional_screens diff --git a/opencsp/app/sofast/lib/process_optics_geometry.py b/opencsp/app/sofast/lib/process_optics_geometry.py index fd5fc085..a22c7cad 100644 --- a/opencsp/app/sofast/lib/process_optics_geometry.py +++ b/opencsp/app/sofast/lib/process_optics_geometry.py @@ -12,6 +12,7 @@ from opencsp.app.sofast.lib.DefinitionEnsemble import DefinitionEnsemble from opencsp.app.sofast.lib.DefinitionFacet import DefinitionFacet from opencsp.app.sofast.lib.ParamsOpticGeometry import ParamsOpticGeometry +from opencsp.app.sofast.lib.ParamsMaskCalculation import ParamsMaskCalculation from opencsp.app.sofast.lib.DebugOpticsGeometry import DebugOpticsGeometry import opencsp.app.sofast.lib.image_processing as ip from opencsp.app.sofast.lib.SpatialOrientation import SpatialOrientation @@ -366,7 +367,8 @@ def process_multifacet_geometry( orientation: SpatialOrientation, camera: Camera, dist_optic_screen: float, - params: ParamsOpticGeometry = ParamsOpticGeometry(), + params_geometry: ParamsOpticGeometry = ParamsOpticGeometry(), + params_mask: ParamsMaskCalculation = ParamsMaskCalculation(), debug: DebugOpticsGeometry = DebugOpticsGeometry(), ) -> tuple[ cdc.CalculationDataGeometryGeneral, @@ -393,8 +395,10 @@ def process_multifacet_geometry( Camera object dist_optic_screen : float Optic to screen distance, meters - params : ParamsOpticGeometry, optional + params_geometry : ParamsOpticGeometry, optional ParamsOpticGeometry object, by default ParamsOpticGeometry() + params_mask : ParamsMaskCalculation, optional + ParamsMaskCalculation object, by default ParamsMaskCalculation() debug : DebugOpticsGeometry, optional DebugOpticsGeometry object, by default DebugOpticsGeometry() @@ -512,7 +516,10 @@ def process_multifacet_geometry( plt.title('Expected Perimeter Points') # Refine perimeter points - args = [params.perimeter_refine_axial_search_dist, params.perimeter_refine_perpendicular_search_dist] + 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 @@ -545,9 +552,9 @@ def process_multifacet_geometry( # Refine facet corners args = [ - params.facet_corns_refine_step_length, - params.facet_corns_refine_perpendicular_search_dist, - params.facet_corns_refine_frac_keep, + params_geometry.facet_corns_refine_step_length, + params_geometry.facet_corns_refine_perpendicular_search_dist, + params_geometry.facet_corns_refine_frac_keep, ] loops_facets_refined: list[LoopXY] = [] for idx in range(num_facets): @@ -583,7 +590,11 @@ def process_multifacet_geometry( mask_processed *= mask_raw[..., np.newaxis] mask_processed = np.logical_and(mask_processed, mask_fitted) for idx in range(num_facets): - data_image_processing_facet[idx].mask_processed = mask_processed[..., idx] + mask = mask_processed[..., idx] + # If enabled, keep largest mask area (fill holes) for each individual facet + if params_mask.keep_largest_area: + mask = ip.keep_largest_mask_area(mask) + data_image_processing_facet[idx].mask_processed = mask # Refine R/T with all refined facet corners r_ensemble_cam_refine_2, v_cam_ensemble_cam_refine_2 = sp.calc_rt_from_img_pts(