Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
2381503
add PerspectiveTransform.pixels_to_meters_transforms() and meters_to_…
bbean23 Nov 18, 2025
a5ac65a
fix circular import issue
bbean23 Nov 18, 2025
51b3ca5
add list-of-pairs option to Vxy.from_list() and Vxyz.from_list()
bbean23 Nov 18, 2025
bfc1f29
black
bbean23 Nov 18, 2025
a239390
fix Vxyz.from_list() and the from_list tests for Vxy and Vxyz
bbean23 Nov 19, 2025
0d10b14
add coordinate_transforms to SpotAnalysisOperable, apply coordinate t…
bbean23 Nov 19, 2025
27e5a36
rename transforms -> conversions
bbean23 Nov 25, 2025
cacfa13
better unit tests for TestPerspectiveTransform
bbean23 Nov 25, 2025
b0a6e89
unset conversions when data changes
bbean23 Nov 25, 2025
9101dd9
finish renaming transforms -> conversions
bbean23 Nov 25, 2025
97d919c
formatting
bbean23 Nov 25, 2025
a1e005f
add transformed_pixels_to_meters_conversions() and transformed_pixels…
bbean23 Nov 25, 2025
a4b5fef
use new function image_files_in_directory() instead of files_in_direc…
bbean23 Nov 25, 2025
b74e93c
fix TestTargetBoardLocatorImageProcessor, commit images for tests
bbean23 Nov 25, 2025
0d9d091
TargetBoardLocatorImageProcessor sets x_coordinates_transform and y_c…
bbean23 Nov 25, 2025
d7f17aa
fix single plot for ViewCrossSectionImageProcessor
bbean23 Nov 26, 2025
4126441
use transformed coordinates in ViewCrossSectionImageProcessor, add un…
bbean23 Nov 26, 2025
9d1c29a
use transformed coordinates in View3dImageProcessor, add unit tests f…
bbean23 Nov 26, 2025
8124947
fix numbering_outline_color in ImageGrid
bbean23 Nov 26, 2025
d7f29f3
update ViewAnnotationsImageProcessor and ViewHighlightImageProcessor …
bbean23 Nov 26, 2025
d053dda
Merge branch 'develop' into 309-code-bug-incorrect-units-on-enclosed-…
jehsharp Dec 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
import os

from numpy._typing._array_like import NDArray
import sympy

