Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Image projection with screen markers #108

Merged
merged 36 commits into from
Jun 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
c351bf7
Added dataclass to IP
braden6521 May 24, 2024
17be7ce
Updated test_ImageProjection to work with ImageProjection changes.
braden6521 May 28, 2024
277bb17
Updated image_projection_test.h5 to new format.
braden6521 May 28, 2024
8561444
Added button to SofastGUI
braden6521 May 28, 2024
6e43635
Changed name of calibration image in ImageProjectionSetupGUI
braden6521 May 28, 2024
e4d9816
Added show_calibration_marker_image placeholder function im ImageProj…
braden6521 May 28, 2024
0e3e47f
Added a run test to testImageProjection
braden6521 May 28, 2024
6cc0766
Updated ImageProjection references in SystemSofastFringe.
braden6521 May 28, 2024
0facd47
Updated ImageProjection loading func in SofastGUI
braden6521 May 28, 2024
0b65a83
Made display_data not private in ImageProjection
braden6521 May 28, 2024
342810d
Added docstring to SystemSofastFringe
braden6521 May 28, 2024
d343f0e
Updated SystemSofastFixed to use new ImageProjection data structure.
braden6521 May 28, 2024
8e7dd67
ImageProjectionSetupGUI is now compatible with new imageProjection cl…
braden6521 May 28, 2024
1343f83
Added marker calibration image button to IP setup GUI
braden6521 May 28, 2024
95c8a56
Updated test_SystemSofastFringe to use new ImageProjection class
braden6521 May 28, 2024
4b1cfe1
Updated test_SystemSofastFixed to use new IP class.
braden6521 May 28, 2024
5c44986
Updated TestProjectFixedPatternTarget to use new IP class.
braden6521 May 28, 2024
45474e2
Removed extraneous calls to IP.load_from_hdf
braden6521 May 28, 2024
4a00af1
Updated sofast_common_functions and test_scf to use new ImageProjecti…
braden6521 May 28, 2024
6c7ad63
Removed gui_x position from ImageProjection
braden6521 May 28, 2024
1652b1d
Updated HDF5 test files to new IP format
braden6521 May 28, 2024
283de18
Updated CalibrateDisplayShape to use new IP class
braden6521 May 28, 2024
06aee78
Removed UI_x input from IP setup GUI
braden6521 May 28, 2024
a8b5608
Made calibration fiducial image zeros base instead of ones
braden6521 May 28, 2024
a934b5a
Fixed deactivate bussons in IP setup GUI
braden6521 May 28, 2024
f95575b
Added marker calibration image to IP
braden6521 May 28, 2024
7241326
Removed extraneous data_type input in IP setup GUI
braden6521 May 28, 2024
37abcba
Updated example calibration screen shape to use new IP class
braden6521 May 28, 2024
2608793
Updated example_calculate_dot_locations_from_display_shape
braden6521 May 28, 2024
647370d
Updated Sofast CLI
braden6521 May 28, 2024
7157030
Added doc strings to ImageProjection
braden6521 May 31, 2024
eccf8f7
Added dataclass to IP
braden6521 May 24, 2024
118c78f
Updated test_ImageProjection to work with ImageProjection changes.
braden6521 May 28, 2024
0e23cf7
Updated test_SystemSofastFringe to use new ImageProjection class
braden6521 May 28, 2024
8f3532f
Removed extraneous calls to IP.load_from_hdf
braden6521 May 28, 2024
ded3a3c
Made calibration fiducial image zeros base instead of ones
braden6521 May 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions contrib/app/sofast/SofastCommandLineInterface.py
Original file line number Diff line number Diff line change
Expand Up @@ -485,8 +485,8 @@ def _run_given_input(self, retval: str) -> None:
image_acquisition_in.frame_size = (1626, 1236)
image_acquisition_in.gain = 230

