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/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..0307cc5c3 --- /dev/null +++ b/doc/source/library_reference/common/lib/cv/config.rst @@ -0,0 +1,101 @@ +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 +================= + +image_filters +------------- + +.. 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 + +image_reshapers +--------------- + +.. 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 + +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 + +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 \ No newline at end of file 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/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 diff --git a/opencsp/common/lib/cv/OpticalFlow.py b/opencsp/common/lib/cv/OpticalFlow.py index f6d2beecd..45561622c 100644 --- a/opencsp/common/lib/cv/OpticalFlow.py +++ b/opencsp/common/lib/cv/OpticalFlow.py @@ -20,6 +20,14 @@ 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. + """ + + # "ChatGPT 4o-mini" assisted with generating this docstring. _max_cache_count = 10 def __init__( @@ -42,35 +50,66 @@ def __init__( dense_flags=0, cache=False, ): - """Wrapper class around cv::calcOpticalFlowFarneback (and also cv::calcOpticalFlowPyrLK, eventually). + """ + 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" + 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) + 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 - 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: + Parameters + ---------- + 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. + grayscale_normalization : Callable[[np.ndarray], np.ndarray], optional + A function for normalizing grayscale images (default is None). + 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. + (default is 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. (default is 1). + dense_winsize : int, optional + 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 + 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. + (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 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). + - 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 + (aka on solo). (default is False) """ + # "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 +143,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 +265,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 + ------- + 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 + ----- + 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 +318,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 +355,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 +404,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 +464,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 +587,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 +608,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 from limit_by_magnitude or limit_by_angle. - 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 + ------- + saved_path_name_ext: 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 +673,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 generated from the :py:meth:`save` method. + + 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 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}") 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/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/annotations/HotspotAnnotation.py b/opencsp/common/lib/cv/annotations/HotspotAnnotation.py index f3c789475..534ee732f 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, 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. + + 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): + """ + 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 + -------- + >>> 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: 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..52d2a9f66 100644 --- a/opencsp/common/lib/cv/annotations/PointAnnotations.py +++ b/opencsp/common/lib/cv/annotations/PointAnnotations.py @@ -3,4 +3,16 @@ class PointAnnotations(pf.PointFiducials, AbstractAnnotations): + """ + A class representing point annotations. + + 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. + """ + + # "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..dee819cf4 100644 --- a/opencsp/common/lib/cv/fiducials/AbstractFiducials.py +++ b/opencsp/common/lib/cv/fiducials/AbstractFiducials.py @@ -18,77 +18,132 @@ 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. + :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): """ + 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 of this instance, in pixels. + + Parameters + ---------- + index : int, optional + 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 + ------- + 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. + 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). + camera) + + Returns + ------- + scipy.spatial.transform.Rotation + The rotation of the fiducial relative to the camera's reference frame. + + 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: - 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:: + .. code-block:: python 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: + + .. code-block:: python Rotation.from_euler( 'yz', - [ [np.pi, 0], - [0, np.pi] ] + [[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 + """ + 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.""" # TODO is this a good definition? + corner to the other. + + Returns + ------- + list[float] + The sizes of the fiducial in pixels. + """ + + # "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. + + 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. """ + # "ChatGPT 4o" assisted with generating this docstring. ret = [] for i in range(len(self.origin)): @@ -102,9 +157,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 +182,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..336ac4dd8 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 + Ignored for BcsFiducials. + + 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 + 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 a single value: the diameter of the BCS target. + """ + # "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 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 plot's axes. + # + # Parameters + # ---------- + # axes : matplotlib.axes.Axes + # The plot's 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..27b237945 100644 --- a/opencsp/common/lib/cv/fiducials/PointFiducials.py +++ b/opencsp/common/lib/cv/fiducials/PointFiducials.py @@ -7,31 +7,105 @@ 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 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 this class. + """ + # "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. The default implementation for PointFiducials 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. The default implementation for PointFiducials 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..165a30e23 100644 --- a/opencsp/common/lib/cv/spot_analysis/ImagesIterable.py +++ b/opencsp/common/lib/cv/spot_analysis/ImagesIterable.py @@ -75,12 +75,20 @@ class ImagesIterable(Iterable[CacheableImage]): 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. If a callable, then will be passed + the current iteration index as an argument. + + Raises + ------ + TypeError + If the provided stream is not one of the supported types. """ + # "ChatGPT 4o-mini" assisted with generating this docstring. if isinstance(stream, _IndexableIterable): self._images_iterable = stream elif isinstance(stream, vh.VideoHandler): @@ -113,4 +121,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/SpotAnalysisOperablesStream.py b/opencsp/common/lib/cv/spot_analysis/SpotAnalysisOperablesStream.py index 016425ec7..433d58a4d 100644 --- a/opencsp/common/lib/cv/spot_analysis/SpotAnalysisOperablesStream.py +++ b/opencsp/common/lib/cv/spot_analysis/SpotAnalysisOperablesStream.py @@ -25,13 +25,17 @@ 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. 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 self.images_iter = None self.default_support_images: dict[ImageType, CacheableImage] = None @@ -48,14 +52,15 @@ def set_defaults(self, default_support_images: dict[ImageType, CacheableImage], 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 as defaults + in the generated operables. Can be empty. 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. """ + # "ChatGPT 4o-mini" assisted with generating this docstring. self.default_support_images = default_support_images self.default_data = default_data 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 diff --git a/opencsp/test/test_DocStringsExist.py b/opencsp/test/test_DocStringsExist.py index 2f7e37ade..46be5b6a6 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] = []