from opencsp.common.lib.cv.CacheableImage import CacheableImage
import contrib.common.lib.cv.PerspectiveTransform as pt
import opencsp.common.lib.cv.PerspectiveTransform as pt
import contrib.common.lib.cv.RegionDetector as rd
from opencsp.common.lib.cv.spot_analysis.SpotAnalysisOperable import SpotAnalysisOperable
from opencsp.common.lib.cv.spot_analysis.image_processor.AbstractSpotAnalysisImageProcessor import (
Expand All @@ -19,6 +20,7 @@
import opencsp.common.lib.opencsp_path.opencsp_root_path as orp
import opencsp.common.lib.render.Color as color
import opencsp.common.lib.tool.file_tools as ft
import opencsp.common.lib.tool.image_tools as it
import opencsp.common.lib.tool.log_tools as lt


Expand Down Expand Up @@ -257,7 +259,9 @@ def _find_rectangle_in_reference_image(self):

# Compile a list of all reference images
if os.path.isdir(self.reference_image_dir_or_file):
image_filenames = ft.files_in_directory(self.reference_image_dir_or_file, files_only=True)
image_filenames = it.image_files_in_directory(
self.reference_image_dir_or_file, it.pil_image_formats_readable
)
image_files: list[str] = []
for filename in image_filenames:
file_path_name_ext = os.path.join(self.reference_image_dir_or_file, filename)
Expand Down Expand Up @@ -360,6 +364,13 @@ def _execute(self, operable: SpotAnalysisOperable, is_last: bool) -> list[SpotAn
# target board images
my_visualization_images = [annotated_cacheable]

# apply the changes to the image coordinates
x, y = sympy.symbols('x y')
x_coordinates_transform, y_coordinates_transform = self.transform.transformed_pixels_to_meters_conversions()
if operable.x_coordinates_transform is not None:
x_coordinates_transform = x_coordinates_transform.subs({x: operable.x_coordinates_transform})
y_coordinates_transform = y_coordinates_transform.subs({y: operable.y_coordinates_transform})

visualization_images = copy.copy(operable.visualization_images)
visualization_images[self] = my_visualization_images
algorithm_images = copy.copy(operable.algorithm_images)
Expand All @@ -369,5 +380,7 @@ def _execute(self, operable: SpotAnalysisOperable, is_last: bool) -> list[SpotAn
primary_image=isolated_cacheable,
visualization_images=visualization_images,
algorithm_images=algorithm_images,
x_coordinates_transform=x_coordinates_transform,
y_coordinates_transform=y_coordinates_transform,
)
return [ret]
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,12 @@ def visualize_operable(

# initialize the figure
self.figure.clear()
self.figure.view.imshow(image)
tc = lambda x, y: operable.transform_coordinates(p2.Pxy((x, y)))[1].astuple()
(height, width), _ = it.dims_and_nchannels(image)
img_xy, img_xy2 = tc(0, 0), tc(width, height)
self.figure.view.draw_image(
base_image.nparray, img_xy, (img_xy2[0] - img_xy[0], img_xy2[1] - img_xy[1]), invert_ylim=True
)

# render
include_label = len(to_render) > 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,12 @@ def visualize_operable(

# show the visualization
self.figure.clear()
self.figure.view.imshow(new_image)
tc = lambda x, y: operable.transform_coordinates(p2.Pxy((x, y)))[1].astuple()
(height, width), _ = it.dims_and_nchannels(new_image)
img_xy, img_xy2 = tc(0, 0), tc(width, height)
self.figure.view.draw_image(
base_image.nparray, img_xy, (img_xy2[0] - img_xy[0], img_xy2[1] - img_xy[1]), invert_ylim=True
)
self.figure.view.show(block=False)

# build the return value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from PIL import Image

from contrib.common.lib.cv.spot_analysis.image_processor import TargetBoardLocatorImageProcessor
from opencsp.common.lib.cv.CacheableImage import CacheableImage
from opencsp.common.lib.cv.spot_analysis.SpotAnalysisOperable import SpotAnalysisOperable
import opencsp.common.lib.geometry.Pxy as p2
import opencsp.common.lib.tool.file_tools as ft
import opencsp.common.lib.tool.image_tools as it
Expand All @@ -30,71 +32,81 @@ def test_target_board_location(self):
cropped_x1x2y1y2=None,
target_width_meters=2.44,
target_height_meters=2.44,
canny_edges_gradient=30,
canny_non_edges_gradient=20,
canny_edges_gradient=10,
canny_non_edges_gradient=15,
# canny_test_gradients=[(5,5),(5,10),(5,15),(5,20),(10,5),(10,10),(10,15),(10,20)],
# debug_target_locating=True
)
lighted = Image.open(ft.join(self.data_dir, "09W01.JPG"))
lighted = Image.open(ft.join(self.data_dir, "09W01.png"))
result = processor.process_images([lighted])[0]
result.to_image().save(ft.join(self.out_dir, self._testMethodName + ".png"))
result.save(ft.join(self.out_dir, self._testMethodName + ".png"))

corners = processor.corners
self.assertAlmostEqual(corners["tl"].x[0], 517, delta=20)
self.assertAlmostEqual(corners["tl"].y[0], 257, delta=20)
self.assertAlmostEqual(corners["tr"].x[0], 1107, delta=20)
self.assertAlmostEqual(corners["tr"].y[0], 268, delta=20)
self.assertAlmostEqual(corners["br"].x[0], 1094, delta=20)
self.assertAlmostEqual(corners["br"].y[0], 855, delta=20)
self.assertAlmostEqual(corners["bl"].x[0], 503, delta=20)
self.assertAlmostEqual(corners["bl"].y[0], 842, delta=20)

def test_target_board_location_cropped(self):
# crop the input image
crop_size = random.randint(1, 200)
crop = [crop_size, 1626 + 1 - crop_size, crop_size, 1236 + 1 - crop_size]
lighted = Image.open(ft.join(self.data_dir, "09W01.JPG"))
lighted_cropped = lighted.crop([crop[0], crop[2], crop[1], crop[3]])

# evaluate
processor = TargetBoardLocatorImageProcessor(
reference_image_dir_or_file=ft.join(self.data_dir, "reference_target_board"),
cropped_x1x2y1y2=crop,
target_width_meters=2.44,
target_height_meters=2.44,
canny_edges_gradient=30,
canny_non_edges_gradient=20,
# debug_target_locating=True
)
result = processor.process_images([lighted_cropped])[0]
result.to_image().save(ft.join(self.out_dir, self._testMethodName + ".png"))

# verify
corners = processor.corners
self.assertAlmostEqual(corners["tl"].x[0], 517 - crop_size, delta=20, msg=f"failed for {crop_size=}")
self.assertAlmostEqual(corners["tl"].y[0], 257 - crop_size, delta=20, msg=f"failed for {crop_size=}")
self.assertAlmostEqual(corners["tr"].x[0], 1107 - crop_size, delta=20, msg=f"failed for {crop_size=}")
self.assertAlmostEqual(corners["tr"].y[0], 268 - crop_size, delta=20, msg=f"failed for {crop_size=}")
self.assertAlmostEqual(corners["br"].x[0], 1094 - crop_size, delta=20, msg=f"failed for {crop_size=}")
self.assertAlmostEqual(corners["br"].y[0], 855 - crop_size, delta=20, msg=f"failed for {crop_size=}")
self.assertAlmostEqual(corners["bl"].x[0], 503 - crop_size, delta=20, msg=f"failed for {crop_size=}")
self.assertAlmostEqual(corners["bl"].y[0], 842 - crop_size, delta=20, msg=f"failed for {crop_size=}")
# fmt: off
self.assertAlmostEqual(corners["tl"].x[0], 35.7295165, delta=2) # max delta for (max - min) in 100 runs: 0.31794104
self.assertAlmostEqual(corners["tl"].y[0], 29.26274199, delta=2) # max delta for (max - min) in 100 runs: 0.07478309
self.assertAlmostEqual(corners["tr"].x[0], 625.25789369, delta=2) # max delta for (max - min) in 100 runs: 0.31775339
self.assertAlmostEqual(corners["tr"].y[0], 45.87442367, delta=2) # max delta for (max - min) in 100 runs: 0.0335551
self.assertAlmostEqual(corners["br"].x[0], 605.83119206, delta=2) # max delta for (max - min) in 100 runs: 0.30441256
self.assertAlmostEqual(corners["br"].y[0], 633.02880323, delta=2) # max delta for (max - min) in 100 runs: 0.01701056
self.assertAlmostEqual(corners["bl"].x[0], 16.30514864, delta=2) # max delta for (max - min) in 100 runs: 0.49065163
self.assertAlmostEqual(corners["bl"].y[0], 616.74420236, delta=2) # max delta for (max - min) in 100 runs: 0.05482708
# fmt: on

def test_perspective_transform(self):
corners = {
"tl": p2.Pxy([519.42333545, 256.22223199]),
"tr": p2.Pxy([1108.33624737, 271.21012117]),
"br": p2.Pxy([1091.97009466, 857.37629342]),
"bl": p2.Pxy([501.9556769, 840.95732619]),
"tl": p2.Pxy([35.7295165, 29.26274199]),
"tr": p2.Pxy([625.25789369, 45.87442367]),
"br": p2.Pxy([605.83119206, 633.02880323]),
"bl": p2.Pxy([16.30514864, 616.74420236]),
}
processor = TargetBoardLocatorImageProcessor.from_corners(
corners, target_width_meters=2.44, target_height_meters=2.44
)
lighted = Image.open(ft.join(self.data_dir, "09W01.JPG"))
lighted = Image.open(ft.join(self.data_dir, "09W01.png"))
result = processor.process_images([lighted])[0]
result.to_image().save(ft.join(self.out_dir, self._testMethodName + ".png"))
result.save(ft.join(self.out_dir, self._testMethodName + ".png"))

expected = Image.open(ft.join(self.data_dir, "09W01_transformed.png"))
npt.assert_allclose(result.nparray, np.array(expected), atol=2)
npt.assert_array_equal(np.array(result), np.array(expected))

def test_coordinate_transform(self):
corners = {
"tl": p2.Pxy([35.7295165, 29.26274199]),
"tr": p2.Pxy([625.25789369, 45.87442367]),
"br": p2.Pxy([605.83119206, 633.02880323]),
"bl": p2.Pxy([16.30514864, 616.74420236]),
}
processor = TargetBoardLocatorImageProcessor.from_corners(
corners, target_width_meters=2.44, target_height_meters=2.44
)
lighted = CacheableImage.from_single_source(Image.open(ft.join(self.data_dir, "09W01.png")))
operable = SpotAnalysisOperable(lighted, "lighted")
dewarped_operable = processor.process_operable(operable, is_last=True)[0]
dewarped_image = dewarped_operable.primary_image.nparray
(h, w), _ = it.dims_and_nchannels(dewarped_image)

# Check that we get the expected values from the transforms.
# First, sanity check.
tx = operable.transform_coordinates
self.assertEqual(tx(p2.Pxy([0, 0]))[0], False)
self.assertEqual(tx(p2.Pxy([1108, 0]))[0], False)
self.assertEqual(tx(p2.Pxy([1108, 857]))[0], False)
self.assertEqual(tx(p2.Pxy([0, 857]))[0], False)
npt.assert_array_almost_equal(tx(p2.Pxy([0, 0]))[1]._data, p2.Pxy([0, 0])._data)
npt.assert_array_almost_equal(tx(p2.Pxy([1108, 0]))[1]._data, p2.Pxy([1108, 0])._data)
npt.assert_array_almost_equal(tx(p2.Pxy([1108, 857]))[1]._data, p2.Pxy([1108, 857])._data)
npt.assert_array_almost_equal(tx(p2.Pxy([0, 857]))[1]._data, p2.Pxy([0, 857])._data)
# Now check that the cropped transform is correct.
tcx = dewarped_operable.transform_coordinates
self.assertEqual(tcx(p2.Pxy([0, 0]))[0], True)
self.assertEqual(tcx(p2.Pxy([w, 0]))[0], True)
self.assertEqual(tcx(p2.Pxy([w, h]))[0], True)
self.assertEqual(tcx(p2.Pxy([0, h]))[0], True)
npt.assert_array_almost_equal(tcx(p2.Pxy([0, 0]))[1]._data, p2.Pxy([0, 0])._data)
npt.assert_array_almost_equal(tcx(p2.Pxy([w, 0]))[1]._data, p2.Pxy([2.44, 0])._data)
npt.assert_array_almost_equal(tcx(p2.Pxy([w, h]))[1]._data, p2.Pxy([2.44, 2.44])._data)
npt.assert_array_almost_equal(tcx(p2.Pxy([0, h]))[1]._data, p2.Pxy([0, 2.44])._data)


if __name__ == "__main__":
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 7 additions & 4 deletions contrib/experiments/ExExLookback/DirectSun/cross_section.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,12 @@
}
],
"source": [
"print(\"opencsp_settings['opencsp_root_path']['large_data_example_dir'] =\", opencsp_settings['opencsp_root_path']['large_data_example_dir'])\n",
"print(\n",
" \"opencsp_settings['opencsp_root_path']['large_data_example_dir'] =\",\n",
" opencsp_settings['opencsp_root_path']['large_data_example_dir'],\n",
")\n",
"experiment_dir = os.path.join(\n",
" opencsp_settings['opencsp_root_path']['large_data_example_dir'], \"1xSunFilter_ManualFocus_VarExp\",\n",
" opencsp_settings['opencsp_root_path']['large_data_example_dir'], \"1xSunFilter_ManualFocus_VarExp\"\n",
")\n",
"input_dir = experiment_dir\n",
"intermediary_dir = os.path.join(experiment_dir, \"intermediary\")\n",
Expand Down Expand Up @@ -341,8 +344,8 @@
" \"GetOrigl\": CustomSimpleImageProcessor(\n",
" lambda o: get_primary_prev_processor(o, cropping_image_processors[\"Original\"])\n",
" ),\n",
"# TODO RCB FIX THE BELOW\n",
"# \"CropCent\": CroppingImageProcessor.by_center_and_size(centroid_pixel_locator, width=1400, height=1400),\n",
" # TODO RCB FIX THE BELOW\n",
" # \"CropCent\": CroppingImageProcessor.by_center_and_size(centroid_pixel_locator, width=1400, height=1400),\n",
" \"CropCent\": CroppingImageProcessor.by_center_and_size(centroid_pixel_locator, width_height=(1400, 1400)),\n",
"}\n",
"cropping_image_processors_list = list(cropping_image_processors.values())\n",
Expand Down
Loading