Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,9 @@ def visualize_operable(
for fiducials in to_render:
fiducials.render_to_figure(self.figure, image, include_label)

# show the visualization
self.figure.view.show(block=False, legend=include_label)
# show the legend
if include_label:
self.figure.figure.legend()

return [self.figure]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@ def visualize_operable(
# show the visualization
self.prepare_figure_records([self.figure], [new_image.shape])
self.figure.view.imshow(new_image)
self.figure.view.show(block=False)

# build the return value
cacheable_image = CacheableImage(new_image)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import opencsp.common.lib.render_control.RenderControlFigure as rcf
import opencsp.common.lib.render_control.RenderControlFigureRecord as rcfr
import opencsp.common.lib.tool.log_tools as lt
import opencsp.common.lib.tool.system_tools as st


class VisualizationCoordinator:
Expand Down Expand Up @@ -304,6 +305,9 @@ def visualize(
processor_visualizations, _visualization_image_no_axes = visualization_processor._visualize_operable(
operable, is_last
)
if not st.is_notebook():
for fig_record in visualization_processor._initialized_figure_records:
fig_record.view.show(block=False)

# compile all visualizations together into a single operable to be returned
if len(processor_visualizations) > 0:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import opencsp.common.lib.render_control.RenderControlFigureRecord as rcfr
import opencsp.common.lib.tool.image_tools as it
import opencsp.common.lib.tool.log_tools as lt
import opencsp.common.lib.tool.system_tools as st


class AbstractVisualizationImageProcessor(AbstractSpotAnalysisImageProcessor, ABC):
Expand Down Expand Up @@ -156,6 +157,8 @@ def __init__(
visualization image processor that has its base_image_selector set to
'visualization', and then the value is unset.
"""
self._initialized_figure_records: weakref.WeakSet[rcfr.RenderControlFigureRecord] = weakref.WeakSet()
""" The figure records returned from init_figure_records(). """

@property
@abstractmethod
Expand Down Expand Up @@ -299,6 +302,9 @@ def _init_figure_records(self, render_control_fig: rcf.RenderControlFigure) -> l
"""
self._render_control_fig = weakref.ref(render_control_fig)
ret = self.init_figure_records(render_control_fig)
self._initialized_figure_records.clear()
for fig_record in ret:
self._initialized_figure_records.add(fig_record)
self.initialized_figure_records = True
return ret

Expand Down Expand Up @@ -390,6 +396,11 @@ def _execute(self, operable: SpotAnalysisOperable, is_last: bool) -> list[SpotAn
self._init_figure_records(render_control)
new_visualizations, _visualization_image_no_axes = self._visualize_operable(operable, is_last)

# draw the visualizations
if not st.is_notebook():
for fig_record in self._initialized_figure_records:
fig_record.view.show(block=False)

# get the visualization images list
visualization_images = copy.copy(operable.visualization_images)
if self not in visualization_images:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,6 @@ def visualize_operable(
x_mesh, y_mesh = np.meshgrid(x_arr, y_arr)
self.view.draw_xyz_surface_customshape(x_mesh, y_mesh, image, self.rcs)

# draw
self.view.show(block=False)

return [self.fig_record]

def close_figures(self):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -332,30 +332,20 @@ def visualize_operable(

# Draw the image w/ cross section line overlays
i_view = self.views[0]
tc = lambda x, y: operable.transform_coordinates(p2.Pxy((x, y)))[1].astuple()
img_xy, img_xy2 = tc(0, 0), tc(cropped_width, cropped_height)
i_view.draw_image(
base_image.nparray, img_xy, (img_xy2[0] - img_xy[0], img_xy2[1] - img_xy[1]), invert_ylim=True
)
i_view.draw_pq_list([tc(cs_cropped_x, 0), tc(cs_cropped_x, cropped_height)], style=vstyle)
i_view.draw_pq_list([tc(0, cs_cropped_y), tc(cropped_width, cs_cropped_y)], style=hstyle)
i_view.draw_image(base_image, (0, 0), (cropped_width, cropped_height))
i_view.draw_pq_list([(cs_cropped_x, 0), (cs_cropped_x, cropped_height)], style=vstyle)
i_view.draw_pq_list([(0, cs_cropped_y_mlab), (cropped_width, cs_cropped_y_mlab)], style=hstyle)

# Draw the cross sections for the no-sun image.
# Draw the cross sections for the primary image using the same axes.
graphs_per_plot_cnt = 0
graphs_per_plot_cnt += self._draw_null_image_cross_section(operable, cs_loc_cropped, cropped_region)
graphs_per_plot_cnt += self._draw_cross_section(operable, np_image, cs_loc, cropped_region)

# draw
first_view = True
for view in self.views:
legend = graphs_per_plot_cnt > 1

# jhs modified, only the first view has actual data to display in the enclosed_energy jupyter notebook
# example, so I am only showing that one here. Also, this is true for the target_identification example
if first_view:
view.show(block=False, legend=legend)
first_view = False
# add the legend
if graphs_per_plot_cnt > 1:
for fig_record in self._figure_records:
fig_record.figure.legend()

# explicitly set the y-axis range
if self.y_range is not None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,9 @@ def visualize_operable(
else:
ret = [self.apply_mapping_jet(operable, base_image)]

processed_image = ret[0].nparray

self.figure.clear()
self.figure.view.imshow(processed_image)
self.figure.view.show(block=False)
# draw the image
self.prepare_figure_records([self.figure], [ret[0].nparray.shape])
self.figure.view.imshow(ret[0])

return ret

Expand Down
15 changes: 10 additions & 5 deletions opencsp/common/lib/render/View3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import scipy.ndimage
from typing import TYPE_CHECKING

from opencsp.common.lib.cv.CacheableImage import CacheableImage
import opencsp.common.lib.render.axis_3d as ax3d
import opencsp.common.lib.render.view_spec as vs
import opencsp.common.lib.render_control.RenderControlHeatmap as rcheat
Expand Down Expand Up @@ -486,6 +487,8 @@ def imshow(self, *args, colorbar=False, **kwargs) -> None:
img = args[0]
args = list(args)
args[0] = load_as_necessary(img)
if isinstance(args[0], CacheableImage):
args[0] = args[0].nparray

im = self.axis.imshow(*args, interpolation="none", **kwargs)
if self.equal:
Expand All @@ -497,7 +500,7 @@ def imshow(self, *args, colorbar=False, **kwargs) -> None:

def draw_image(
self,
path_or_array: str | np.ndarray,
path_or_array_or_cacheable: str | np.ndarray | CacheableImage,
xy_location: tuple[float, float] = None,
width_height: tuple[float, float] = None,
cmap: str | matplotlib.colors.Colormap = None,
Expand All @@ -513,7 +516,7 @@ def draw_image(

Parameters
----------
path_or_array : str | np.ndarray
path_or_array_or_cacheable : str | np.ndarray | CacheableImage
The image to be drawn.
xy_location : tuple[float, float], optional
The location at which to draw the image, in graph coordinate units.
Expand All @@ -533,10 +536,12 @@ def draw_image(
large to small order instead of small to large order. This
effectively puts the origin for the graph at the top.
"""
if isinstance(path_or_array, str):
img = mpimg.imread(path_or_array)
if isinstance(path_or_array_or_cacheable, str):
img = mpimg.imread(path_or_array_or_cacheable)
elif isinstance(path_or_array_or_cacheable, CacheableImage):
img = path_or_array_or_cacheable.nparray
else:
img: np.ndarray = path_or_array
img: np.ndarray = path_or_array_or_cacheable
imgw, imgh = img.shape[1], img.shape[0]
xbnd, ybnd = self.axis.get_xbound(), self.axis.get_ybound()
xdraw, ydraw = xbnd, ybnd
Expand Down
22 changes: 22 additions & 0 deletions opencsp/common/lib/tool/system_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,28 @@
from opencsp import opencsp_settings


def is_notebook() -> bool:
"""
Is this code running in a notebook. From
https://stackoverflow.com/questions/15411967/how-can-i-check-if-code-is-executed-in-the-ipython-notebook

Returns
-------
bool
True if this code is executing in a Jypter notebook, or False otherwise.
"""
try:
shell = get_ipython().__class__.__name__ # type: ignore
if shell == 'ZMQInteractiveShell':
return True # Jupyter notebook or qtconsole
elif shell == 'TerminalInteractiveShell':
return False # Terminal running IPython
else:
return False # Other type (?)
except NameError:
return False # Probably standard Python interpreter


def is_solo():
"""Determines if this computer is one of the Solo HPC nodes.

Expand Down
4 changes: 2 additions & 2 deletions opencsp/default_settings.ini
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# These are the default settings for the OpenCSP code base.
#
# Settings values can be overridden by creating a file in one of the following locations:
# Windows: %USERPROFILE%/.opencsp/opencsp_settings.ini
# Other: ~/.config/opencsp/opencsp_settings.ini
# Windows: %USERPROFILE%/.opencsp/settings/opencsp_settings.ini
# Other: ~/.config/opencsp/settings/opencsp_settings.ini
# In addition, the environmental variable OPENCSP_SETTINGS_DIRS, if set, will be
# split on semicolon ';' characters and each directory searched for a
# 'opencsp_settings.ini' file.
Expand Down