image_projection_in = ImageProjection.load_from_hdf_and_display(file_image_projection)
image_projection_in.display_data['image_delay'] = 200
image_projection_in = ImageProjection.load_from_hdf(file_image_projection)
image_projection_in.display_data.image_delay_ms = 200

camera_in = Camera.load_from_hdf(file_camera)
facet_definition_in = DefinitionFacet.load_from_json(file_facet_definition_json)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from opencsp.app.sofast.lib.MeasurementSofastFringe import MeasurementSofastFringe
from opencsp.app.sofast.lib.CalibrateDisplayShape import CalibrateDisplayShape, DataInput
from opencsp.common.lib.camera.Camera import Camera
from opencsp.common.lib.deflectometry.ImageProjection import ImageProjection
from opencsp.common.lib.deflectometry.ImageProjection import ImageProjectionData
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
Expand Down Expand Up @@ -61,7 +61,7 @@ def example_screen_shape_calibration():

# Load input data
camera = Camera.load_from_hdf(file_camera_distortion)
image_projection_data = ImageProjection.load_from_hdf(file_image_projection)
image_projection_data = ImageProjectionData.load_from_hdf(file_image_projection)
screen_cal_point_pairs = np.loadtxt(file_screen_cal_point_pairs, delimiter=',', skiprows=1, dtype=int)

# Store input data in data class
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

from opencsp.app.sofast.lib.DotLocationsFixedPattern import DotLocationsFixedPattern
from opencsp.app.sofast.lib.DisplayShape import DisplayShape
from opencsp.app.sofast.lib.SystemSofastFixed import SystemSofastFixed
from opencsp.common.lib.deflectometry.ImageProjection import ImageProjection
from opencsp.app.sofast.lib.PatternSofastFixed import PatternSofastFixed
from opencsp.common.lib.deflectometry.ImageProjection import ImageProjectionData
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
Expand Down Expand Up @@ -33,15 +33,17 @@ def example_calculate_dot_locations_from_display_shape():

# Load data
display = DisplayShape.load_from_hdf(file_display)
im_proj_params = ImageProjection.load_from_hdf(file_image_projection)
im_proj_params = ImageProjectionData.load_from_hdf(file_image_projection)

# 2. Define dot projection object
# ===============================
width_dot = 3 # pixels
spacing_dot = 6 # pixels

# Create fixed pattern projection object
projection = SystemSofastFixed(im_proj_params['size_x'], im_proj_params['size_y'], width_dot, spacing_dot)
projection = PatternSofastFixed(
im_proj_params.active_area_size_x, im_proj_params.active_area_size_y, width_dot, spacing_dot
)

# 3. Define DotLocationsFixedPattern object
# =========================================
Expand Down
42 changes: 31 additions & 11 deletions opencsp/app/sofast/SofastGUI.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from opencsp.common.lib.camera.ImageAcquisitionAbstract import ImageAcquisitionAbstract
from opencsp.common.lib.camera.image_processing import highlight_saturation
from opencsp.common.lib.camera.LiveView import LiveView
from opencsp.common.lib.deflectometry.ImageProjection import ImageProjection
from opencsp.common.lib.deflectometry.ImageProjection import ImageProjection, ImageProjectionData
from opencsp.common.lib.geometry.Vxyz import Vxyz
import opencsp.common.lib.opencsp_path.opencsp_root_path as orp
import opencsp.common.lib.tool.exception_tools as et
Expand All @@ -46,7 +46,7 @@ def __init__(self) -> 'SofastGUI':
self.root.title('SOFAST')

# Set size of GUI
self.root.geometry('600x640+200+100')
self.root.geometry('600x680+200+100')

