Skip to content

Commit

Permalink
opencsp/common/lib/cv/fiducials: test docs
Browse files Browse the repository at this point in the history
  • Loading branch information
e10harvey committed Nov 13, 2024
1 parent b12687f commit 6f31bf5
Show file tree
Hide file tree
Showing 4 changed files with 256 additions and 44 deletions.
135 changes: 99 additions & 36 deletions opencsp/common/lib/cv/fiducials/AbstractFiducials.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,72 +23,114 @@ class AbstractFiducials(ABC):

def __init__(self, style: rcps.RenderControlPointSeq = None, pixels_to_meters: Callable[[p2.Pxy], v3.Vxyz] = None):
"""
Initializes the AbstractFiducials with a specified rendering style and pixel-to-meter conversion function.
Parameters
----------
style : RenderControlPointSeq, optional
How to render this fiducial when using the defaul render_to_plot() method. By default rcps.default().
style : rcps.RenderControlPointSeq, optional
How to render this fiducial when using the default render_to_plot() method. Defaults to rcps.default().
pixels_to_meters : Callable[[p2.Pxy], v3.Vxyz], optional
Conversion function to get the physical point in space for the given x/y position information. Used in the
default self.scale implementation. A good implementation of this function will correct for many factors such
as relative camera position and camera distortion. For extreme accuracy, this will also account for
non-uniformity in the target surface. Defaults to a simple 1 meter per pixel model.
"""
# "ChatGPT 4o" assisted with generating this docstring.
self.style = style if style is not None else rcps.default()
self.pixels_to_meters = pixels_to_meters

@abstractmethod
def get_bounding_box(self, index=0) -> reg.RegionXY:
"""The X/Y bounding box(es) of this instance, in pixels."""
"""
Get the X/Y bounding box(es) of this instance, in pixels.
Parameters
----------
index : int, optional
The index of the fiducial for which to retrieve the bounding box. Defaults to 0.
Returns
-------
reg.RegionXY
The bounding box of the fiducial.
"""

# "ChatGPT 4o" assisted with generating this docstring.

@property
@abstractmethod
def origin(self) -> p2.Pxy:
"""The origin point(s) of this instance, in pixels."""
"""
Get the origin point(s) of this instance, in pixels.
Returns
-------
p2.Pxy
The origin point(s) of the fiducial.
"""

# "ChatGPT 4o" assisted with generating this docstring.

@property
@abstractmethod
def rotation(self) -> scipy.spatial.transform.Rotation:
"""
The pointing of the normal vector(s) of this instance.
This is relative to the camera's reference frame, where x is positive
to the right, y is positive down, and z is positive in (away from the
camera).
Get the pointing of the normal vector(s) of this instance.
Returns
-------
scipy.spatial.transform.Rotation
The rotation of the fiducial relative to the camera's reference frame.
This can be used to describe the forward transformation from the
camera's perspective. For example, an aruco marker whose origin is in
the center of the image and is facing towards the camera could have the
rotation::
Notes
-----
This can be used to describe the forward transformation from the camera's perspective. For example, an ArUco
marker whose origin is in the center of the image and is facing towards the camera could have the rotation
defined as:
Rotation.from_euler('y', np.pi)
Rotation.from_euler('y', np.pi)
If that same aruco marker was also placed upside down, then it's
rotation could be::
If that same ArUco marker was also placed upside down, then its rotation could be defined as:
Rotation.from_euler(
'yz',
[ [np.pi, 0],
[0, np.pi] ]
)
Rotation.from_euler(
'yz',
[[np.pi, 0],
[0, np.pi]]
)
Not that this just describes rotation, and not the translation. We call
the rotation and translation together the orientation.
Note that this just describes rotation, and not the translation. We call the rotation and translation together
the orientation.
"""

# "ChatGPT 4o" assisted with generating this docstring.

@property
@abstractmethod
def size(self) -> list[float]:
"""The scale(s) of this fiducial, in pixels, relative to its longest axis.
For example, if the fiducial is a square QR-code and is oriented tangent
to the camera, then the scale will be the number of pixels from one
corner to the other.""" # TODO is this a good definition?
"""
Get the scale(s) of this fiducial, in pixels, relative to its longest axis.
Returns
-------
list[float]
The sizes of the fiducial in pixels. For example, if the fiducial is a square QR-code and is oriented tangent
to the camera, then the scale will be the number of pixels from one corner to the other.
"""

# "ChatGPT 4o" assisted with generating this docstring.

@property
def scale(self) -> list[float]:
"""
The scale(s) of this fiducial, in meters, relative to its longest axis.
This can be used to determine the distance and rotation of the
fiducial relative to the camera.
Get the scale(s) of this fiducial, in meters, relative to its longest axis.
Returns
-------
list[float]
The scales of the fiducial in meters. This can be used to determine the distance and rotation of the
fiducial relative to the camera.
"""
# "ChatGPT 4o" assisted with generating this docstring.
ret = []

for i in range(len(self.origin)):
Expand All @@ -102,9 +144,13 @@ def scale(self) -> list[float]:
return ret

def _render(self, axes: matplotlib.axes.Axes):
"""
Called from render(). The parameters are always guaranteed to be set.
"""
# Render the fiducial on the given axes.
#
# Parameters
# ----------
# axes : matplotlib.axes.Axes
# The axes on which to render the fiducial.

