From 1eb9a20248f717e83d37988303c8327ccb407a55 Mon Sep 17 00:00:00 2001 From: Evan Harvey Date: Mon, 25 Nov 2024 13:30:56 -0700 Subject: [PATCH 01/11] common/lib/cv: Test docs --- opencsp/common/lib/cv/OpticalFlow.py | 301 ++++++++++++---- .../lib/cv/annotations/HotspotAnnotation.py | 43 +++ .../lib/cv/annotations/PointAnnotations.py | 15 + .../lib/cv/fiducials/AbstractFiducials.py | 135 +++++-- .../common/lib/cv/fiducials/BcsFiducial.py | 85 ++++- .../common/lib/cv/fiducials/PointFiducials.py | 77 +++- .../lib/cv/spot_analysis/ImagesIterable.py | 23 +- .../spot_analysis/SpotAnalysisImagesStream.py | 48 +++ .../SpotAnalysisOperablesStream.py | 22 +- opencsp/test/test_DocStringsExist.py | 332 +++++++++++++++++- 10 files changed, 941 insertions(+), 140 deletions(-) diff --git a/opencsp/common/lib/cv/OpticalFlow.py b/opencsp/common/lib/cv/OpticalFlow.py index f6d2beecd..274812dd5 100644 --- a/opencsp/common/lib/cv/OpticalFlow.py +++ b/opencsp/common/lib/cv/OpticalFlow.py @@ -20,6 +20,21 @@ class OpticalFlow: + """ + A class for computing optical flow between two images using OpenCV. + + This class wraps around OpenCV's optical flow functions, providing functionality + to compute dense optical flow and manage caching of results. + + Attributes + ---------- + mag : np.ndarray, optional + OpenCSP version of the magnitude matrix. + ang : np.ndarray, optional + OpenCSP version of the angle matrix. + """ + + # "ChatGPT 4o-mini" assisted with generating this docstring. _max_cache_count = 10 def __init__( @@ -42,35 +57,58 @@ def __init__( dense_flags=0, cache=False, ): - """Wrapper class around cv::calcOpticalFlowFarneback (and also cv::calcOpticalFlowPyrLK, eventually). + """ + Initializes the OpticalFlow object with the specified parameters. - Note: opencsp is not compatible with the multiprocessing library on linux. Typical error message:: + Parameters + ---------- + frame1_dir : str + Directory for the first input image. + frame1_name_ext : str + Filename of the first input image. + frame2_dir : str + Directory for the second input image. + frame2_name_ext : str + Filename of the second input image. + grayscale_normalization : Callable[[np.ndarray], np.ndarray], optional + A function for normalizing grayscale images (default is None). + prev_flow : npt.NDArray[np.float32], optional + Previous flow calculations to speed up computation (default is None). + pyr_scale : float, optional + Image scale for pyramid construction (default is 0.5). + levels : int, optional + Number of pyramid layers (default is 5). + dense_winsize : int, optional + Averaging window size for optical flow (default is 15). + iterations : int, optional + Number of iterations at each pyramid level (default is 3). + poly_n : int, optional + Size of the pixel neighborhood for polynomial expansion (default is 5). + poly_sigma : float, optional + Standard deviation of the Gaussian used for smoothing (default is 1.2). + dense_flags : int, optional + Operation flags for optical flow calculation (default is 0). + cache : bool, optional + If True, enables caching of results (default is False). + + Raises + ------ + ValueError + If cache is enabled in production mode. + + Notes + ----- + Wrapper class around cv::calcOpticalFlowFarneback (and also cv::calcOpticalFlowPyrLK, eventually). + + opencsp is not compatible with the multiprocessing library on linux. Typical error message:: "global /io/opencv/modules/core/src/parallel_impl.cpp (240) WorkerThread 6: Can't spawn new thread: res = 11" This is due to some sort of bug with how multiprocessing processes and opencv threads interact. Possible solutions: - use concurrent.futures.ThreadPoolExecutor - Loky multiprocessing https://github.com/joblib/loky (I (BGB) couldn't make this one work) - - Args: - ----- - - frame1_dir (str): Directory for frame1 - - frame1_name_ext (str): First input image, which will be the reference point for the flow - - frame2_dir (str): Directory for frame2 - - frame2_name_ext (str): Second input image, to compare to the first - - prev_flow (_type_, optional): I think this is the previous flow calculations to make computation faster? Idk... Defaults to None. - - pyr_scale (float, optional): parameter, specifying the image scale (<1) to build pyramids for each image; pyr_scale=0.5 means a classical pyramid, where each next layer is twice smaller than the previous one. Defaults to 0.5. - - levels (int, optional): number of pyramid layers including the initial image; levels=1 means that no extra layers are created and only the original images are used. Defaults to 5. - - dense_winsize (int, optional): averaging window size; larger values increase the algorithm robustness to image noise and give more chances for fast motion detection, but yield more blurred motion field. Defaults to 15. - - iterations (int, optional): number of iterations the algorithm does at each pyramid level. Defaults to 3. - - poly_n (int, optional): size of the pixel neighborhood used to find polynomial expansion in each pixel; larger values mean that the image will be approximated with smoother surfaces, yielding more robust algorithm and more blurred motion field, typically poly_n =5 or 7. Defaults to 5. - - poly_sigma (float, optional): standard deviation of the Gaussian that is used to smooth derivatives used as a basis for the polynomial expansion; for poly_n=5, you can set poly_sigma=1.1, for poly_n=7, a good value would be poly_sigma=1.5. Defaults to 1.2. - - dense_flags (int, optional): operation flags that can be a combination of the following: - - OPTFLOW_USE_INITIAL_FLOW uses the input flow as an initial flow approximation. - - OPTFLOW_FARNEBACK_GAUSSIAN uses the Gaussian winsize×winsize filter instead of a box filter of the same size for optical flow estimation; usually, this option gives z more accurate flow than with a box filter, at the cost of lower speed; normally, winsize for a Gaussian window should be set to a larger value to achieve the same level of robustness. - - cache (bool, optional): If True, then pickle the results from the previous 5 computations and save them in the user's home directory. If False, then don't save them. Defaults to False. - The cache option should not be used in production runs. I (BGB) use it for rapid development. It will error when used while running in production (aka on solo). """ + # "ChatGPT 4o-mini" assisted with generating this docstring. self._frame1_dir = frame1_dir self._frame1_name_ext = frame1_name_ext self._frame2_dir = frame2_dir @@ -104,6 +142,16 @@ def __init__( lt.error_and_raise(ValueError, "OpticalFlow(cache=True) should not be used in production code!") def clear_cache(self): + """ + Clears the cache directory by deleting cached files. + + This method removes any cached optical flow data stored in the cache directory. + + Returns + ------- + None + """ + # "ChatGPT 4o-mini" assisted with generating this docstring. if ft.directory_exists(self._cache_dir): ft.delete_files_in_directory(self._cache_dir, "cache*") @@ -216,14 +264,24 @@ def _load_image(self, f1_or_f2=1) -> npt.NDArray[np.int_]: return img def dense(self) -> tuple[np.ndarray, np.ndarray]: - """Computes the optical flow between two images on a pixel-by-pixel basis using the Gunnar Farneback's algorithm. - https://docs.opencv.org/3.4/dc/d6b/group__video__track.html#ga5d10ebbd59fe09c5f650289ec0ece5af + """ + Computes the optical flow between two images on a pixel-by-pixel basis using Gunnar Farneback's algorithm. - Returns: - -------- - - np.ndarray: self.mag, the magnitude of the flow per pixel (pixels) - - np.ndarray: self.ang, the direction of the flow per pixel (radians, 0 to the right, positive counter-clockwise) + This method calculates the dense optical flow and returns the magnitude and angle of the flow. + + Returns + ------- + tuple[np.ndarray, np.ndarray] + A tuple containing: + - np.ndarray: The magnitude of the flow per pixel. + - np.ndarray: The direction of the flow per pixel (radians, 0 to the right, positive counter-clockwise). + + Notes + ----- + Uses the Gunnar Farneback's algorithm: + https://docs.opencv.org/3.4/dc/d6b/group__video__track.html#ga5d10ebbd59fe09c5f650289ec0ece5af """ + # "ChatGPT 4o-mini" assisted with generating this docstring. if self._mag is None: # use "is", with "==" numpy does an element-wise comparison if not self._load_from_cache(): # load the images @@ -259,20 +317,27 @@ def dense(self) -> tuple[np.ndarray, np.ndarray]: return self.mag, self.ang def limit_by_magnitude(self, lower: float, upper: float, keep="inside"): - """Sets any magnitudes not in the keep range (and the corresponding angles) to 0. + """ + Sets any magnitudes not in the specified range (and the corresponding angles) to 0. - Once applied, you can run dense() again to recover the original values. + Once applied, you can run the `dense()` method again to recover the original values. - Arguments: + Parameters ---------- - - lower (float): The bottom of the range of values to include. - - upper (float): The top of the range of values to include. - - keep (str): Either "inside" or "outside". If "inside", then values upper will - be set to 0. If "outside", then values >lower and upper will be set to 0. + If "outside", then values > lower and < upper will be set to 0 (default is "inside"). + + Returns + ------- + np.ndarray + The indices that were set to 0. + """ + # "ChatGPT 4o-mini" assisted with generating this docstring. if lower > upper: lt.error_and_raise( RuntimeError, @@ -289,7 +354,27 @@ def limit_by_magnitude(self, lower: float, upper: float, keep="inside"): return bad_indicies def limit_by_angle(self, lower: float, upper: float, keep="inside"): - """Same as limit_by_magnitude, but for angle instead.""" + """ + Sets any angles not in the specified range (and the corresponding magnitudes) to 0. + + This method is similar to `limit_by_magnitude`, but it operates on the angle values instead. + + Parameters + ---------- + lower : float + The bottom of the range of angles to include. + upper : float + The top of the range of angles to include. + keep : str, optional + Either "inside" or "outside". If "inside", then values < lower or > upper will be set to 0. + If "outside", then values > lower and < upper will be set to 0 (default is "inside"). + + Returns + ------- + np.ndarray + The indices that were set to 0. + """ + # "ChatGPT 4o-mini" assisted with generating this docstring. if lower > upper: lt.error_and_raise( RuntimeError, @@ -318,28 +403,34 @@ def limit_by_angle(self, lower: float, upper: float, keep="inside"): return bad_indicies def to_img(self, mag_render_clip: tuple[float, float] = None): - """Converts the flow to an image by mapping: - - magnitude of movement to value/intensity - - angle of movement to hue - - up: green - - down: red - - left: blue - - right: yellow + """ + Converts the flow to an image by mapping the magnitude of movement to value/intensity and the angle of movement to hue. - Note: must call dense() first. + The mapping of angles to hues is as follows: + - Up: green + - Down: red + - Left: blue + - Right: yellow - Args: - ----- - mag_render_clip (tuple[float,float]): If set, then clip the rendered magnitude to this range (aka low->0, high->255). Defaults to None. + Note: + This method must be called after `dense()` has been executed. - Returns: - -------- - np.ndarray: The resulting image, which can be passed to View3D.draw_image (row major, RGB color channels) + Parameters + ---------- + mag_render_clip : tuple[float, float], optional + If provided, clips the rendered magnitude to this range (low -> 0, high -> 255). Defaults to None. - Raises: + Returns ------- - RuntimeError: If dense() hasn't been executed yet + np.ndarray + The resulting image, which can be passed to `View3D.draw_image` (row major, RGB color channels). + + Raises + ------ + RuntimeError + If `dense()` hasn't been executed yet. """ + # "ChatGPT 4o-mini" assisted with generating this docstring. # HSV range is 0-179 (H), 0-255 (S), 0-255 (V) # https://docs.opencv.org/3.4/df/d9d/tutorial_py_colorspaces.html if not isinstance(self.mag, np.ndarray): @@ -372,6 +463,17 @@ def to_img(self, mag_render_clip: tuple[float, float] = None): return bgr def draw_flow_angle_reference(self): + """ + Generates and displays a reference image for optical flow angles. + + This method creates a circular reference image that visualizes the angles of optical flow with corresponding colors. + The image is displayed with angle labels at key positions. + + Returns + ------- + None + """ + # "ChatGPT 4o-mini" assisted with generating this docstring. r = 500 h = r * 2 w = r * 2 @@ -484,8 +586,19 @@ def draw_flow_angle_reference(self): @staticmethod def get_save_file_name_ext(frame1_name_maybe_ext: str): - """Get the file name used to save the results from this - flow analysis in save(), given the frame1 name.""" + """ + Generates the file name used to save the results from the flow analysis. + + Parameters + ---------- + frame1_name_maybe_ext : str + The base name of the frame, which may include an extension. + + Returns + ------- + str + The generated file name for saving optical flow results. + """ return frame1_name_maybe_ext + "_optflow.npy" def _default_save_file_name_ext(self, name_ext=""): @@ -494,21 +607,32 @@ def _default_save_file_name_ext(self, name_ext=""): return name_ext def save(self, dir: str, name_ext="", overwrite=False): - """Saves the magnitude and angle matrices computed in the dense() method to the given file. + """ + Saves the magnitude and angle matrices computed in the `dense()` method to the specified file. - Note that this saves the matrices exactly as they were computed in dense (aka - does not save them as after limit_by_magnitude or limit_by_angle are applied). + Note: + This method saves the matrices exactly as they were computed in `dense()`, without applying any limits. - Arguments: + Parameters ---------- - dir (str): The directory to save to. - name_ext (str, optional): The file to save to. Defaults to get_save_file_name_ext(). - overwrite (bool, optional): True to overwrite the existing optical flow save file. Defaults to False. + dir : str + The directory where the file will be saved. + name_ext : str, optional + The file name to save to. Defaults to the output of `get_save_file_name_ext()`. + overwrite : bool, optional + If True, overwrites any existing file. Defaults to False. + + Returns + ------- + str + The full path of the saved file. - Returns: - -------- - saved_path_name_ext (str): The location for the saved file. + Raises + ------ + RuntimeError + If the matrices do not exist or if the file already exists and `overwrite` is False. """ + # "ChatGPT 4o-mini" assisted with generating this docstring. # I (BGB) tried the save+load with different styles of numpy saving, including # np.save(), np.savez(), and np.savez_compressed(). The results for a # 7680x4320 image with a NVME drive are: @@ -548,22 +672,47 @@ def save(self, dir: str, name_ext="", overwrite=False): @classmethod def from_file(cls, dir: str, name_ext: str, error_on_not_exist=True): + """ + Creates an instance of the class by loading magnitude and angle matrices from a file. + + Parameters + ---------- + dir : str + The directory where the file is located. + name_ext : str + The name of the file to load. + error_on_not_exist : bool, optional + If True, raises an error if the file does not exist. Defaults to True. + + Returns + ------- + OpticalFlow + An instance of the class populated with data from the file. + """ + # "ChatGPT 4o-mini" assisted with generating this docstring. ret = cls("", "a", "", "b") ret.load(dir, name_ext, error_on_not_exist) return ret def load(self, dir: str, name_ext: str = "", error_on_not_exist=True): - """Loads the magnitude and angle matrices from the given file into this instance. - - Args: - dir (str): The directory to save to. - name_ext (str, optional): The file to save to. Defaults to self.frame1_name_ext+"_optflow.npy". - error_on_not_exist (bool, optional): True to throw an error when the given file doesn't exist. Defaults to True. + """ + Loads the magnitude and angle matrices from the specified file into this instance. - Returns: - mag (np.ndarray): The magnitudes matrix, as is returned from the dense() method, or None if the file doesn't exist - ang (np.ndarray): The angles matrix, as is returned from the dense() method, or None if the file doesn't exist + Parameters + ---------- + dir : str + The directory where the file is located. + name_ext : str, optional + The name of the file to load. Defaults to the output of `_default_save_file_name_ext()`. + error_on_not_exist : bool, optional + If True, raises an error if the file does not exist. Defaults to True. + + Returns + ------- + tuple[np.ndarray, np.ndarray] + A tuple containing the magnitude and angle matrices, or (None, None) if the file does not exist. """ + # "ChatGPT 4o-mini" assisted with generating this docstring. lt.info(f"Loading flow from {dir}/{name_ext}") name_ext = self._default_save_file_name_ext(name_ext) dir_name_ext = os.path.join(dir, name_ext) diff --git a/opencsp/common/lib/cv/annotations/HotspotAnnotation.py b/opencsp/common/lib/cv/annotations/HotspotAnnotation.py index f3c789475..41d8f3109 100644 --- a/opencsp/common/lib/cv/annotations/HotspotAnnotation.py +++ b/opencsp/common/lib/cv/annotations/HotspotAnnotation.py @@ -4,7 +4,50 @@ class HotspotAnnotation(PointAnnotations): + """ + A class representing a hotspot annotation in a graphical context. + + This class extends the `PointAnnotations` class to create a specific type of annotation + that represents a hotspot, which can be rendered with a specific style and point location. + + Attributes + ---------- + style : rcps.RenderControlPointSeq + The rendering style of the hotspot annotation. + point : p2.Pxy + The point location of the hotspot annotation. + """ + + # "ChatGPT 4o" assisted with generating this docstring. + def __init__(self, style: rcps.RenderControlPointSeq = None, point: p2.Pxy = None): + """ + A class representing a hotspot annotation in a graphical context. + + This class extends the `PointAnnotations` class to create a specific type of annotation + that represents a hotspot, which can be rendered with a specific style and point location. + + Parameters + ---------- + style : rcps.RenderControlPointSeq, optional + The rendering style for the hotspot annotation. If not provided, a default style with + blue color, 'x' marker, and a marker size of 1 will be used. + point : p2.Pxy, optional + The point location of the hotspot annotation, represented as a Pxy object. If not provided, + the annotation will not have a specific point location. + + Examples + -------- + >>> hotspot = HotspotAnnotation() + >>> print(hotspot.style.color) + 'blue' + + >>> point = p2.Pxy(10, 20) + >>> hotspot_with_point = HotspotAnnotation(point=point) + >>> print(hotspot_with_point.point) + Pxy(10, 20) + """ + # "ChatGPT 4o" assisted with generating this docstring. if style is None: style = rcps.RenderControlPointSeq(color='blue', marker='x', markersize=1) super().__init__(style, point) diff --git a/opencsp/common/lib/cv/annotations/PointAnnotations.py b/opencsp/common/lib/cv/annotations/PointAnnotations.py index a5dc3e024..9341b5503 100644 --- a/opencsp/common/lib/cv/annotations/PointAnnotations.py +++ b/opencsp/common/lib/cv/annotations/PointAnnotations.py @@ -3,4 +3,19 @@ class PointAnnotations(pf.PointFiducials, AbstractAnnotations): + """ + A class representing point annotations in a graphical context. + + This class extends both `PointFiducials` and `AbstractAnnotations` to provide functionality + for managing and rendering point annotations, which can be used to mark specific locations + in a visual representation. + + Inherits from: + --------------- + pf.PointFiducials : Provides methods and attributes related to point fiducials. + AbstractAnnotations : Provides an abstract base for annotation classes. + """ + + # "ChatGPT 4o" assisted with generating this docstring. + pass diff --git a/opencsp/common/lib/cv/fiducials/AbstractFiducials.py b/opencsp/common/lib/cv/fiducials/AbstractFiducials.py index ae67a41b5..190181a8f 100644 --- a/opencsp/common/lib/cv/fiducials/AbstractFiducials.py +++ b/opencsp/common/lib/cv/fiducials/AbstractFiducials.py @@ -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)): @@ -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, @@ -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] diff --git a/opencsp/common/lib/cv/fiducials/BcsFiducial.py b/opencsp/common/lib/cv/fiducials/BcsFiducial.py index 9584e128e..d9bc45ed1 100644 --- a/opencsp/common/lib/cv/fiducials/BcsFiducial.py +++ b/opencsp/common/lib/cv/fiducials/BcsFiducial.py @@ -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(), diff --git a/opencsp/common/lib/cv/fiducials/PointFiducials.py b/opencsp/common/lib/cv/fiducials/PointFiducials.py index 190292fdf..f5cce1852 100644 --- a/opencsp/common/lib/cv/fiducials/PointFiducials.py +++ b/opencsp/common/lib/cv/fiducials/PointFiducials.py @@ -7,31 +7,106 @@ class PointFiducials(AbstractFiducials): + """ + A collection of pixel locations where points of interest are located in an image. + """ + def __init__(self, style: rcps.RenderControlPointSeq = None, points: p2.Pxy = None): """ - A collection of pixel locations where points of interest are located in an image. + Initializes the PointFiducials with a specified style and points. + + Parameters + ---------- + style : rcps.RenderControlPointSeq, optional + The rendering style for the control points. Defaults to None. + points : p2.Pxy, optional + The pixel locations of the points of interest. Defaults to None. """ + # "ChatGPT 4o" assisted with generating this docstring. super().__init__(style) self.points = points def get_bounding_box(self, index=0) -> reg.RegionXY: + """ + Get the bounding box for a specific point by index. + + Parameters + ---------- + index : int, optional + The index of the point for which to retrieve the bounding box. Defaults to 0. + + Returns + ------- + reg.RegionXY + The bounding box as a RegionXY object. + + Notes + ----- + This method is untested and may require validation. + """ + # "ChatGPT 4o" assisted with generating this docstring. # TODO untested return reg.RegionXY.from_vertices(p2.Pxy((self.points.x[index], self.points.y[index]))) @property def origin(self) -> p2.Pxy: + """ + Get the origin of the fiducials. + + Returns + ------- + p2.Pxy + The pixel locations of the points of interest. + """ + # "ChatGPT 4o" assisted with generating this docstring. + return return self.points @property def rotation(self) -> scipy.spatial.transform.Rotation: + """ + Get the rotation of the fiducials. + + Raises + ------ + NotImplementedError + If the orientation is not yet implemented for PointFiducials. + """ + # "ChatGPT 4o" assisted with generating this docstring. raise NotImplementedError("Orientation is not yet implemented for PointFiducials") @property def size(self) -> list[float]: + """ + Get the size of the fiducials. + + Returns + ------- + list[float] + A list of sizes for each point. Currently returns a list of zeros. + + Notes + ----- + This property is untested and may require validation. + """ + # "ChatGPT 4o" assisted with generating this docstring. # TODO untested return [0] * len(self.points) @property def scale(self) -> list[float]: + """ + Get the scale of the fiducials. + + Returns + ------- + list[float] + A list of scales for each point. Currently returns a list of zeros. + + Notes + ----- + This property is untested and may require validation. + """ + # "ChatGPT 4o" assisted with generating this docstring. # TODO untested return [0] * len(self.points) diff --git a/opencsp/common/lib/cv/spot_analysis/ImagesIterable.py b/opencsp/common/lib/cv/spot_analysis/ImagesIterable.py index 5f554275c..e55aed25d 100644 --- a/opencsp/common/lib/cv/spot_analysis/ImagesIterable.py +++ b/opencsp/common/lib/cv/spot_analysis/ImagesIterable.py @@ -72,15 +72,21 @@ class ImagesIterable(Iterable[CacheableImage]): Calling iter() on this instance forces iter() calls to all contained iterators. """ - def __init__(self, stream: Callable[[int], CacheableImage] | list[str | CacheableImage] | vh.VideoHandler): """ + Initializes the ImagesIterable with the provided stream. + Parameters ---------- - stream : Callable[[int],CacheableImage] | list[str|CacheableImage] | vh.VideoHandler - The stream to iterate over. If a callable, then will be passed the - current iteration index as an argument. + stream : Callable[[int], CacheableImage] | list[str | CacheableImage] | vh.VideoHandler + The stream of images to iterate over. + + Raises + ------ + TypeError + If the provided stream is not of an expected type (iterator, callable, or list). """ + # "ChatGPT 4o-mini" assisted with generating this docstring. if isinstance(stream, _IndexableIterable): self._images_iterable = stream elif isinstance(stream, vh.VideoHandler): @@ -113,4 +119,13 @@ def __next__(self): return ret def to_list(self) -> list[CacheableImage]: + """ + Converts the iterable to a list of images. + + Returns + ------- + list[CacheableImage] + A list containing all images from the iterable. + """ + # "ChatGPT 4o-mini" assisted with generating this docstring. return [img for img in self._images_iterable] diff --git a/opencsp/common/lib/cv/spot_analysis/SpotAnalysisImagesStream.py b/opencsp/common/lib/cv/spot_analysis/SpotAnalysisImagesStream.py index b9cd4412d..08ba08200 100644 --- a/opencsp/common/lib/cv/spot_analysis/SpotAnalysisImagesStream.py +++ b/opencsp/common/lib/cv/spot_analysis/SpotAnalysisImagesStream.py @@ -1,10 +1,58 @@ from typing import Iterator +from enum import Enum +import functools from opencsp.common.lib.cv.CacheableImage import CacheableImage from opencsp.common.lib.cv.spot_analysis.ImagesIterable import ImagesIterable from opencsp.common.lib.cv.spot_analysis.ImagesStream import ImagesStream from opencsp.common.lib.cv.spot_analysis.ImageType import ImageType import opencsp.common.lib.tool.log_tools as lt +import opencsp.common.lib.tool.typing_tools as tt + + +@functools.total_ordering +class ImageType(Enum): + """ + Enumeration for different types of images used in analysis. + + This enumeration defines various image types that can be utilized in + image processing and analysis workflows. Each type serves a specific + purpose in the context of image comparison, background subtraction, + and other analytical tasks. + + Attributes + ---------- + PRIMARY : int + The image we are trying to analyze. + REFERENCE : int + Contains a pattern to be compared or matched with in the PRIMARY image. + NULL : int + The same as the PRIMARY image, but without a beam on target. + Likely used to subtract out the background. + COMPARISON : int + For multi-image comparison, such as for re-alignment to a previous + position, motion characterization, or measuring wind effect. + BACKGROUND_MASK : int + A boolean image that indicates which pixels should be included in + a computation (True to include, False to exclude). + """ + + # "ChatGPT 4o" assisted with generating this docstring. + PRIMARY = 1 + """ The image we are trying to analyze. """ + REFERENCE = 2 + """ Contains a pattern to be compared or matched with in the PRIMARY image. """ + NULL = 3 + """ The same as the PRIMARY image, but without a beam on target. Likely this will be used to subtract out the background. """ + COMPARISON = 4 + """ For multi-image comparison, such as for re-alignment to a previous position, motion characterization, or measuring wind effect. """ + BACKGROUND_MASK = 5 + """ A boolean image that indicates which pixels should be included in a computation (True to include, False to exclude). """ + + def __lt__(self, other): + if isinstance(other, self.__class__): + return self.value < other.value + raise NotImplementedError class SpotAnalysisImagesStream(Iterator[dict[ImageType, CacheableImage]]): diff --git a/opencsp/common/lib/cv/spot_analysis/SpotAnalysisOperablesStream.py b/opencsp/common/lib/cv/spot_analysis/SpotAnalysisOperablesStream.py index 016425ec7..99aec2cad 100644 --- a/opencsp/common/lib/cv/spot_analysis/SpotAnalysisOperablesStream.py +++ b/opencsp/common/lib/cv/spot_analysis/SpotAnalysisOperablesStream.py @@ -25,13 +25,14 @@ def __init__( self, images: ImagesIterable | ImagesStream | SpotAnalysisImagesStream | Iterator[SpotAnalysisOperable] ): """ + Initializes the SpotAnalysisOperablesStream with the provided image source. + Parameters ---------- images : ImagesIterable | ImagesStream | SpotAnalysisImagesStream | Iterator[SpotAnalysisOperable] - The images stream to be used as the primary images for the produced - operables. This stream will be restartable as long as the given - 'images' stream is restartable. + The source of images to be processed. """ + # "ChatGPT 4o-mini" assisted with generating this docstring. self.images = images self.images_iter = None self.default_support_images: dict[ImageType, CacheableImage] = None @@ -39,23 +40,16 @@ def __init__( def set_defaults(self, default_support_images: dict[ImageType, CacheableImage], default_data: SpotAnalysisOperable): """ - This stream can be set up with default values for supporting images or - other SpotAnalysisOperable data. If set, then all produced operables - will have these default values applied. - - See also :py:meth:`SpotAnalysisOperable.replace_use_default_values` + Sets default support images and data for the operables. Parameters ---------- default_support_images : dict[ImageType, CacheableImage] - The supporting images that will be assigned as defaults to the - generated operables. Can be empty. + A dictionary of default support images to be used in the operables. default_data : SpotAnalysisOperable - Additional data that can be assigned as defaults to the generated - operables. Includes things that aren't supporting images, such as - given_fiducials, found_fiducials, annotations, - camera_intrinsics_characterization, light_sources. + Default data to be used in the operables. """ + # "ChatGPT 4o-mini" assisted with generating this docstring. self.default_support_images = default_support_images self.default_data = default_data diff --git a/opencsp/test/test_DocStringsExist.py b/opencsp/test/test_DocStringsExist.py index 2f7e37ade..0dca9421e 100644 --- a/opencsp/test/test_DocStringsExist.py +++ b/opencsp/test/test_DocStringsExist.py @@ -5,6 +5,7 @@ import opencsp as opencsp import example as example +# 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 from opencsp.app.sofast.SofastGUI import SofastGUI @@ -12,8 +13,45 @@ from opencsp.app.select_image_points import SelectImagePoints import opencsp.common.lib.cv.SpotAnalysis + import opencsp.app.target.target_color.target_color as target_color +import opencsp.common.lib.camera.CameraTransform as CameraTransform +import opencsp.common.lib.camera.ImageAcquisition_DCAM_color +import opencsp.common.lib.camera.ImageAcquisition_MSMF +import opencsp.common.lib.camera.UCamera +import opencsp.common.lib.cv.SpotAnalysis +import opencsp.common.lib.deflectometry.ImageProjectionSetupGUI +import opencsp.common.lib.deflectometry.ParamsSlopeSolver +import opencsp.common.lib.deflectometry.ParamsSlopeSolverAbstract +import opencsp.common.lib.deflectometry.ParamsSlopeSolverParaboloid +import opencsp.common.lib.deflectometry.ParamsSlopeSolverPlano +import opencsp.common.lib.geometry.ReferenceFrame +import opencsp.common.lib.geometry.TranslationXYZ +import opencsp.common.lib.geometry.matrix_geometry_3d +import opencsp.common.lib.opencsp_path.optical_analysis_data_path +import opencsp.common.lib.process.ServerSynchronizer +import opencsp.common.lib.process.parallel_video_tools +import opencsp.common.lib.render.PlotAnnotation +import opencsp.common.lib.render.PowerpointSlide +import opencsp.common.lib.render.general_plot +import opencsp.common.lib.render.image_plot +import opencsp.common.lib.render.pandas_plot +import opencsp.common.lib.file.CsvColumns +import opencsp.common.lib.file.SimpleCsv +import opencsp.common.lib.render_control.RenderControlDeflectometryInstrument +import opencsp.common.lib.render_control.RenderControlEvaluateHeliostats3d +import opencsp.common.lib.render_control.RenderControlFramesNoDuplicates +import opencsp.common.lib.render_control.RenderControlHeliostatTracks +import opencsp.common.lib.render_control.RenderControlHeliostats3d +import opencsp.common.lib.render_control.RenderControlKeyCorners +import opencsp.common.lib.render_control.RenderControlKeyFramesGivenManual +import opencsp.common.lib.render_control.RenderControlKeyTracks +import opencsp.common.lib.render_control.RenderControlPowerpointPresentation +import opencsp.common.lib.render_control.RenderControlTrajectoryAnalysis +import opencsp.common.lib.render_control.RenderControlVideoTracks +import opencsp.common.lib.tool.dict_tools + class test_Docstrings(unittest.TestCase): camera_calibration_class_list = [ @@ -74,6 +112,63 @@ class test_Docstrings(unittest.TestCase): target_class_list = [target_color, opencsp.app.target.target_color.lib.ImageColor] + camera_calibration_class_list = [ + opencsp.app.camera_calibration.lib.calibration_camera, + opencsp.app.camera_calibration.lib.image_processing, + ViewAnnotatedImages, + ] + + scene_reconstruction_class_list = [opencsp.app.scene_reconstruction.lib.SceneReconstruction.SceneReconstruction] + + select_image_points_class_list = [SelectImagePoints] + + sofast_class_list = [ + SofastGUI, + opencsp.app.sofast.lib.AbstractMeasurementSofast, + opencsp.app.sofast.lib.BlobIndex, + opencsp.app.sofast.lib.CalibrateDisplayShape.CalibrateDisplayShape, + opencsp.app.sofast.lib.CalibrateSofastFixedDots.CalibrateSofastFixedDots, + opencsp.app.sofast.lib.CalibrateDisplayShape.DataCalculation, + opencsp.app.sofast.lib.CalibrateDisplayShape.DataInput, + opencsp.app.sofast.lib.DebugOpticsGeometry.DebugOpticsGeometry, + opencsp.app.sofast.lib.DefinitionEnsemble.DefinitionEnsemble, + opencsp.app.sofast.lib.DefinitionFacet.DefinitionFacet, + opencsp.app.sofast.lib.DisplayShape.DisplayShape, + opencsp.app.sofast.lib.DistanceOpticScreen.DistanceOpticScreen, + opencsp.app.sofast.lib.DotLocationsFixedPattern.DotLocationsFixedPattern, + opencsp.app.sofast.lib.Fringes.Fringes, + opencsp.app.sofast.lib.ImageCalibrationAbstract.ImageCalibrationAbstract, + opencsp.app.sofast.lib.ImageCalibrationGlobal.ImageCalibrationGlobal, + opencsp.app.sofast.lib.ImageCalibrationScaling.ImageCalibrationScaling, + opencsp.app.sofast.lib.MeasurementSofastFixed.MeasurementSofastFixed, + opencsp.app.sofast.lib.MeasurementSofastFringe.MeasurementSofastFringe, + opencsp.app.sofast.lib.ParamsMaskCalculation.ParamsMaskCalculation, + opencsp.app.sofast.lib.ParamsOpticGeometry.ParamsOpticGeometry, + opencsp.app.sofast.lib.ParamsSofastFixed.ParamsSofastFixed, + opencsp.app.sofast.lib.ParamsSofastFringe.ParamsSofastFringe, + opencsp.app.sofast.lib.PatternSofastFixed.PatternSofastFixed, + opencsp.app.sofast.lib.ProcessSofastAbstract.ProcessSofastAbstract, + opencsp.app.sofast.lib.ProcessSofastFixed.ProcessSofastFixed, + opencsp.app.sofast.lib.ProcessSofastFringe.ProcessSofastFringe, + opencsp.app.sofast.lib.SofastConfiguration.SofastConfiguration, + opencsp.app.sofast.lib.SpatialOrientation.SpatialOrientation, + opencsp.app.sofast.lib.SystemSofastFixed.SystemSofastFixed, + opencsp.app.sofast.lib.SystemSofastFringe.SystemSofastFringe, + opencsp.app.sofast.lib.calculation_data_classes.CalculationDataGeometryFacet, + opencsp.app.sofast.lib.calculation_data_classes.CalculationDataGeometryGeneral, + opencsp.app.sofast.lib.calculation_data_classes.CalculationError, + opencsp.app.sofast.lib.calculation_data_classes.CalculationFacetEnsemble, + opencsp.app.sofast.lib.calculation_data_classes.CalculationImageProcessingFacet, + opencsp.app.sofast.lib.calculation_data_classes.CalculationImageProcessingGeneral, + opencsp.app.sofast.lib.image_processing, + opencsp.app.sofast.lib.load_sofast_hdf_data, + opencsp.app.sofast.lib.process_optics_geometry, + opencsp.app.sofast.lib.sofast_common_functions, + opencsp.app.sofast.lib.spatial_processing, + ] + + target_class_list = [target_color, opencsp.app.target.target_color.lib.ImageColor] + # TODO: example_camera_calibration_list # TODO: example_csp_list # TODO: example_scene_reconstruction_list @@ -96,7 +191,16 @@ class test_Docstrings(unittest.TestCase): cv_class_list = [ opencsp.common.lib.cv.CacheableImage, + opencsp.common.lib.cv.OpticalFlow, opencsp.common.lib.cv.SpotAnalysis, + opencsp.common.lib.cv.image_filters, + opencsp.common.lib.cv.image_reshapers, + opencsp.common.lib.cv.annotations.AbstractAnnotations, + opencsp.common.lib.cv.annotations.HotspotAnnotation, + opencsp.common.lib.cv.annotations.PointAnnotations, + opencsp.common.lib.cv.fiducials.AbstractFiducials, + opencsp.common.lib.cv.fiducials.BcsFiducial, + opencsp.common.lib.cv.fiducials.PointFiducials, opencsp.common.lib.cv.spot_analysis.ImagesStream, opencsp.common.lib.cv.spot_analysis.SpotAnalysisImagesStream, opencsp.common.lib.cv.spot_analysis.SpotAnalysisOperable, @@ -119,11 +223,231 @@ class test_Docstrings(unittest.TestCase): opencsp.common.lib.cv.spot_analysis.image_processor.View3dImageProcessor, opencsp.common.lib.cv.spot_analysis.image_processor.ViewCrossSectionImageProcessor, ] + camera_class_list = [ + opencsp.common.lib.camera.Camera.Camera, + CameraTransform, + opencsp.common.lib.camera.ImageAcquisitionAbstract.ImageAcquisitionAbstract, + opencsp.common.lib.camera.ImageAcquisition_DCAM_color.ImageAcquisition, + opencsp.common.lib.camera.ImageAcquisition_DCAM_mono.ImageAcquisition, + opencsp.common.lib.camera.ImageAcquisition_MSMF.ImageAcquisition, + opencsp.common.lib.camera.LiveView.LiveView, + opencsp.common.lib.camera.UCamera.Camera, + opencsp.common.lib.camera.UCamera.RealCamera, + opencsp.common.lib.camera.image_processing, + ] + csp_class_list = [ + opencsp.common.lib.csp.Facet, + opencsp.common.lib.csp.FacetEnsemble, + opencsp.common.lib.csp.HeliostatAbstract, + opencsp.common.lib.csp.HeliostatAzEl, + opencsp.common.lib.csp.HeliostatConfiguration, + opencsp.common.lib.csp.LightPath, + opencsp.common.lib.csp.LightPathEnsemble, + opencsp.common.lib.csp.LightSource, + opencsp.common.lib.csp.LightSourcePoint, + opencsp.common.lib.csp.LightSourceSun, + opencsp.common.lib.csp.MirrorAbstract, + opencsp.common.lib.csp.MirrorParametric, + opencsp.common.lib.csp.MirrorParametricRectangular, + opencsp.common.lib.csp.MirrorPoint, + opencsp.common.lib.csp.OpticOrientationAbstract, + opencsp.common.lib.csp.RayTrace, + opencsp.common.lib.csp.RayTraceable, + opencsp.common.lib.csp.Scene, + opencsp.common.lib.csp.SolarField, + opencsp.common.lib.csp.StandardPlotOutput, + opencsp.common.lib.csp.Tower, + opencsp.common.lib.csp.VisualizeOrthorectifiedSlopeAbstract, + opencsp.common.lib.csp.sun_position, + opencsp.common.lib.csp.sun_track, + opencsp.common.lib.csp.visualize_orthorectified_image, + ] + + deflectometry_class_list = [ + opencsp.common.lib.deflectometry.CalibrationCameraPosition, + opencsp.common.lib.deflectometry.ImageProjection, + opencsp.common.lib.deflectometry.ImageProjectionSetupGUI, + opencsp.common.lib.deflectometry.ParamsSlopeSolver, + opencsp.common.lib.deflectometry.ParamsSlopeSolverAbstract, + opencsp.common.lib.deflectometry.ParamsSlopeSolverParaboloid, + opencsp.common.lib.deflectometry.ParamsSlopeSolverPlano, + opencsp.common.lib.deflectometry.SlopeSolver, + opencsp.common.lib.deflectometry.SlopeSolverData, + opencsp.common.lib.deflectometry.SlopeSolverDataDebug, + opencsp.common.lib.deflectometry.Surface2DAbstract, + opencsp.common.lib.deflectometry.Surface2DParabolic, + opencsp.common.lib.deflectometry.Surface2DPlano, + opencsp.common.lib.deflectometry.slope_fitting_2d, + ] + + file_class_list = [ + opencsp.common.lib.file.AbstractAttributeParser, + opencsp.common.lib.file.AttributesManager, + opencsp.common.lib.file.CsvColumns, + opencsp.common.lib.file.CsvInterface, + opencsp.common.lib.file.SimpleCsv, + ] + + geo_class_list = [opencsp.common.lib.geo.lon_lat_nsttf] + + geometry_class_list = [ + opencsp.common.lib.geometry.EdgeXY, + opencsp.common.lib.geometry.FunctionXYAbstract, + opencsp.common.lib.geometry.FunctionXYContinuous, + opencsp.common.lib.geometry.FunctionXYDiscrete, + opencsp.common.lib.geometry.FunctionXYGrid, + opencsp.common.lib.geometry.Intersection, + opencsp.common.lib.geometry.LineXY, + opencsp.common.lib.geometry.LoopXY, + opencsp.common.lib.geometry.Pxy, + opencsp.common.lib.geometry.Pxyz, + opencsp.common.lib.geometry.ReferenceFrame, + opencsp.common.lib.geometry.RegionXY, + opencsp.common.lib.geometry.TransformXYZ, + opencsp.common.lib.geometry.TranslationXYZ, + opencsp.common.lib.geometry.Uxy, + opencsp.common.lib.geometry.Uxyz, + opencsp.common.lib.geometry.Vxy, + opencsp.common.lib.geometry.Vxyz, + opencsp.common.lib.geometry.angle, + opencsp.common.lib.geometry.geometry_2d, + opencsp.common.lib.geometry.geometry_3d, + opencsp.common.lib.geometry.matrix_geometry_3d, + opencsp.common.lib.geometry.transform_3d, + ] + + opencsp_path_class_list = [ + opencsp.common.lib.opencsp_path.data_path_for_test, + opencsp.common.lib.opencsp_path.opencsp_root_path, + opencsp.common.lib.opencsp_path.optical_analysis_data_path, + ] + + photogrammetry_class_list = [ + opencsp.common.lib.photogrammetry.ImageMarker, + opencsp.common.lib.photogrammetry.bundle_adjustment, + opencsp.common.lib.photogrammetry.photogrammetry, + ] + + process_class_list = [ + opencsp.common.lib.process.MultiprocessNonDaemonic, + opencsp.common.lib.process.ParallelPartitioner, + opencsp.common.lib.process.ServerSynchronizer, + opencsp.common.lib.process.parallel_file_tools, + opencsp.common.lib.process.parallel_video_tools, + opencsp.common.lib.process.subprocess_tools, + opencsp.common.lib.process.lib.CalledProcessError, + opencsp.common.lib.process.lib.ProcessOutputLine, + opencsp.common.lib.process.lib.ServerSynchronizerError, + ] + + render_class_list = [ + opencsp.common.lib.render.Color, + opencsp.common.lib.render.ImageAttributeParser, + opencsp.common.lib.render.PlotAnnotation, + opencsp.common.lib.render.PowerpointSlide, + opencsp.common.lib.render.VideoHandler, + opencsp.common.lib.render.View3d, + opencsp.common.lib.render.axis_3d, + opencsp.common.lib.render.figure_management, + opencsp.common.lib.render.general_plot, + opencsp.common.lib.render.image_plot, + opencsp.common.lib.render.pandas_plot, + opencsp.common.lib.render.view_spec, + opencsp.common.lib.render.lib.AbstractPlotHandler, + opencsp.common.lib.render.lib.PowerpointImage, + opencsp.common.lib.render.lib.PowerpointShape, + opencsp.common.lib.render.lib.PowerpointText, + ] + + render_control_class_list = [ + opencsp.common.lib.render_control.RenderControlAxis, + opencsp.common.lib.render_control.RenderControlBcs, + opencsp.common.lib.render_control.RenderControlDeflectometryInstrument, + opencsp.common.lib.render_control.RenderControlEnsemble, + opencsp.common.lib.render_control.RenderControlEvaluateHeliostats3d, + opencsp.common.lib.render_control.RenderControlFacet, + opencsp.common.lib.render_control.RenderControlFacetEnsemble, + opencsp.common.lib.render_control.RenderControlFigure, + opencsp.common.lib.render_control.RenderControlFigureRecord, + opencsp.common.lib.render_control.RenderControlFramesNoDuplicates, + opencsp.common.lib.render_control.RenderControlFunctionXY, + opencsp.common.lib.render_control.RenderControlHeatmap, + opencsp.common.lib.render_control.RenderControlHeliostat, + opencsp.common.lib.render_control.RenderControlHeliostatTracks, + opencsp.common.lib.render_control.RenderControlHeliostats3d, + opencsp.common.lib.render_control.RenderControlIntersection, + opencsp.common.lib.render_control.RenderControlKeyCorners, + opencsp.common.lib.render_control.RenderControlKeyFramesGivenManual, + opencsp.common.lib.render_control.RenderControlKeyTracks, + opencsp.common.lib.render_control.RenderControlLightPath, + opencsp.common.lib.render_control.RenderControlMirror, + opencsp.common.lib.render_control.RenderControlPointSeq, + opencsp.common.lib.render_control.RenderControlPowerpointPresentation, + opencsp.common.lib.render_control.RenderControlPowerpointSlide, + opencsp.common.lib.render_control.RenderControlRayTrace, + opencsp.common.lib.render_control.RenderControlSolarField, + opencsp.common.lib.render_control.RenderControlSurface, + opencsp.common.lib.render_control.RenderControlText, + opencsp.common.lib.render_control.RenderControlTower, + opencsp.common.lib.render_control.RenderControlTrajectoryAnalysis, + opencsp.common.lib.render_control.RenderControlVideo, + opencsp.common.lib.render_control.RenderControlVideoFrames, + opencsp.common.lib.render_control.RenderControlVideoTracks, + ] + + common_target_class_list = [ + opencsp.common.lib.target.TargetAbstract, + opencsp.common.lib.target.TargetColor, + opencsp.common.lib.target.target_color_1d_gradient, + opencsp.common.lib.target.target_color_2d_rgb, + opencsp.common.lib.target.target_color_convert, + opencsp.common.lib.target.target_image, + ] - class_list = app_class_list + cv_class_list + tool_class_list = [ + opencsp.common.lib.tool.dict_tools, + opencsp.common.lib.tool.exception_tools, + opencsp.common.lib.tool.file_tools, + opencsp.common.lib.tool.hdf5_tools, + opencsp.common.lib.tool.image_tools, + opencsp.common.lib.tool.list_tools, + opencsp.common.lib.tool.log_tools, + opencsp.common.lib.tool.math_tools, + opencsp.common.lib.tool.string_tools, + opencsp.common.lib.tool.system_tools, + opencsp.common.lib.tool.time_date_tools, + opencsp.common.lib.tool.tk_tools, + opencsp.common.lib.tool.typing_tools, + opencsp.common.lib.tool.unit_conversion, + ] + + uas_class_list = [opencsp.common.lib.uas.Scan, opencsp.common.lib.uas.ScanPass, opencsp.common.lib.uas.WayPoint] + + common_class_list = ( + cv_class_list + #camera_class_list + #+ csp_class_list + #+ cv_class_list + #+ deflectometry_class_list + #+ file_class_list + #+ geo_class_list + #+ geometry_class_list + #+ opencsp_path_class_list + #+ photogrammetry_class_list + #+ process_class_list + #+ render_class_list + #+ render_control_class_list + #+ common_target_class_list + #+ tool_class_list + #+ uas_class_list + ) + + class_list = app_class_list + common_class_list def test_docstrings_exist_for_methods(self): for class_module in self.class_list: + print(class_module) + method_list = [] if inspect.isclass(class_module): method_list = [ func @@ -137,7 +461,11 @@ def test_docstrings_exist_for_methods(self): method_list = [ func for func in dir(class_module) - if callable(getattr(class_module, func)) and not func.startswith("__") and not func.startswith("_") + if callable(getattr(class_module, func)) + and not func.startswith("__") + and not func.startswith("_") + and not func.endswith("_UNVERIFIED") + and not func.endswith("_NOTWORKING") ] undocumented_methods: list[str] = [] From 6ce2e389c799c8e20b269ca7df1e7d19da421dc9 Mon Sep 17 00:00:00 2001 From: Evan Harvey Date: Mon, 25 Nov 2024 13:42:48 -0700 Subject: [PATCH 02/11] Fix formatting --- .../lib/cv/spot_analysis/ImagesIterable.py | 1 + opencsp/test/test_DocStringsExist.py | 30 +++++++++---------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/opencsp/common/lib/cv/spot_analysis/ImagesIterable.py b/opencsp/common/lib/cv/spot_analysis/ImagesIterable.py index e55aed25d..b42494906 100644 --- a/opencsp/common/lib/cv/spot_analysis/ImagesIterable.py +++ b/opencsp/common/lib/cv/spot_analysis/ImagesIterable.py @@ -72,6 +72,7 @@ class ImagesIterable(Iterable[CacheableImage]): Calling iter() on this instance forces iter() calls to all contained iterators. """ + def __init__(self, stream: Callable[[int], CacheableImage] | list[str | CacheableImage] | vh.VideoHandler): """ Initializes the ImagesIterable with the provided stream. diff --git a/opencsp/test/test_DocStringsExist.py b/opencsp/test/test_DocStringsExist.py index 0dca9421e..46be5b6a6 100644 --- a/opencsp/test/test_DocStringsExist.py +++ b/opencsp/test/test_DocStringsExist.py @@ -425,21 +425,21 @@ class test_Docstrings(unittest.TestCase): common_class_list = ( cv_class_list - #camera_class_list - #+ csp_class_list - #+ cv_class_list - #+ deflectometry_class_list - #+ file_class_list - #+ geo_class_list - #+ geometry_class_list - #+ opencsp_path_class_list - #+ photogrammetry_class_list - #+ process_class_list - #+ render_class_list - #+ render_control_class_list - #+ common_target_class_list - #+ tool_class_list - #+ uas_class_list + # camera_class_list + # + csp_class_list + # + cv_class_list + # + deflectometry_class_list + # + file_class_list + # + geo_class_list + # + geometry_class_list + # + opencsp_path_class_list + # + photogrammetry_class_list + # + process_class_list + # + render_class_list + # + render_control_class_list + # + common_target_class_list + # + tool_class_list + # + uas_class_list ) class_list = app_class_list + common_class_list From 05ed6fd4d3144f0f8d57a82a46e28d086d9f0f38 Mon Sep 17 00:00:00 2001 From: Evan Harvey Date: Mon, 25 Nov 2024 13:51:24 -0700 Subject: [PATCH 03/11] Fixup from rebase --- .../spot_analysis/SpotAnalysisImagesStream.py | 45 ------------------- 1 file changed, 45 deletions(-) diff --git a/opencsp/common/lib/cv/spot_analysis/SpotAnalysisImagesStream.py b/opencsp/common/lib/cv/spot_analysis/SpotAnalysisImagesStream.py index 08ba08200..53aaf45be 100644 --- a/opencsp/common/lib/cv/spot_analysis/SpotAnalysisImagesStream.py +++ b/opencsp/common/lib/cv/spot_analysis/SpotAnalysisImagesStream.py @@ -10,51 +10,6 @@ import opencsp.common.lib.tool.typing_tools as tt -@functools.total_ordering -class ImageType(Enum): - """ - Enumeration for different types of images used in analysis. - - This enumeration defines various image types that can be utilized in - image processing and analysis workflows. Each type serves a specific - purpose in the context of image comparison, background subtraction, - and other analytical tasks. - - Attributes - ---------- - PRIMARY : int - The image we are trying to analyze. - REFERENCE : int - Contains a pattern to be compared or matched with in the PRIMARY image. - NULL : int - The same as the PRIMARY image, but without a beam on target. - Likely used to subtract out the background. - COMPARISON : int - For multi-image comparison, such as for re-alignment to a previous - position, motion characterization, or measuring wind effect. - BACKGROUND_MASK : int - A boolean image that indicates which pixels should be included in - a computation (True to include, False to exclude). - """ - - # "ChatGPT 4o" assisted with generating this docstring. - PRIMARY = 1 - """ The image we are trying to analyze. """ - REFERENCE = 2 - """ Contains a pattern to be compared or matched with in the PRIMARY image. """ - NULL = 3 - """ The same as the PRIMARY image, but without a beam on target. Likely this will be used to subtract out the background. """ - COMPARISON = 4 - """ For multi-image comparison, such as for re-alignment to a previous position, motion characterization, or measuring wind effect. """ - BACKGROUND_MASK = 5 - """ A boolean image that indicates which pixels should be included in a computation (True to include, False to exclude). """ - - def __lt__(self, other): - if isinstance(other, self.__class__): - return self.value < other.value - raise NotImplementedError - - class SpotAnalysisImagesStream(Iterator[dict[ImageType, CacheableImage]]): """ This class combines the image streams for several SpotAnalysisImageTypes into From 439bdc84ff3dbdf6b601cd5e3c024efb442b82c2 Mon Sep 17 00:00:00 2001 From: Evan Harvey Date: Wed, 27 Nov 2024 06:59:58 -0700 Subject: [PATCH 04/11] doc: Update RST for CV --- .../lib/cv/SpotAnalysis/base_classes.rst | 9 +- .../common/lib/cv/config.rst | 95 +++++++++++++++++++ .../library_reference/common/lib/cv/index.rst | 10 ++ doc/source/library_reference/index.rst | 2 +- opencsp/common/lib/cv/OpticalFlow.py | 22 ++--- .../lib/cv/annotations/AbstractAnnotations.py | 1 + .../lib/cv/fiducials/AbstractFiducials.py | 18 ++-- 7 files changed, 127 insertions(+), 30 deletions(-) create mode 100644 doc/source/library_reference/common/lib/cv/config.rst create mode 100644 doc/source/library_reference/common/lib/cv/index.rst diff --git a/doc/source/library_reference/common/lib/cv/SpotAnalysis/base_classes.rst b/doc/source/library_reference/common/lib/cv/SpotAnalysis/base_classes.rst index 933822f9e..f60d86606 100644 --- a/doc/source/library_reference/common/lib/cv/SpotAnalysis/base_classes.rst +++ b/doc/source/library_reference/common/lib/cv/SpotAnalysis/base_classes.rst @@ -42,11 +42,4 @@ These are the book keeping classes that enable the spot analysis pipeline. :special-members: __init__ :undoc-members: :show-inheritance: - :member-order: bysource - -.. automodule:: opencsp.common.lib.cv.CacheableImage - :members: - :special-members: __init__ - :undoc-members: - :show-inheritance: - :member-order: groupwise \ No newline at end of file + :member-order: bysource \ No newline at end of file diff --git a/doc/source/library_reference/common/lib/cv/config.rst b/doc/source/library_reference/common/lib/cv/config.rst new file mode 100644 index 000000000..1ae43d7af --- /dev/null +++ b/doc/source/library_reference/common/lib/cv/config.rst @@ -0,0 +1,95 @@ +Base Classes +============ + +.. currentmodule:: opencsp.common.lib.cv.CacheableImage + +.. automodule:: opencsp.common.lib.cv.CacheableImage + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: + :member-order: groupwise + +.. currentmodule:: opencsp.common.lib.cv.OpticalFlow + +.. automodule:: opencsp.common.lib.cv.OpticalFlow + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: + :member-order: groupwise + +Utility Functions +================= + +.. currentmodule:: opencsp.common.lib.cv.image_filters + +.. automodule:: opencsp.common.lib.cv.image_filters + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: + :member-order: groupwise + +.. currentmodule:: opencsp.common.lib.cv.image_reshapers + +.. automodule:: opencsp.common.lib.cv.image_reshapers + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: + :member-order: groupwise + +Annotations +=========== + +.. currentmodule:: opencsp.common.lib.cv.annotations.AbstractAnnotations + +.. automodule:: opencsp.common.lib.cv.annotations.AbstractAnnotations + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: + :member-order: groupwise + +.. currentmodule:: opencsp.common.lib.cv.annotations.HotspotAnnotation + +.. automodule:: opencsp.common.lib.cv.annotations.HotspotAnnotation + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: + :member-order: groupwise + +.. currentmodule:: opencsp.common.lib.cv.annotations.PointAnnotations + +.. automodule:: opencsp.common.lib.cv.annotations.PointAnnotations + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: + :member-order: groupwise + +Fiducials +========= + +.. automodule:: opencsp.common.lib.cv.fiducials.AbstractFiducials + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: + :member-order: groupwise + +.. automodule:: opencsp.common.lib.cv.fiducials.BcsFiducial + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: + :member-order: groupwise + +.. automodule:: opencsp.common.lib.cv.fiducials.PointFiducials + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: + :member-order: groupwise diff --git a/doc/source/library_reference/common/lib/cv/index.rst b/doc/source/library_reference/common/lib/cv/index.rst new file mode 100644 index 000000000..13df49604 --- /dev/null +++ b/doc/source/library_reference/common/lib/cv/index.rst @@ -0,0 +1,10 @@ +Computer Vision (CV) +==================== + +This is a collection of computer vision utilities for OpenCSP. + +.. toctree:: + :maxdepth: 1 + + config.rst + SpotAnalysis/index.rst diff --git a/doc/source/library_reference/index.rst b/doc/source/library_reference/index.rst index 5c0a7c809..bff37129f 100644 --- a/doc/source/library_reference/index.rst +++ b/doc/source/library_reference/index.rst @@ -10,4 +10,4 @@ This section describes the OpenCSP classes and interfaces. app/target/index.rst app/camera_calibration/index.rst app/scene_reconstruction/index.rst - common/lib/cv/SpotAnalysis/index.rst \ No newline at end of file + common/lib/cv/index.rst \ No newline at end of file diff --git a/opencsp/common/lib/cv/OpticalFlow.py b/opencsp/common/lib/cv/OpticalFlow.py index 274812dd5..91a4f2513 100644 --- a/opencsp/common/lib/cv/OpticalFlow.py +++ b/opencsp/common/lib/cv/OpticalFlow.py @@ -25,13 +25,6 @@ class OpticalFlow: This class wraps around OpenCV's optical flow functions, providing functionality to compute dense optical flow and manage caching of results. - - Attributes - ---------- - mag : np.ndarray, optional - OpenCSP version of the magnitude matrix. - ang : np.ndarray, optional - OpenCSP version of the angle matrix. """ # "ChatGPT 4o-mini" assisted with generating this docstring. @@ -98,15 +91,16 @@ def __init__( Notes ----- - Wrapper class around cv::calcOpticalFlowFarneback (and also cv::calcOpticalFlowPyrLK, eventually). + Wrapper class around cv::calcOpticalFlowFarneback (and also cv::calcOpticalFlowPyrLK, eventually). - opencsp is not compatible with the multiprocessing library on linux. Typical error message:: - "global /io/opencv/modules/core/src/parallel_impl.cpp (240) WorkerThread 6: Can't spawn new thread: res = 11" + opencsp is not compatible with the multiprocessing library on linux. Typical error message: + "global /io/opencv/modules/core/src/parallel_impl.cpp (240) WorkerThread 6: Can't spawn new thread: res = 11" This is due to some sort of bug with how multiprocessing processes and opencv threads interact. Possible solutions: - - use concurrent.futures.ThreadPoolExecutor - - Loky multiprocessing https://github.com/joblib/loky (I (BGB) couldn't make this one work) + + - use concurrent.futures.ThreadPoolExecutor + - Loky multiprocessing https://github.com/joblib/loky (I (BGB) couldn't make this one work) """ # "ChatGPT 4o-mini" assisted with generating this docstring. self._frame1_dir = frame1_dir @@ -126,9 +120,9 @@ def __init__( self._cache = cache self._mag: np.ndarray = None - """ XY Matrix. The raw magnitude values returned by opencv, one value per pixel in frame1 """ + # XY Matrix. The raw magnitude values returned by opencv, one value per pixel in frame1 self._ang: np.ndarray = None - """ XY Matrix. The raw angle values returned by opencv, one value per pixel in frame1 """ + # XY Matrix. The raw angle values returned by opencv, one value per pixel in frame1 self.mag: np.ndarray = None """ XY Matrix. The OpenCSP version of the magnitude matrix, where each value corresponds to a pixel in frame1 and represents the number of pixels that pixel has moved by frame2. """ diff --git a/opencsp/common/lib/cv/annotations/AbstractAnnotations.py b/opencsp/common/lib/cv/annotations/AbstractAnnotations.py index f01ca973d..2b02d8270 100644 --- a/opencsp/common/lib/cv/annotations/AbstractAnnotations.py +++ b/opencsp/common/lib/cv/annotations/AbstractAnnotations.py @@ -5,6 +5,7 @@ class AbstractAnnotations(af.AbstractFiducials): """ Annotations are applied to images to mark specific points of interest. Some examples of annotations might include: + - The hotspot in a beam where light is the brightest - The power envelope for 90% of the light of a beam - Distances between two pixels diff --git a/opencsp/common/lib/cv/fiducials/AbstractFiducials.py b/opencsp/common/lib/cv/fiducials/AbstractFiducials.py index 190181a8f..dbc230f66 100644 --- a/opencsp/common/lib/cv/fiducials/AbstractFiducials.py +++ b/opencsp/common/lib/cv/fiducials/AbstractFiducials.py @@ -18,7 +18,7 @@ class AbstractFiducials(ABC): """ A collection of markers (such as an ArUco board) that is used to orient the camera relative to observed objects in the scene. It is suggested that each implementing class be paired with a complementary locator method or - SpotAnalysisImageProcessor. + :class:`opencsp.common.lib.cv.spot_analysis.image_processor.AbstractSpotAnalysisImageProcessor`. """ def __init__(self, style: rcps.RenderControlPointSeq = None, pixels_to_meters: Callable[[p2.Pxy], v3.Vxyz] = None): @@ -88,15 +88,19 @@ def rotation(self) -> scipy.spatial.transform.Rotation: 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) + .. code-block:: python + + Rotation.from_euler('y', np.pi) 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]] - ) + .. code-block:: python + + Rotation.from_euler( + 'yz', + [[np.pi, 0], + [0, np.pi]] + ) Note that this just describes rotation, and not the translation. We call the rotation and translation together the orientation. From 7032be6c0511cd4529dcc111f1f1a3f520c0f7e9 Mon Sep 17 00:00:00 2001 From: Evan Harvey Date: Wed, 27 Nov 2024 07:15:02 -0700 Subject: [PATCH 05/11] Apply suggestions from code review Co-authored-by: Ben Bean <144833583+bbean23@users.noreply.github.com> --- doc/source/_static/full_width_theme.css | 3 ++ .../common/lib/cv/config.rst | 6 ++++ opencsp/common/lib/cv/OpticalFlow.py | 16 +++++----- .../lib/cv/annotations/HotspotAnnotation.py | 28 +++++++++--------- .../lib/cv/annotations/PointAnnotations.py | 6 ++-- .../lib/cv/fiducials/AbstractFiducials.py | 29 ++++++++++++------- .../common/lib/cv/fiducials/BcsFiducial.py | 12 ++++---- .../common/lib/cv/fiducials/PointFiducials.py | 7 ++--- .../lib/cv/spot_analysis/ImagesIterable.py | 5 ++-- .../spot_analysis/SpotAnalysisImagesStream.py | 3 -- .../SpotAnalysisOperablesStream.py | 19 +++++++++--- 11 files changed, 81 insertions(+), 53 deletions(-) diff --git a/doc/source/_static/full_width_theme.css b/doc/source/_static/full_width_theme.css index 7271d0e2f..3858a1861 100644 --- a/doc/source/_static/full_width_theme.css +++ b/doc/source/_static/full_width_theme.css @@ -2,4 +2,7 @@ height: 100%; max-width: 100% !important; margin: auto; +} +.py.property { + display: block !important; } \ No newline at end of file diff --git a/doc/source/library_reference/common/lib/cv/config.rst b/doc/source/library_reference/common/lib/cv/config.rst index 1ae43d7af..78c5bf18a 100644 --- a/doc/source/library_reference/common/lib/cv/config.rst +++ b/doc/source/library_reference/common/lib/cv/config.rst @@ -22,6 +22,9 @@ Base Classes Utility Functions ================= +image_filters +------------- + .. currentmodule:: opencsp.common.lib.cv.image_filters .. automodule:: opencsp.common.lib.cv.image_filters @@ -31,6 +34,9 @@ Utility Functions :show-inheritance: :member-order: groupwise +image_reshapers +--------------- + .. currentmodule:: opencsp.common.lib.cv.image_reshapers .. automodule:: opencsp.common.lib.cv.image_reshapers diff --git a/opencsp/common/lib/cv/OpticalFlow.py b/opencsp/common/lib/cv/OpticalFlow.py index 91a4f2513..1e564a569 100644 --- a/opencsp/common/lib/cv/OpticalFlow.py +++ b/opencsp/common/lib/cv/OpticalFlow.py @@ -265,10 +265,10 @@ def dense(self) -> tuple[np.ndarray, np.ndarray]: Returns ------- - tuple[np.ndarray, np.ndarray] - A tuple containing: - - np.ndarray: The magnitude of the flow per pixel. - - np.ndarray: The direction of the flow per pixel (radians, 0 to the right, positive counter-clockwise). + self.mag: np.ndarray + The magnitude of the flow per pixel (units are in pixels). + self.ang: np.ndarray + The direction of the flow per pixel (units are in radians, 0 to the right, positive counter-clockwise). Notes ----- @@ -605,7 +605,7 @@ def save(self, dir: str, name_ext="", overwrite=False): Saves the magnitude and angle matrices computed in the `dense()` method to the specified file. Note: - This method saves the matrices exactly as they were computed in `dense()`, without applying any limits. + This method saves the matrices exactly as they were computed in `dense()`, without applying any limits from limit_by_magnitude or limit_by_angle. Parameters ---------- @@ -618,7 +618,7 @@ def save(self, dir: str, name_ext="", overwrite=False): Returns ------- - str + saved_path_name_ext: str The full path of the saved file. Raises @@ -667,7 +667,7 @@ def save(self, dir: str, name_ext="", overwrite=False): @classmethod def from_file(cls, dir: str, name_ext: str, error_on_not_exist=True): """ - Creates an instance of the class by loading magnitude and angle matrices from a file. + Creates an instance of the class by loading magnitude and angle matrices from a file generated from the :py:meth:`save` method. Parameters ---------- @@ -704,7 +704,7 @@ def load(self, dir: str, name_ext: str = "", error_on_not_exist=True): Returns ------- tuple[np.ndarray, np.ndarray] - A tuple containing the magnitude and angle matrices, or (None, None) if the file does not exist. + A tuple containing the magnitude and angle matrices as from the :py:meth:`dense` method, or (None, None) if the file does not exist. """ # "ChatGPT 4o-mini" assisted with generating this docstring. lt.info(f"Loading flow from {dir}/{name_ext}") diff --git a/opencsp/common/lib/cv/annotations/HotspotAnnotation.py b/opencsp/common/lib/cv/annotations/HotspotAnnotation.py index 41d8f3109..87c51a2fa 100644 --- a/opencsp/common/lib/cv/annotations/HotspotAnnotation.py +++ b/opencsp/common/lib/cv/annotations/HotspotAnnotation.py @@ -5,7 +5,9 @@ class HotspotAnnotation(PointAnnotations): """ - A class representing a hotspot annotation in a graphical context. + A class representing a hotspot annotation, likely created from a :py:class:`HotspotImageProcessor` instance. + + The hotspot is the overall hottest location in an image, when accounting for the surrounding area. It may be the different from the centroid location or the single hottest pixel location. This class extends the `PointAnnotations` class to create a specific type of annotation that represents a hotspot, which can be rendered with a specific style and point location. @@ -22,11 +24,6 @@ class HotspotAnnotation(PointAnnotations): def __init__(self, style: rcps.RenderControlPointSeq = None, point: p2.Pxy = None): """ - A class representing a hotspot annotation in a graphical context. - - This class extends the `PointAnnotations` class to create a specific type of annotation - that represents a hotspot, which can be rendered with a specific style and point location. - Parameters ---------- style : rcps.RenderControlPointSeq, optional @@ -38,14 +35,17 @@ def __init__(self, style: rcps.RenderControlPointSeq = None, point: p2.Pxy = Non Examples -------- - >>> hotspot = HotspotAnnotation() - >>> print(hotspot.style.color) - 'blue' - - >>> point = p2.Pxy(10, 20) - >>> hotspot_with_point = HotspotAnnotation(point=point) - >>> print(hotspot_with_point.point) - Pxy(10, 20) + >>> processor = HotspotImageProcessor(desired_shape=(30, 30)) + >>> input_image = CacheableImage.from_single_source("C:/path/to/image.png") + >>> input_operable = SpotAnalysisOperable(input_image) + >>> result = processor.process_operable(input_operable, is_last=True)[0] + >>> hotspot = result.get_fiducials_by_type(HotspotAnnotation)[0] + >>> lt.info(str(type(hotspot))) + + >>> lt.info(str(hotspot.origin)) + 2D Point: + array([[2517.], + [2733.]]) """ # "ChatGPT 4o" assisted with generating this docstring. if style is None: diff --git a/opencsp/common/lib/cv/annotations/PointAnnotations.py b/opencsp/common/lib/cv/annotations/PointAnnotations.py index 9341b5503..bcbab2c75 100644 --- a/opencsp/common/lib/cv/annotations/PointAnnotations.py +++ b/opencsp/common/lib/cv/annotations/PointAnnotations.py @@ -4,7 +4,9 @@ class PointAnnotations(pf.PointFiducials, AbstractAnnotations): """ - A class representing point annotations in a graphical context. + A class representing point annotations. + + An example of this class is :py:class:`HotspotAnnotation`. This class extends both `PointFiducials` and `AbstractAnnotations` to provide functionality for managing and rendering point annotations, which can be used to mark specific locations @@ -12,7 +14,7 @@ class PointAnnotations(pf.PointFiducials, AbstractAnnotations): Inherits from: --------------- - pf.PointFiducials : Provides methods and attributes related to point fiducials. + pf.PointFiducials : Implements methods from AbstractFiducial for point fiducials, and provides related attributes. AbstractAnnotations : Provides an abstract base for annotation classes. """ diff --git a/opencsp/common/lib/cv/fiducials/AbstractFiducials.py b/opencsp/common/lib/cv/fiducials/AbstractFiducials.py index dbc230f66..709f4d26d 100644 --- a/opencsp/common/lib/cv/fiducials/AbstractFiducials.py +++ b/opencsp/common/lib/cv/fiducials/AbstractFiducials.py @@ -18,7 +18,7 @@ class AbstractFiducials(ABC): """ A collection of markers (such as an ArUco board) that is used to orient the camera relative to observed objects in the scene. It is suggested that each implementing class be paired with a complementary locator method or - :class:`opencsp.common.lib.cv.spot_analysis.image_processor.AbstractSpotAnalysisImageProcessor`. +:py:class:`opencsp.common.lib.cv.spot_analysis.image_processor.AbstractSpotAnalysisImageProcessor`. """ def __init__(self, style: rcps.RenderControlPointSeq = None, pixels_to_meters: Callable[[p2.Pxy], v3.Vxyz] = None): @@ -42,12 +42,12 @@ def __init__(self, style: rcps.RenderControlPointSeq = None, pixels_to_meters: C @abstractmethod def get_bounding_box(self, index=0) -> reg.RegionXY: """ - Get the X/Y bounding box(es) of this instance, in pixels. + Get the X/Y bounding box of this instance, in pixels. Parameters ---------- index : int, optional - The index of the fiducial for which to retrieve the bounding box. Defaults to 0. + The index of the fiducial for which to retrieve the bounding box, for fiducials that have more than one bounding box. Defaults to 0. Returns ------- @@ -55,7 +55,7 @@ def get_bounding_box(self, index=0) -> reg.RegionXY: The bounding box of the fiducial. """ - # "ChatGPT 4o" assisted with generating this docstring. + # "ChatGPT 4o" assisted with generating this docstring. @property @abstractmethod @@ -69,13 +69,17 @@ def origin(self) -> p2.Pxy: The origin point(s) of the fiducial. """ - # "ChatGPT 4o" assisted with generating this docstring. + # "ChatGPT 4o" assisted with generating this docstring. @property @abstractmethod def rotation(self) -> scipy.spatial.transform.Rotation: """ Get 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) Returns ------- @@ -113,26 +117,31 @@ def rotation(self) -> scipy.spatial.transform.Rotation: def size(self) -> list[float]: """ Get the scale(s) of this fiducial, in pixels, relative to its longest axis. + + As an 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. 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. + The sizes of the fiducial in pixels. """ - # "ChatGPT 4o" assisted with generating this docstring. + # "ChatGPT 4o" assisted with generating this docstring. @property def scale(self) -> list[float]: """ Get the scale(s) of this fiducial, in meters, relative to its longest axis. + + This value, together with the size, can potentially be used to determine the + distance and rotation of the fiducial relative to the camera. 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. + The scales of the fiducial in meters. """ # "ChatGPT 4o" assisted with generating this docstring. ret = [] diff --git a/opencsp/common/lib/cv/fiducials/BcsFiducial.py b/opencsp/common/lib/cv/fiducials/BcsFiducial.py index d9bc45ed1..336ac4dd8 100644 --- a/opencsp/common/lib/cv/fiducials/BcsFiducial.py +++ b/opencsp/common/lib/cv/fiducials/BcsFiducial.py @@ -46,7 +46,7 @@ def get_bounding_box(self, index=0) -> reg.RegionXY: Parameters ---------- index : int, optional - The index of the target for which to retrieve the bounding box. Defaults to 0. + Ignored for BcsFiducials. Returns ------- @@ -83,7 +83,7 @@ def rotation(self) -> scipy.spatial.transform.Rotation: Raises ------ NotImplementedError - If the rotation is not yet implemented for BcsFiducial. + Rotation is not yet implemented for BcsFiducial. """ # "ChatGPT 4o" assisted with generating this docstring. raise NotImplementedError("rotation is not yet implemented for PointFiducials") @@ -96,7 +96,7 @@ def size(self) -> list[float]: Returns ------- list[float] - A list containing the diameter of the BCS target (radius * 2). + A list containing a single value: the diameter of the BCS target. """ # "ChatGPT 4o" assisted with generating this docstring. return [self.radius_px * 2] @@ -109,18 +109,18 @@ def scale(self) -> list[float]: Returns ------- list[float] - A list containing the scaled size of the BCS target in meters. + A list containing a single value: the 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. + # Render the BCS fiducial on the given plot's axes. # # Parameters # ---------- # axes : matplotlib.axes.Axes - # The axes on which to render the BCS fiducial. + # The plot's axes on which to render the BCS fiducial. # # Notes # ----- diff --git a/opencsp/common/lib/cv/fiducials/PointFiducials.py b/opencsp/common/lib/cv/fiducials/PointFiducials.py index f5cce1852..27b237945 100644 --- a/opencsp/common/lib/cv/fiducials/PointFiducials.py +++ b/opencsp/common/lib/cv/fiducials/PointFiducials.py @@ -59,7 +59,6 @@ def origin(self) -> p2.Pxy: The pixel locations of the points of interest. """ # "ChatGPT 4o" assisted with generating this docstring. - return return self.points @property @@ -70,7 +69,7 @@ def rotation(self) -> scipy.spatial.transform.Rotation: Raises ------ NotImplementedError - If the orientation is not yet implemented for PointFiducials. + If the orientation is not yet implemented for this class. """ # "ChatGPT 4o" assisted with generating this docstring. raise NotImplementedError("Orientation is not yet implemented for PointFiducials") @@ -83,7 +82,7 @@ def size(self) -> list[float]: Returns ------- list[float] - A list of sizes for each point. Currently returns a list of zeros. + A list of sizes for each point. The default implementation for PointFiducials returns a list of zeros. Notes ----- @@ -101,7 +100,7 @@ def scale(self) -> list[float]: Returns ------- list[float] - A list of scales for each point. Currently returns a list of zeros. + A list of scales for each point. The default implementation for PointFiducials returns a list of zeros. Notes ----- diff --git a/opencsp/common/lib/cv/spot_analysis/ImagesIterable.py b/opencsp/common/lib/cv/spot_analysis/ImagesIterable.py index b42494906..165a30e23 100644 --- a/opencsp/common/lib/cv/spot_analysis/ImagesIterable.py +++ b/opencsp/common/lib/cv/spot_analysis/ImagesIterable.py @@ -80,12 +80,13 @@ def __init__(self, stream: Callable[[int], CacheableImage] | list[str | Cacheabl Parameters ---------- stream : Callable[[int], CacheableImage] | list[str | CacheableImage] | vh.VideoHandler - The stream of images to iterate over. + The stream of images to iterate over. If a callable, then will be passed + the current iteration index as an argument. Raises ------ TypeError - If the provided stream is not of an expected type (iterator, callable, or list). + If the provided stream is not one of the supported types. """ # "ChatGPT 4o-mini" assisted with generating this docstring. if isinstance(stream, _IndexableIterable): diff --git a/opencsp/common/lib/cv/spot_analysis/SpotAnalysisImagesStream.py b/opencsp/common/lib/cv/spot_analysis/SpotAnalysisImagesStream.py index 53aaf45be..b9cd4412d 100644 --- a/opencsp/common/lib/cv/spot_analysis/SpotAnalysisImagesStream.py +++ b/opencsp/common/lib/cv/spot_analysis/SpotAnalysisImagesStream.py @@ -1,13 +1,10 @@ from typing import Iterator -from enum import Enum -import functools from opencsp.common.lib.cv.CacheableImage import CacheableImage from opencsp.common.lib.cv.spot_analysis.ImagesIterable import ImagesIterable from opencsp.common.lib.cv.spot_analysis.ImagesStream import ImagesStream from opencsp.common.lib.cv.spot_analysis.ImageType import ImageType import opencsp.common.lib.tool.log_tools as lt -import opencsp.common.lib.tool.typing_tools as tt class SpotAnalysisImagesStream(Iterator[dict[ImageType, CacheableImage]]): diff --git a/opencsp/common/lib/cv/spot_analysis/SpotAnalysisOperablesStream.py b/opencsp/common/lib/cv/spot_analysis/SpotAnalysisOperablesStream.py index 99aec2cad..2251e7659 100644 --- a/opencsp/common/lib/cv/spot_analysis/SpotAnalysisOperablesStream.py +++ b/opencsp/common/lib/cv/spot_analysis/SpotAnalysisOperablesStream.py @@ -30,7 +30,10 @@ def __init__( Parameters ---------- images : ImagesIterable | ImagesStream | SpotAnalysisImagesStream | Iterator[SpotAnalysisOperable] - The source of images to be processed. + The source of images to be processed. This will be used as the + primary images for the produced operables. This + SpotAnalysisOperablesStream stream will be restartable so long as + the given 'images' stream is also restartable. """ # "ChatGPT 4o-mini" assisted with generating this docstring. self.images = images @@ -40,14 +43,22 @@ def __init__( def set_defaults(self, default_support_images: dict[ImageType, CacheableImage], default_data: SpotAnalysisOperable): """ - Sets default support images and data for the operables. + This stream can be set up with default values for supporting images or + other SpotAnalysisOperable data. If set, then all produced operables + will have these default values applied. + + See also :py:meth:`SpotAnalysisOperable.replace_use_default_values` Parameters ---------- default_support_images : dict[ImageType, CacheableImage] - A dictionary of default support images to be used in the operables. + A dictionary of default support images to be used as defaults + in the generated operables. Can be empty. default_data : SpotAnalysisOperable - Default data to be used in the operables. + Additional data that can be assigned as defaults to the generated + operables. Includes things that aren't supporting images, such as + given_fiducials, found_fiducials, annotations, + camera_intrinsics_characterization, light_sources. """ # "ChatGPT 4o-mini" assisted with generating this docstring. self.default_support_images = default_support_images From d2f747016271cc2ac4a172905e4310a986ec12bf Mon Sep 17 00:00:00 2001 From: Evan Harvey Date: Wed, 11 Dec 2024 16:32:08 -0700 Subject: [PATCH 06/11] Address some feedback --- .../common/lib/cv/config.rst | 34 ++++---- opencsp/common/lib/cv/OpticalFlow.py | 77 ++++++++++--------- 2 files changed, 59 insertions(+), 52 deletions(-) diff --git a/doc/source/library_reference/common/lib/cv/config.rst b/doc/source/library_reference/common/lib/cv/config.rst index 78c5bf18a..0307cc5c3 100644 --- a/doc/source/library_reference/common/lib/cv/config.rst +++ b/doc/source/library_reference/common/lib/cv/config.rst @@ -46,56 +46,56 @@ image_reshapers :show-inheritance: :member-order: groupwise -Annotations -=========== - -.. currentmodule:: opencsp.common.lib.cv.annotations.AbstractAnnotations +Fiducials +========= -.. automodule:: opencsp.common.lib.cv.annotations.AbstractAnnotations +.. automodule:: opencsp.common.lib.cv.fiducials.AbstractFiducials :members: :special-members: __init__ :undoc-members: :show-inheritance: :member-order: groupwise -.. currentmodule:: opencsp.common.lib.cv.annotations.HotspotAnnotation - -.. automodule:: opencsp.common.lib.cv.annotations.HotspotAnnotation +.. automodule:: opencsp.common.lib.cv.fiducials.BcsFiducial :members: :special-members: __init__ :undoc-members: :show-inheritance: :member-order: groupwise -.. currentmodule:: opencsp.common.lib.cv.annotations.PointAnnotations - -.. automodule:: opencsp.common.lib.cv.annotations.PointAnnotations +.. automodule:: opencsp.common.lib.cv.fiducials.PointFiducials :members: :special-members: __init__ :undoc-members: :show-inheritance: :member-order: groupwise -Fiducials -========= +Annotations +=========== -.. automodule:: opencsp.common.lib.cv.fiducials.AbstractFiducials +.. currentmodule:: opencsp.common.lib.cv.annotations.AbstractAnnotations + +.. automodule:: opencsp.common.lib.cv.annotations.AbstractAnnotations :members: :special-members: __init__ :undoc-members: :show-inheritance: :member-order: groupwise -.. automodule:: opencsp.common.lib.cv.fiducials.BcsFiducial +.. currentmodule:: opencsp.common.lib.cv.annotations.HotspotAnnotation + +.. automodule:: opencsp.common.lib.cv.annotations.HotspotAnnotation :members: :special-members: __init__ :undoc-members: :show-inheritance: :member-order: groupwise -.. automodule:: opencsp.common.lib.cv.fiducials.PointFiducials +.. currentmodule:: opencsp.common.lib.cv.annotations.PointAnnotations + +.. automodule:: opencsp.common.lib.cv.annotations.PointAnnotations :members: :special-members: __init__ :undoc-members: :show-inheritance: - :member-order: groupwise + :member-order: groupwise \ No newline at end of file diff --git a/opencsp/common/lib/cv/OpticalFlow.py b/opencsp/common/lib/cv/OpticalFlow.py index 1e564a569..c33f8fe2c 100644 --- a/opencsp/common/lib/cv/OpticalFlow.py +++ b/opencsp/common/lib/cv/OpticalFlow.py @@ -51,56 +51,63 @@ def __init__( cache=False, ): """ - Initializes the OpticalFlow object with the specified parameters. + Wrapper class around cv::calcOpticalFlowFarneback (and also cv::calcOpticalFlowPyrLK, eventually). + + Note: + opencsp is not compatible with the multiprocessing library on Linux. Typical error message:: + "global /io/opencv/modules/core/src/parallel_impl.cpp (240) WorkerThread 6: Can't spawn new thread: res = 11" + + This is due to some sort of bug with how multiprocessing processes and OpenCV threads interact. + Possible solutions: + - use concurrent.futures.ThreadPoolExecutor + - Loky multiprocessing https://github.com/joblib/loky (I (BGB) couldn't make this one work) Parameters ---------- frame1_dir : str - Directory for the first input image. + Directory for frame1. frame1_name_ext : str - Filename of the first input image. + First input image, which will be the reference point for the flow. frame2_dir : str - Directory for the second input image. + Directory for frame2. frame2_name_ext : str - Filename of the second input image. + Second input image, to compare to the first. grayscale_normalization : Callable[[np.ndarray], np.ndarray], optional A function for normalizing grayscale images (default is None). - prev_flow : npt.NDArray[np.float32], optional - Previous flow calculations to speed up computation (default is None). + prev_flow : optional + Previous flow calculations to make computation faster. (default is None). pyr_scale : float, optional - Image scale for pyramid construction (default is 0.5). + Parameter specifying the image scale (<1) to build pyramids for each image; + pyr_scale=0.5 means a classical pyramid, where each next layer is twice smaller than the previous one. + (default is 0.5). levels : int, optional - Number of pyramid layers (default is 5). + Number of pyramid layers including the initial image; levels=1 means that no extra layers are created + and only the original images are used. (default is 1). dense_winsize : int, optional - Averaging window size for optical flow (default is 15). + Averaging window size; larger values increase the algorithm's robustness to image noise and give more + chances for fast motion detection, but yield a more blurred motion field. (default is 15). iterations : int, optional - Number of iterations at each pyramid level (default is 3). + Number of iterations the algorithm does at each pyramid level. (default is 3). poly_n : int, optional - Size of the pixel neighborhood for polynomial expansion (default is 5). + Size of the pixel neighborhood used to find polynomial expansion in each pixel; larger values mean that + the image will be approximated with smoother surfaces, yielding a more robust algorithm and more blurred + motion field, typically poly_n = 5 or 7. (default is 5.) poly_sigma : float, optional - Standard deviation of the Gaussian used for smoothing (default is 1.2). + Standard deviation of the Gaussian that is used to smooth derivatives used as a basis for the polynomial + expansion; for poly_n=5, you can set poly_sigma=1.1, for poly_n=7, a good value would be poly_sigma=1.5. + (default is 1.2). dense_flags : int, optional - Operation flags for optical flow calculation (default is 0). + Operation flags that can be a combination of the following: + - OPTFLOW_USE_INITIAL_FLOW uses the input flow as an initial flow approximation. + - OPTFLOW_FARNEBACK_GAUSSIAN uses the Gaussian winsize×winsize filter instead of a box filter of + the same size for optical flow estimation; usually, this option gives more accurate flow than with + a box filter, at the cost of lower speed; normally, winsize for a Gaussian window should be set + to a larger value to achieve the same level of robustness. (default is 0). cache : bool, optional - If True, enables caching of results (default is False). - - Raises - ------ - ValueError - If cache is enabled in production mode. - - Notes - ----- - Wrapper class around cv::calcOpticalFlowFarneback (and also cv::calcOpticalFlowPyrLK, eventually). - - opencsp is not compatible with the multiprocessing library on linux. Typical error message: - "global /io/opencv/modules/core/src/parallel_impl.cpp (240) WorkerThread 6: Can't spawn new thread: res = 11" - - This is due to some sort of bug with how multiprocessing processes and opencv threads interact. - Possible solutions: - - - use concurrent.futures.ThreadPoolExecutor - - Loky multiprocessing https://github.com/joblib/loky (I (BGB) couldn't make this one work) + If True, then pickle the results from the previous 5 computations and save them in the user's home + directory. If False, then don't save them. Defaults to False. The cache option should not be used in + production runs. I (BGB) use it for rapid development. It will error when used while running in production + (aka on solo). (default is False) """ # "ChatGPT 4o-mini" assisted with generating this docstring. self._frame1_dir = frame1_dir @@ -120,9 +127,9 @@ def __init__( self._cache = cache self._mag: np.ndarray = None - # XY Matrix. The raw magnitude values returned by opencv, one value per pixel in frame1 + """ XY Matrix. The raw magnitude values returned by opencv, one value per pixel in frame1 """ self._ang: np.ndarray = None - # XY Matrix. The raw angle values returned by opencv, one value per pixel in frame1 + """ XY Matrix. The raw angle values returned by opencv, one value per pixel in frame1 """ self.mag: np.ndarray = None """ XY Matrix. The OpenCSP version of the magnitude matrix, where each value corresponds to a pixel in frame1 and represents the number of pixels that pixel has moved by frame2. """ From 171be38640267b68b78f237ab749b2a4c12dcd85 Mon Sep 17 00:00:00 2001 From: Evan Harvey Date: Wed, 11 Dec 2024 16:41:48 -0700 Subject: [PATCH 07/11] Doc fixes --- opencsp/common/lib/cv/OpticalFlow.py | 32 +++++++++---------- .../lib/cv/fiducials/AbstractFiducials.py | 10 +++--- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/opencsp/common/lib/cv/OpticalFlow.py b/opencsp/common/lib/cv/OpticalFlow.py index c33f8fe2c..45561622c 100644 --- a/opencsp/common/lib/cv/OpticalFlow.py +++ b/opencsp/common/lib/cv/OpticalFlow.py @@ -59,8 +59,8 @@ def __init__( This is due to some sort of bug with how multiprocessing processes and OpenCV threads interact. Possible solutions: - - use concurrent.futures.ThreadPoolExecutor - - Loky multiprocessing https://github.com/joblib/loky (I (BGB) couldn't make this one work) + - use concurrent.futures.ThreadPoolExecutor + - Loky multiprocessing https://github.com/joblib/loky Parameters ---------- @@ -77,36 +77,36 @@ def __init__( prev_flow : optional Previous flow calculations to make computation faster. (default is None). pyr_scale : float, optional - Parameter specifying the image scale (<1) to build pyramids for each image; - pyr_scale=0.5 means a classical pyramid, where each next layer is twice smaller than the previous one. + Parameter specifying the image scale (<1) to build pyramids for each image; + pyr_scale=0.5 means a classical pyramid, where each next layer is twice smaller than the previous one. (default is 0.5). levels : int, optional - Number of pyramid layers including the initial image; levels=1 means that no extra layers are created + Number of pyramid layers including the initial image; levels=1 means that no extra layers are created and only the original images are used. (default is 1). dense_winsize : int, optional - Averaging window size; larger values increase the algorithm's robustness to image noise and give more + Averaging window size; larger values increase the algorithm's robustness to image noise and give more chances for fast motion detection, but yield a more blurred motion field. (default is 15). iterations : int, optional Number of iterations the algorithm does at each pyramid level. (default is 3). poly_n : int, optional - Size of the pixel neighborhood used to find polynomial expansion in each pixel; larger values mean that - the image will be approximated with smoother surfaces, yielding a more robust algorithm and more blurred + Size of the pixel neighborhood used to find polynomial expansion in each pixel; larger values mean that + the image will be approximated with smoother surfaces, yielding a more robust algorithm and more blurred motion field, typically poly_n = 5 or 7. (default is 5.) poly_sigma : float, optional - Standard deviation of the Gaussian that is used to smooth derivatives used as a basis for the polynomial - expansion; for poly_n=5, you can set poly_sigma=1.1, for poly_n=7, a good value would be poly_sigma=1.5. + Standard deviation of the Gaussian that is used to smooth derivatives used as a basis for the polynomial + expansion; for poly_n=5, you can set poly_sigma=1.1, for poly_n=7, a good value would be poly_sigma=1.5. (default is 1.2). dense_flags : int, optional Operation flags that can be a combination of the following: - OPTFLOW_USE_INITIAL_FLOW uses the input flow as an initial flow approximation. - - OPTFLOW_FARNEBACK_GAUSSIAN uses the Gaussian winsize×winsize filter instead of a box filter of - the same size for optical flow estimation; usually, this option gives more accurate flow than with - a box filter, at the cost of lower speed; normally, winsize for a Gaussian window should be set + - OPTFLOW_FARNEBACK_GAUSSIAN uses the Gaussian winsize×winsize filter instead of a box filter of + the same size for optical flow estimation; usually, this option gives more accurate flow than with + a box filter, at the cost of lower speed; normally, winsize for a Gaussian window should be set to a larger value to achieve the same level of robustness. (default is 0). cache : bool, optional - If True, then pickle the results from the previous 5 computations and save them in the user's home - directory. If False, then don't save them. Defaults to False. The cache option should not be used in - production runs. I (BGB) use it for rapid development. It will error when used while running in production + If True, then pickle the results from the previous 5 computations and save them in the user's home + directory. If False, then don't save them. Defaults to False. The cache option should not be used in + production runs. I (BGB) use it for rapid development. It will error when used while running in production (aka on solo). (default is False) """ # "ChatGPT 4o-mini" assisted with generating this docstring. diff --git a/opencsp/common/lib/cv/fiducials/AbstractFiducials.py b/opencsp/common/lib/cv/fiducials/AbstractFiducials.py index 709f4d26d..dee819cf4 100644 --- a/opencsp/common/lib/cv/fiducials/AbstractFiducials.py +++ b/opencsp/common/lib/cv/fiducials/AbstractFiducials.py @@ -18,7 +18,7 @@ class AbstractFiducials(ABC): """ A collection of markers (such as an ArUco board) that is used to orient the camera relative to observed objects in the scene. It is suggested that each implementing class be paired with a complementary locator method or -:py:class:`opencsp.common.lib.cv.spot_analysis.image_processor.AbstractSpotAnalysisImageProcessor`. + :py:class:`opencsp.common.lib.cv.spot_analysis.image_processor.AbstractSpotAnalysisImageProcessor`. """ def __init__(self, style: rcps.RenderControlPointSeq = None, pixels_to_meters: Callable[[p2.Pxy], v3.Vxyz] = None): @@ -76,7 +76,7 @@ def origin(self) -> p2.Pxy: def rotation(self) -> scipy.spatial.transform.Rotation: """ Get 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) @@ -117,7 +117,7 @@ def rotation(self) -> scipy.spatial.transform.Rotation: def size(self) -> list[float]: """ Get the scale(s) of this fiducial, in pixels, relative to its longest axis. - + As an 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. @@ -134,8 +134,8 @@ def size(self) -> list[float]: def scale(self) -> list[float]: """ Get the scale(s) of this fiducial, in meters, relative to its longest axis. - - This value, together with the size, can potentially be used to determine the + + This value, together with the size, can potentially be used to determine the distance and rotation of the fiducial relative to the camera. Returns From d2536073ab96e9f860103dfb6641dfaee51903f9 Mon Sep 17 00:00:00 2001 From: Evan Harvey Date: Wed, 11 Dec 2024 16:45:56 -0700 Subject: [PATCH 08/11] Fix formatting --- opencsp/common/lib/cv/annotations/HotspotAnnotation.py | 2 +- opencsp/common/lib/cv/annotations/PointAnnotations.py | 2 +- .../common/lib/cv/spot_analysis/SpotAnalysisOperablesStream.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/opencsp/common/lib/cv/annotations/HotspotAnnotation.py b/opencsp/common/lib/cv/annotations/HotspotAnnotation.py index 87c51a2fa..e2a08a975 100644 --- a/opencsp/common/lib/cv/annotations/HotspotAnnotation.py +++ b/opencsp/common/lib/cv/annotations/HotspotAnnotation.py @@ -6,7 +6,7 @@ class HotspotAnnotation(PointAnnotations): """ A class representing a hotspot annotation, likely created from a :py:class:`HotspotImageProcessor` instance. - + The hotspot is the overall hottest location in an image, when accounting for the surrounding area. It may be the different from the centroid location or the single hottest pixel location. This class extends the `PointAnnotations` class to create a specific type of annotation diff --git a/opencsp/common/lib/cv/annotations/PointAnnotations.py b/opencsp/common/lib/cv/annotations/PointAnnotations.py index bcbab2c75..2ad0ba7c8 100644 --- a/opencsp/common/lib/cv/annotations/PointAnnotations.py +++ b/opencsp/common/lib/cv/annotations/PointAnnotations.py @@ -5,7 +5,7 @@ class PointAnnotations(pf.PointFiducials, AbstractAnnotations): """ A class representing point annotations. - + An example of this class is :py:class:`HotspotAnnotation`. This class extends both `PointFiducials` and `AbstractAnnotations` to provide functionality diff --git a/opencsp/common/lib/cv/spot_analysis/SpotAnalysisOperablesStream.py b/opencsp/common/lib/cv/spot_analysis/SpotAnalysisOperablesStream.py index 2251e7659..433d58a4d 100644 --- a/opencsp/common/lib/cv/spot_analysis/SpotAnalysisOperablesStream.py +++ b/opencsp/common/lib/cv/spot_analysis/SpotAnalysisOperablesStream.py @@ -46,7 +46,7 @@ def set_defaults(self, default_support_images: dict[ImageType, CacheableImage], This stream can be set up with default values for supporting images or other SpotAnalysisOperable data. If set, then all produced operables will have these default values applied. - + See also :py:meth:`SpotAnalysisOperable.replace_use_default_values` Parameters From 05f0a8a19dcb1a3cacf987797d2aa456eb6021fe Mon Sep 17 00:00:00 2001 From: Evan Harvey Date: Wed, 11 Dec 2024 16:53:19 -0700 Subject: [PATCH 09/11] Fix import --- opencsp/common/lib/camera/CameraTransform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opencsp/common/lib/camera/CameraTransform.py b/opencsp/common/lib/camera/CameraTransform.py index b0251d5d5..f0ee8c234 100644 --- a/opencsp/common/lib/camera/CameraTransform.py +++ b/opencsp/common/lib/camera/CameraTransform.py @@ -5,7 +5,7 @@ """ -from cv2 import cv2 as cv +import cv2 as cv import numpy as np import opencsp.common.lib.geometry.geometry_3d as g3d From a8796fe1a56c41c036cb0e19cdc1f59d168172e3 Mon Sep 17 00:00:00 2001 From: Evan Harvey Date: Wed, 11 Dec 2024 16:55:50 -0700 Subject: [PATCH 10/11] Fix cv2 imports --- opencsp/common/lib/render/PlotAnnotation.py | 2 +- opencsp/common/lib/render/image_plot.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/opencsp/common/lib/render/PlotAnnotation.py b/opencsp/common/lib/render/PlotAnnotation.py index c47294a0d..33912e44d 100644 --- a/opencsp/common/lib/render/PlotAnnotation.py +++ b/opencsp/common/lib/render/PlotAnnotation.py @@ -5,7 +5,7 @@ """ -from cv2 import cv2 as cv +import cv2 as cv import matplotlib.pyplot as plt import opencsp.common.lib.render_control.RenderControlPointSeq as rcps diff --git a/opencsp/common/lib/render/image_plot.py b/opencsp/common/lib/render/image_plot.py index 311acbdb1..2a4699f2c 100644 --- a/opencsp/common/lib/render/image_plot.py +++ b/opencsp/common/lib/render/image_plot.py @@ -5,7 +5,7 @@ """ -from cv2 import cv2 as cv +import cv2 as cv import matplotlib.pyplot as plt import os From 5ecfb13684f4261e52b1bd371f684e9e2b3b1422 Mon Sep 17 00:00:00 2001 From: Evan Harvey Date: Wed, 11 Dec 2024 17:06:20 -0700 Subject: [PATCH 11/11] Fix reference links --- opencsp/common/lib/cv/annotations/HotspotAnnotation.py | 2 +- opencsp/common/lib/cv/annotations/PointAnnotations.py | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/opencsp/common/lib/cv/annotations/HotspotAnnotation.py b/opencsp/common/lib/cv/annotations/HotspotAnnotation.py index e2a08a975..534ee732f 100644 --- a/opencsp/common/lib/cv/annotations/HotspotAnnotation.py +++ b/opencsp/common/lib/cv/annotations/HotspotAnnotation.py @@ -5,7 +5,7 @@ class HotspotAnnotation(PointAnnotations): """ - A class representing a hotspot annotation, likely created from a :py:class:`HotspotImageProcessor` instance. + A class representing a hotspot annotation, likely created from a :py:class:`opencsp.common.lib.cv.spot_analysis.image_processor.HotspotImageProcessor` instance. The hotspot is the overall hottest location in an image, when accounting for the surrounding area. It may be the different from the centroid location or the single hottest pixel location. diff --git a/opencsp/common/lib/cv/annotations/PointAnnotations.py b/opencsp/common/lib/cv/annotations/PointAnnotations.py index 2ad0ba7c8..52d2a9f66 100644 --- a/opencsp/common/lib/cv/annotations/PointAnnotations.py +++ b/opencsp/common/lib/cv/annotations/PointAnnotations.py @@ -6,16 +6,11 @@ class PointAnnotations(pf.PointFiducials, AbstractAnnotations): """ A class representing point annotations. - An example of this class is :py:class:`HotspotAnnotation`. + An example of this class is :py:class:`opencsp.common.lib.cv.annotations.HotspotAnnotation.HotspotAnnotation`. This class extends both `PointFiducials` and `AbstractAnnotations` to provide functionality for managing and rendering point annotations, which can be used to mark specific locations in a visual representation. - - Inherits from: - --------------- - pf.PointFiducials : Implements methods from AbstractFiducial for point fiducials, and provides related attributes. - AbstractAnnotations : Provides an abstract base for annotation classes. """ # "ChatGPT 4o" assisted with generating this docstring.