Skip to content

Commit

Permalink
Merge pull request #103 from bbean23/102-cross-section-visualization-…
Browse files Browse the repository at this point in the history
…of-a-spot

102 cross section visualization of a spot
  • Loading branch information
e10harvey authored Jun 26, 2024
2 parents fb5780f + 1ff88e3 commit 8c65300
Show file tree
Hide file tree
Showing 21 changed files with 1,066 additions and 146 deletions.
56 changes: 55 additions & 1 deletion contrib/app/SpotAnalysis/PeakFlux.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import os
import re

import numpy as np

import opencsp.common.lib.cv.SpotAnalysis as sa
from opencsp.common.lib.cv.annotations.HotspotAnnotation import HotspotAnnotation
from opencsp.common.lib.cv.fiducials.BcsFiducial import BcsFiducial
from opencsp.common.lib.cv.spot_analysis.SpotAnalysisImagesStream import ImageType
from opencsp.common.lib.cv.spot_analysis.SpotAnalysisOperable import SpotAnalysisOperable
import opencsp.common.lib.cv.spot_analysis.SpotAnalysisOperableAttributeParser as saoap
from opencsp.common.lib.cv.spot_analysis.image_processor import *
import opencsp.common.lib.tool.file_tools as ft
Expand Down Expand Up @@ -56,8 +61,14 @@ def __init__(self, indir: str, outdir: str, experiment_name: str, settings_path_
NullImageSubtractionImageProcessor(),
ConvolutionImageProcessor(kernel="box", diameter=3),
BcsLocatorImageProcessor(),
# View3dImageProcessor(crop_to_threshold=20, max_resolution=(100, 100), interactive=True),
View3dImageProcessor(crop_to_threshold=20, max_resolution=(100, 100), interactive=False),
HotspotImageProcessor(desired_shape=21, draw_debug_view=False),
ViewCrossSectionImageProcessor(
self.get_bcs_origin, 'BCS', single_plot=False, crop_to_threshold=20, interactive=True
),
ViewCrossSectionImageProcessor(
self.get_peak_origin, 'Hotspot', single_plot=False, crop_to_threshold=20, interactive=True
),
PopulationStatisticsImageProcessor(initial_min=0, initial_max=255),
FalseColorImageProcessor(),
AnnotationImageProcessor(),
Expand Down Expand Up @@ -89,6 +100,49 @@ def run(self):
# condensed csv file.
parser = saoap.SpotAnalysisOperableAttributeParser(result, self.spot_analysis)

def get_bcs_origin(self, operable: SpotAnalysisOperable) -> tuple[int, int]:
"""
Returns the origin pixel location of the BCS fiducial assigned to this operable.
Parameters
----------
operable : SpotAnalysisOperable
An operable that has resulted from a BcsLocatorImageProcessor and
has an assigned BCS fiducial.
Returns
-------
bcs_origin: tuple[int, int]
The origin of the BCS fiducial. For circular BCS systems, this will
be the center point of the circle.
"""
fiducials = operable.get_fiducials_by_type(BcsFiducial)
fiducial = fiducials[0]
origin_fx, origin_fy = fiducial.origin.astuple()
origin_ix, origin_iy = int(np.round(origin_fx)), int(np.round(origin_fy))
return origin_ix, origin_iy

def get_peak_origin(self, operable: SpotAnalysisOperable) -> tuple[int, int]:
"""
Get the peak pixel location of the hotspot for the given operable.
Parameters
----------
operable : SpotAnalysisOperable
An operable that has resulted from a HotspotImageProcessor and has
an assigned hotspot annotation.
Returns
-------
peak_origin: tuple[int, int]
The origin of the hotspot annotation.
"""
fiducials = operable.get_fiducials_by_type(HotspotAnnotation)
fiducial = fiducials[0]
origin_fx, origin_fy = fiducial.origin.astuple()
origin_ix, origin_iy = int(np.round(origin_fx)), int(np.round(origin_fy))
return origin_ix, origin_iy


if __name__ == "__main__":
import argparse
Expand Down
7 changes: 7 additions & 0 deletions opencsp/common/lib/cv/SpotAnalysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from typing import Iterator

from opencsp.common.lib.cv.CacheableImage import CacheableImage
from opencsp.common.lib.cv.spot_analysis.VisualizationCoordinator import VisualizationCoordinator
import opencsp.common.lib.cv.spot_analysis.image_processor.AbstractSpotAnalysisImageProcessor as asaip

# from opencsp.common.lib.cv.spot_analysis.image_processor import * # I suggest importing these dynamically as needed, to reduce startup time
Expand Down Expand Up @@ -170,6 +171,8 @@ def __init__(
""" Other supporting data for processing input images. If not None, then
all values here will be made available for processing as the default
values. """
self.visualization_coordinator = VisualizationCoordinator()
""" Shows the same image from all visualization processors at the same time. """

self.set_image_processors(image_processors)

Expand All @@ -182,6 +185,10 @@ def set_image_processors(self, image_processors: list[asaip.AbstractSpotAnalysis
continue
image_processor.assign_inputs(self.image_processors[i - 1])

# register the visualization processors
self.visualization_coordinator.clear()
self.visualization_coordinator.register_visualization_processors(image_processors)

# limit the amount of memory that image processors utilize
from opencsp.common.lib.cv.spot_analysis.image_processor.AbstractSpotAnalysisImageProcessorLeger import (
image_processors_persistant_memory_total,
Expand Down
64 changes: 57 additions & 7 deletions opencsp/common/lib/cv/spot_analysis/SpotAnalysisOperable.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from dataclasses import dataclass, field, replace
import numpy as np
import numpy.typing as npt
import os
import sys

import opencsp.common.lib.csp.LightSource as ls
Expand All @@ -11,6 +12,7 @@
from opencsp.common.lib.cv.spot_analysis.SpotAnalysisImagesStream import ImageType
from opencsp.common.lib.cv.spot_analysis.SpotAnalysisPopulationStatistics import SpotAnalysisPopulationStatistics
import opencsp.common.lib.tool.file_tools as ft
import opencsp.common.lib.tool.log_tools as lt


@dataclass(frozen=True)
Expand Down Expand Up @@ -154,17 +156,47 @@ def replace_use_default_values(

return ret

@property
def primary_image_name_for_logs(self):
image_name = self.primary_image.source_path
def get_primary_path_nameext(self) -> tuple[str, str]:
"""
Finds the best source path/name.ext for the primary image of this operable.
The source path is chosen from among the primary_image's .source_path,
the primary_image_source_path, and the primary_image's .cache_path.
Returns
-------
source_path: str
The path component of the source of the primary_image.
"unknown_path" if there isn't an associated source.
source_name_ext: str
The name.ext component of the source of the primary_image.
"unknown_image" if there isn't an associated source.
"""
for image_name in [
self.primary_image.source_path,
self.primary_image_source_path,
self.primary_image.cache_path,
]:
if image_name is not None and image_name != "":
break

if image_name == None or image_name == "":
image_name = "unknown image"
ret_path, ret_name_ext = "unknown_path", "unknown_image"
else:
path, name, ext = ft.path_components(image_name)
image_name = name + ext
ret_path, name, ext = ft.path_components(image_name)
ret_name_ext = name + ext

return ret_path, ret_name_ext

@property
def best_primary_nameext(self) -> str:
"""The name.ext of the source of the primary_image."""
return self.get_primary_path_nameext()[1]

return image_name
@property
def best_primary_pathnameext(self) -> str:
"""The path/name.ext of the source of the primary_image."""
return os.path.join(*self.get_primary_path_nameext())

@property
def max_popf(self) -> npt.NDArray[np.float_]:
Expand All @@ -183,3 +215,21 @@ def min_popf(self) -> npt.NDArray[np.float_]:
return self.population_statistics.minf
else:
return np.min(self.primary_image.nparray)

def get_fiducials_by_type(
self, fiducial_type: type[af.AbstractFiducials]
) -> list[aa.AbstractAnnotations | af.AbstractFiducials]:
"""
Returns all fiducials from self.given_fiducials, self.found_fiducials,
and self.annotations that match the given type.
"""
matching_given_fiducials = filter(lambda f: isinstance(f, fiducial_type), self.given_fiducials)
matching_found_fiducials = filter(lambda f: isinstance(f, fiducial_type), self.found_fiducials)
matching_annotations = filter(lambda f: isinstance(f, fiducial_type), self.annotations)
ret = list(matching_given_fiducials) + list(matching_found_fiducials) + list(matching_annotations)
if len(ret) == 0:
lt.debug(
"In SpotAnalysisOperable.get_fiducials_by_type(): "
+ f"found 0 fiducials matching type {fiducial_type.__name__} for image {self.best_primary_pathnameext}"
)
return ret
Loading

0 comments on commit 8c65300

Please sign in to comment.