# Add all buttons/widgets to window
self._create_layout()
Expand Down Expand Up @@ -136,6 +136,7 @@ def _plot_image(self, ax: plt.Axes, image: np.ndarray, title: str = '', xlabel:

@functools.cached_property
def cam_options(self):
"""Returns all camera (ImageAcquisition) options that are compatible with Sofast"""
all_cam_options = ImageAcquisitionAbstract.cam_options()

ret: dict[str, type[ImageAcquisitionAbstract]] = {}
Expand Down Expand Up @@ -187,10 +188,17 @@ def _create_layout(self) -> None:
# =============== First Column - Projection controls ===============
r = 0
self.btn_show_cal_image = tkinter.Button(
label_frame_projector, text='Show Calibration Image', command=self.show_calibration_image
label_frame_projector, text='Show Cal Fiducial Image', command=self.show_calibration_fiducial_image
)
self.btn_show_cal_image.grid(row=r, column=0, pady=2, padx=2, sticky='nesw')
tkt.TkToolTip(self.btn_show_cal_image, 'Shows calibration image on projection window.')
tkt.TkToolTip(self.btn_show_cal_image, 'Shows calibration fiducial image on projection window.')
r += 1

self.btn_show_cal_image = tkinter.Button(
label_frame_projector, text='Show Cal Marker Image', command=self.show_calibration_marker_image
)
self.btn_show_cal_image.grid(row=r, column=0, pady=2, padx=2, sticky='nesw')
tkt.TkToolTip(self.btn_show_cal_image, 'Shows calibration marker image on projection window.')
r += 1

self.btn_show_axes = tkinter.Button(label_frame_projector, text='Show Screen Axes', command=self.show_axes)
Expand Down Expand Up @@ -309,7 +317,10 @@ def _create_layout(self) -> None:
# Camera type dropdown
self.var_cam_select = tkinter.StringVar(value=list(self.cam_options.keys())[0])
lbl_camera_type = tkinter.Label(label_frame_settings, text='Select camera:', font=('calibre', 10, 'bold'))
drop_camera_type = tkinter.OptionMenu(label_frame_settings, self.var_cam_select, *list(self.cam_options.keys()))
cam_options = list(self.cam_options.keys())
drop_camera_type = tkinter.OptionMenu(
label_frame_settings, self.var_cam_select, cam_options[0], *cam_options[1:]
)
tkt.TkToolTip(drop_camera_type, 'Select type of camera object to load.')

lbl_camera_type.grid(row=r, column=1, pady=2, padx=2, sticky='nse')
Expand Down Expand Up @@ -547,13 +558,13 @@ def load_image_projection(self) -> None:

# Load file and display
if file != '':
# Load data
image_projection_data = ImageProjection.load_from_hdf(file)
# Close the previous projector instance
with et.ignored(Exception):
ImageProjection.instance().close()
# Create new window
projector_root = tkt.window(self.root, TopLevel=True)
# Load data
image_projection_data = ImageProjectionData.load_from_hdf(file)
# Show window
ImageProjection(projector_root, image_projection_data)
# Build system instance
Expand All @@ -565,13 +576,21 @@ def load_image_projection(self) -> None:

lt.info(f'ImageProjection loaded:\n {file}')

def show_calibration_image(self) -> None:
"""Shows calibration image"""
def show_calibration_fiducial_image(self) -> None:
"""Shows calibration image with fiducials"""
# Check that we have a projector
if not self._check_projector_loaded('show_calibration_fiducial_image'):
return

ImageProjection.instance().show_calibration_fiducial_image()

def show_calibration_marker_image(self) -> None:
"""Shows calibration image with Aruco markers"""
# Check that we have a projector
if not self._check_projector_loaded('show_calibration_image'):
if not self._check_projector_loaded('show_calibration_marker_image'):
return

ImageProjection.instance().show_calibration_image()
ImageProjection.instance().show_calibration_marker_image()

def show_crosshairs(self) -> None:
"""Shows crosshairs"""
Expand Down Expand Up @@ -800,6 +819,7 @@ def _projector_closed(self, image_projection: ImageProjection):
self._camera_closed(None)

def close_image_acquisition(self) -> None:
"""Closes image acquisition"""
# Close the camera
with et.ignored(Exception):
ImageAcquisitionAbstract.instance().close()
Expand Down
9 changes: 5 additions & 4 deletions opencsp/app/sofast/lib/CalibrateDisplayShape.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import opencsp.app.sofast.lib.image_processing as ip
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.deflectometry.ImageProjection import CalParams, ImageProjectionData
from opencsp.common.lib.geometry.Vxy import Vxy
from opencsp.common.lib.geometry.Vxyz import Vxyz
import opencsp.common.lib.photogrammetry.photogrammetry as ph
Expand All @@ -41,7 +41,7 @@ class DataInput:
Aruco marker corners, meters
camera : Camera
Camera object used to capture screen distortion data
image_projection_data : dict
image_projection_data : ImageProjectionData
Image projection parameters
measurements_screen : list[MeasurementSofastFringe]
Screen shape Sofast measurement objects
Expand All @@ -56,7 +56,7 @@ class DataInput:
resolution_xy: tuple[int, int]
pts_xyz_marker: Vxyz
camera: Camera
image_projection_data: dict
image_projection_data: ImageProjectionData
measurements_screen: list[MeasurementSofastFringe]
assume_located_points: bool = True
ray_intersection_threshold: float = 0.001
Expand Down Expand Up @@ -113,7 +113,8 @@ def __init__(self, data_input: DataInput) -> 'CalibrateDisplayShape':

# Load cal params
cal_pattern_params = CalParams(
self.data_input.image_projection_data['size_x'], self.data_input.image_projection_data['size_y']
self.data_input.image_projection_data.active_area_size_x,
self.data_input.image_projection_data.active_area_size_y,
)

# Initialize calculation data structure
Expand Down
6 changes: 3 additions & 3 deletions opencsp/app/sofast/lib/SystemSofastFixed.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def __init__(self, image_acquisition: ImageAcquisitionAbstract) -> 'SystemSofast
"""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
self.image_delay = image_projection.display_data.image_delay_ms # ms
"""The delay after displaying the image to capturing an image, ms"""

def set_pattern_parameters(self, width_pattern: int, spacing_pattern: int):
Expand All @@ -76,8 +76,8 @@ def set_pattern_parameters(self, width_pattern: int, spacing_pattern: int):
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
size_x = image_projection.display_data.active_area_size_x
size_y = image_projection.display_data.active_area_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)

Expand Down
37 changes: 22 additions & 15 deletions opencsp/app/sofast/lib/SystemSofastFringe.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
"""Class for controlling displaying Sofast patterns and capturing images
"""

import copy
import datetime as dt
from typing import Callable
Expand All @@ -21,6 +18,8 @@


class SystemSofastFringe:
"""Class for controlling/displaying Sofast patterns and capturing images"""

def __init__(
self, image_acquisition: ImageAcquisitionAbstract | list[ImageAcquisitionAbstract] = None
) -> 'SystemSofastFringe':
Expand All @@ -40,7 +39,6 @@ def __init__(
Either the image_acquisition or the image_projection has not been loaded.
TypeError:
The image_acquisition is not the correct type.

"""
# Import here to avoid circular dependencies
import opencsp.app.sofast.lib.sofast_common_functions as scf
Expand Down Expand Up @@ -206,7 +204,9 @@ def _create_mask_images_to_display(self) -> None:
array = np.array(image_projection.get_black_array_active_area())
self._mask_images_to_display.append(array)
# Create white image
array = np.array(image_projection.get_black_array_active_area()) + image_projection.max_int
array = (
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh black, you make things so much easier to read.

FYI, what I've done in these situations is to split out the line into several smaller calls by using variables. For example:

black_image = np.array(image_projection.get_black_array_active_area())
white_image = np.array(black_image + image_projection.display_data.projector_max_int)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice comment!

np.array(image_projection.get_black_array_active_area()) + image_projection.display_data.projector_max_int
)
self._mask_images_to_display.append(array)

def _measure_sequence_display(
Expand All @@ -233,7 +233,7 @@ def _measure_sequence_display(

# Wait, then capture image
self.root.after(
image_projection.display_data['image_delay'],
image_projection.display_data.image_delay_ms,
lambda: self._measure_sequence_capture(im_disp_list, im_cap_list, run_next),
)

Expand Down Expand Up @@ -306,13 +306,13 @@ def create_fringe_images_from_image_calibration(self):
min_display_value = self._calibration.calculate_min_display_camera_values()[0]

# Get fringe range
fringe_range = (min_display_value, image_projection.display_data['projector_max_int'])
fringe_range = (min_display_value, image_projection.display_data.projector_max_int)

# Get fringe base images
fringe_images_base = self.fringes.get_frames(
image_projection.size_x,
image_projection.size_y,
image_projection.display_data['projector_data_type'],
image_projection.display_data.active_area_size_x,
image_projection.display_data.active_area_size_y,
image_projection.display_data.projector_data_type,
fringe_range,
)

Expand Down Expand Up @@ -541,11 +541,14 @@ def run_display_camera_response_calibration(self, res: int = 10, run_next: Calla

# Generate grayscale values
self._calibration_display_values = np.arange(
0, image_projection.max_int + 1, res, dtype=image_projection.display_data['projector_data_type']
0,
image_projection.display_data.projector_max_int + 1,
res,
dtype=image_projection.display_data.projector_data_type,
)
if self._calibration_display_values[-1] != image_projection.max_int:
if self._calibration_display_values[-1] != image_projection.display_data.projector_max_int:
self._calibration_display_values = np.concatenate(
(self._calibration_display_values, [image_projection.max_int])
(self._calibration_display_values, [image_projection.display_data.projector_max_int])
)

# Generate grayscale images
Expand All @@ -554,8 +557,12 @@ def run_display_camera_response_calibration(self, res: int = 10, run_next: Calla
# Create image
array = (
np.zeros(
(image_projection.size_y, image_projection.size_x, 3),
dtype=image_projection.display_data['projector_data_type'],
(
image_projection.display_data.active_area_size_y,
image_projection.display_data.active_area_size_x,
3,
),
dtype=image_projection.display_data.projector_data_type,
)
+ dn
)
Expand Down
4 changes: 3 additions & 1 deletion opencsp/app/sofast/lib/sofast_common_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,9 @@ def run_cal():
if on_done is not None:
on_done()

white_image = np.array(image_projection.get_black_array_active_area()) + image_projection.max_int
white_image = (
np.array(image_projection.get_black_array_active_area()) + image_projection.display_data.projector_max_int
)
image_projection.display_image_in_active_area(white_image)
image_projection.root.after(100, run_cal)

Expand Down
4 changes: 2 additions & 2 deletions opencsp/app/sofast/test/test_CalibrateDisplayShape.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from opencsp.app.sofast.lib.CalibrateDisplayShape import CalibrateDisplayShape, DataInput
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.deflectometry.ImageProjection import ImageProjectionData
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
Expand Down Expand Up @@ -47,7 +47,7 @@ def setUpClass(cls):
corner_ids = pts_marker_data[:, 1]
screen_cal_point_pairs = np.loadtxt(file_screen_cal_point_pairs, delimiter=',', skiprows=1).astype(int)
camera = Camera.load_from_hdf(file_camera_distortion)
image_projection_data = ImageProjection.load_from_hdf(file_image_projection)
image_projection_data = ImageProjectionData.load_from_hdf(file_image_projection)

# Store input data in data class
data_input = DataInput(
Expand Down
2 changes: 1 addition & 1 deletion opencsp/app/sofast/test/test_SystemSofastFixed.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def test_system(self):
file_im_proj = join(opencsp_code_dir(), 'test/data/sofast_common/image_projection_test.h5')

# Instantiate image projection class
im_proj = ImageProjection.load_from_hdf_and_display(file_im_proj)
im_proj = ImageProjection.load_from_hdf(file_im_proj)

# Instantiate image acquisition class
im_aq = ImageAcquisition()
Expand Down
Loading