axes.scatter(
self.origin.x,
self.origin.y,
Expand All @@ -123,19 +169,36 @@ def render(self, axes: matplotlib.axes.Axes = None):
Parameters
----------
axes: matplotlib.axes.Axes, optional
The plot to render to. Uses the active plot if None. Default is None.
axes : matplotlib.axes.Axes, optional
The plot to render to. Uses the active plot if None. Defaults to None.
"""
# "ChatGPT 4o" assisted with generating this docstring.
if axes is None:
axes = plt.gca()
self._render(axes)

def render_to_image(self, image: np.ndarray) -> np.ndarray:
"""
Renders this fiducial to the a new image on top of the given image.
Renders this fiducial to a new image on top of the given image.
The default implementation creates a new matplotlib plot, and then renders to it with self.render_to_plot().
The default implementation creates a new matplotlib plot, and then renders to it with self.render().
Parameters
----------
image : np.ndarray
The original image to which the fiducial will be rendered.
Returns
-------
np.ndarray
The updated image with the fiducial rendered on top.
Raises
------
Exception
If an error occurs during the rendering process.
"""
# "ChatGPT 4o" assisted with generating this docstring.
# Create the figure to plot to
dpi = 300
width = image.shape[1]
Expand Down
85 changes: 78 additions & 7 deletions opencsp/common/lib/cv/fiducials/BcsFiducial.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,50 +11,121 @@


class BcsFiducial(AbstractFiducials):
"""
Fiducial for indicating where the BCS target is in an image.
"""

def __init__(
self, origin_px: p2.Pxy, radius_px: float, style: rcb.RenderControlBcs = None, pixels_to_meters: float = 0.1
):
"""
Fiducial for indicating where the BCS target is in an image.
Initializes the BcsFiducial with the specified origin, radius, style, and pixel-to-meter conversion.
Parameters
----------
origin_px : Pxy
The center point of the BCS target, in pixels
origin_px : p2.Pxy
The center point of the BCS target, in pixels.
radius_px : float
The radius of the BCS target, in pixels
style : RenderControlBcs, optional
The rendering style, by default None
The radius of the BCS target, in pixels.
style : rcb.RenderControlBcs, optional
The rendering style for the fiducial. Defaults to None.
pixels_to_meters : float, optional
A simple conversion method for how many meters a pixel represents, for use in scale(). by default 0.1
A conversion factor for how many meters a pixel represents, for use in scale(). Defaults to 0.1.
"""
# "ChatGPT 4o" assisted with generating this docstring.

super().__init__(style=style)
self.origin_px = origin_px
self.radius_px = radius_px
self.pixels_to_meters = pixels_to_meters

def get_bounding_box(self, index=0) -> reg.RegionXY:
"""
Get the bounding box of the BCS target.
Parameters
----------
index : int, optional
The index of the target for which to retrieve the bounding box. Defaults to 0.
Returns
-------
reg.RegionXY
The bounding box as a RegionXY object.
Notes
-----
The bounding box is calculated based on the origin and radius of the BCS target.
"""
# "ChatGPT 4o" assisted with generating this docstring.
x1, x2 = self.origin.x[0] - self.radius_px, self.origin.x[0] + self.radius_px
y1, y2 = self.origin.y[0] - self.radius_px, self.origin.y[0] + self.radius_px
return reg.RegionXY(loop.LoopXY.from_rectangle(x1, y1, x2 - x1, y2 - y1))

@property
def origin(self) -> p2.Pxy:
"""
Get the origin of the BCS fiducial.
Returns
-------
p2.Pxy
The center point of the BCS target in pixels.
"""
# "ChatGPT 4o" assisted with generating this docstring.
return self.origin_px

@property
def rotation(self) -> scipy.spatial.transform.Rotation:
"""
Get the rotation of the BCS fiducial.
Raises
------
NotImplementedError
If the rotation is not yet implemented for BcsFiducial.
"""
# "ChatGPT 4o" assisted with generating this docstring.
raise NotImplementedError("rotation is not yet implemented for PointFiducials")

@property
def size(self) -> list[float]:
"""
Get the size of the BCS fiducial.
Returns
-------
list[float]
A list containing the diameter of the BCS target (radius * 2).
"""
# "ChatGPT 4o" assisted with generating this docstring.
return [self.radius_px * 2]

@property
def scale(self) -> list[float]:
"""
Get the scale of the BCS fiducial.
Returns
-------
list[float]
A list containing the scaled size of the BCS target in meters.
"""
# "ChatGPT 4o" assisted with generating this docstring.
return [self.size * self.pixels_to_meters]

def _render(self, axes: matplotlib.axes.Axes):
# Render the BCS fiducial on the given axes.
#
# Parameters
# ----------
# axes : matplotlib.axes.Axes
# The axes on which to render the BCS fiducial.
#
# Notes
# -----
# This method adds a circle and a marker to the axes based on the style defined for the fiducial.
# "ChatGPT 4o" assisted with generating this doc.
if self.style.linestyle is not None:
circ = matplotlib.patches.Circle(
self.origin.data.tolist(),
Expand Down
Loading

0 comments on commit 6f31bf5

Please sign in to comment.