Skip to content

Commit

Permalink
FEAT: ISAR plot (#5639)
Browse files Browse the repository at this point in the history
Co-authored-by: tnegishi <[email protected]>
Co-authored-by: Maxime Rey <[email protected]>
Co-authored-by: Sébastien Morais <[email protected]>
  • Loading branch information
4 people authored Jan 9, 2025
1 parent 59e48a4 commit ccd0937
Show file tree
Hide file tree
Showing 5 changed files with 219 additions and 132 deletions.
139 changes: 114 additions & 25 deletions src/ansys/aedt/core/visualization/advanced/rcs_visualization.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,9 @@ def __init__(self, input_file):

self.__frequency_units = self.__metadata["frequency_units"]

self.__monostatic_file = self.output_dir / self.__metadata["monostatic_file"]
self.__monostatic_file = None
if self.__metadata["monostatic_file"]:
self.__monostatic_file = self.output_dir / self.__metadata["monostatic_file"]

self.__data_conversion_function = "dB20"
self.__window = "Flat"
Expand All @@ -141,19 +143,23 @@ def __init__(self, input_file):
self.__upsample_range = 512
self.__upsample_azimuth = 64

if not self.__monostatic_file.is_file():
if self.__monostatic_file and not self.__monostatic_file.is_file():
raise Exception("Monostatic file invalid.")

self.rcs_column_names = ["data"]

# Load farfield data
is_rcs_loaded = self.__init_rcs()
if self.__monostatic_file:
is_rcs_loaded = self.__init_rcs()
else:
is_rcs_loaded = True

if not is_rcs_loaded: # pragma: no cover
raise RuntimeError("RCS information can not be loaded.")

# Update active frequency if passed in the initialization
self.frequency = self.frequencies[0]
if self.__monostatic_file:
# Update active frequency if passed in the initialization
self.frequency = self.frequencies[0]

@property
def raw_data(self):
Expand Down Expand Up @@ -611,9 +617,6 @@ class MonostaticRCSPlotter(object):

def __init__(self, rcs_data):

# Public
self.modeler_window = None

# Private
self.__rcs_data = rcs_data
self.__logger = logger
Expand Down Expand Up @@ -716,7 +719,7 @@ def plot_rcs(
"""
curves = []
all_secondary_sweep_value = secondary_sweep_value
if primary_sweep.lower() == "freq" or primary_sweep.lower() == "frequency":
if primary_sweep.casefold() == "freq" or primary_sweep.casefold() == "frequency":
x_key = "Freq"
x = self.rcs_data.frequencies
if secondary_sweep == "IWaveTheta":
Expand All @@ -732,7 +735,7 @@ def plot_rcs(
y_key = "IWavePhi"
else:
data = self.rcs_data.rcs_active_frequency
if primary_sweep.lower() == "iwavephi":
if primary_sweep.casefold() == "iwavephi":
x_key = "IWavePhi"
y_key = "IWaveTheta"
x = self.rcs_data.available_incident_wave_phi
Expand Down Expand Up @@ -902,7 +905,9 @@ def plot_range_profile(
return new

@pyaedt_function_handler()
def plot_waterfall(self, title="Waterfall", output_file=None, show=True, is_polar=False, size=(1920, 1440)):
def plot_waterfall(
self, title="Waterfall", output_file=None, show=True, is_polar=False, size=(1920, 1440), figure=None
):
"""Create a 2D contour plot of the waterfall.
Parameters
Expand All @@ -918,6 +923,10 @@ def plot_waterfall(self, title="Waterfall", output_file=None, show=True, is_pola
Whether to display in polar coordinates. The default is ``True``.
size : tuple, optional
Image size in pixel (width, height).
figure : :class:`matplotlib.pyplot.Figure`, optional
An existing Matplotlib `Figure` to which the plot is added.
If not provided, a new `Figure` and `Axes` objects are created.
Default is ``None``.
Returns
-------
Expand Down Expand Up @@ -961,16 +970,11 @@ def plot_waterfall(self, title="Waterfall", output_file=None, show=True, is_pola
}

new.add_trace(plot_data, 2, props)
_ = new.plot_contour(
trace=0,
polar=is_polar,
snapshot_path=output_file,
show=show,
)
_ = new.plot_contour(trace=0, polar=is_polar, snapshot_path=output_file, show=show, figure=figure)
return new

@pyaedt_function_handler()
def plot_isar_2d(self, title="ISAR", output_file=None, show=True, size=(1920, 1440)):
def plot_isar_2d(self, title="ISAR", output_file=None, show=True, size=(1920, 1440), figure=None):
"""Create a 2D contour plot of the ISAR.
Parameters
Expand All @@ -984,6 +988,10 @@ def plot_isar_2d(self, title="ISAR", output_file=None, show=True, size=(1920, 14
If ``False``, the Matplotlib instance of the plot is shown.
size : tuple, optional
Image size in pixel (width, height).
figure : :class:`matplotlib.pyplot.Figure`, optional
An existing Matplotlib `Figure` to which the plot is added.
If not provided, a new `Figure` and `Axes` objects are created.
Default is ``None``.
Returns
-------
Expand Down Expand Up @@ -1019,12 +1027,7 @@ def plot_isar_2d(self, title="ISAR", output_file=None, show=True, size=(1920, 14
}

new.add_trace(plot_data, 2, props)
_ = new.plot_contour(
trace=0,
polar=False,
snapshot_path=output_file,
show=show,
)
_ = new.plot_contour(trace=0, polar=False, snapshot_path=output_file, show=show, figure=figure)
return new

@pyaedt_function_handler()
Expand Down Expand Up @@ -1617,6 +1620,92 @@ def add_waterfall(

self.all_scene_actors["results"]["waterfall"][waterfall_name] = rcs_mesh

@pyaedt_function_handler()
def add_isar_2d(
self,
plot_type="plane",
color_bar="jet",
):
"""Add the ISAR 2D.
Parameters
----------
plot_type : str, optional
The type of plot to create for the range profile. It can be ``"plane"``, ``"relief"``, and `"projection"``.
The default is ``"plane"``.
color_bar : str, optional
Color mapping to be applied to the RCS data. It can be a color (``"blue"``,
``"green"``, ...) or a colormap (``"jet"``, ``"viridis"``, ...). The default is ``"jet"``.
"""
data_isar_2d = self.rcs_data.isar_2d

down_range = data_isar_2d["Down-range"].unique()
cross_range = data_isar_2d["Cross-range"].unique()

values_2d = data_isar_2d.pivot(index="Cross-range", columns="Down-range", values="Data").to_numpy()

x, y = np.meshgrid(down_range, cross_range)
z = np.zeros_like(x)

if plot_type.casefold() == "relief":
m = 2.0
b = -1.0
z = (values_2d - values_2d.min()) / (values_2d.max() - values_2d.min()) * m + b

if plot_type.casefold() in ["relief", "plane"]:
actor = pv.StructuredGrid()
actor.points = np.c_[x.ravel(), y.ravel(), z.ravel()]

actor.dimensions = (len(down_range), len(cross_range), 1)

actor["values"] = values_2d.ravel()

else:
scene_actors = self.all_scene_actors["model"]
if scene_actors is None:
return None
actor = pv.PolyData()
for model_actor in scene_actors.values():
mesh = model_actor.custom_object.get_mesh()
xypoints = mesh.points
cpos = values_2d.flatten()
xpos_ypos = np.column_stack((x.flatten(), y.flatten(), cpos))
all_indices = self.__find_nearest_neighbors(xpos_ypos, xypoints)

mag_for_color = np.ndarray.flatten(cpos[all_indices])
if mesh.__class__.__name__ != "PolyData":
mesh_triangulated = mesh.triangulate()
model_actor.custom_object.mesh = pv.PolyData(mesh_triangulated.points, mesh_triangulated.cells)
else:
model_actor.custom_object.mesh.clear_data()
model_actor.custom_object.mesh[self.rcs_data.data_conversion_function] = mag_for_color
actor += model_actor.custom_object.mesh

all_results_actors = list(self.all_scene_actors["results"].keys())

if "isar_2d" not in all_results_actors:
self.all_scene_actors["results"]["isar_2d"] = {}

index = 0
while f"isar_2d_{index}" in self.all_scene_actors["results"]["isar_2d"]:
index += 1

isar_name = f"isar_2d_{index}"

isar_object = SceneMeshObject()
isar_object.name = isar_name

scalar_dict = dict(color="#000000", title="ISAR 2D")
isar_object.scalar_dict = scalar_dict

isar_object.cmap = color_bar

isar_object.mesh = actor

rcs_mesh = MeshObjectPlot(isar_object, isar_object.get_mesh())

self.all_scene_actors["results"]["isar_2d"][isar_name] = rcs_mesh

@pyaedt_function_handler()
def clear_scene(self, first_level=None, second_level=None, name=None):
if not first_level:
Expand Down Expand Up @@ -1651,7 +1740,7 @@ def __get_pyvista_range_profile_actor(
if extents is None:
extents = [0, 10, 0, 10, 0, 10]

plot_type_lower = plot_type.lower()
plot_type_lower = plot_type.casefold()
actor = None

if (
Expand Down
26 changes: 14 additions & 12 deletions src/ansys/aedt/core/visualization/plot/matplotlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -1311,14 +1311,7 @@ def _plot_limit_lines(self, convert_to_radians=False):

@pyaedt_function_handler()
def plot_contour(
self,
trace=0,
polar=False,
levels=64,
max_theta=180,
color_bar=None,
snapshot_path=None,
show=True,
self, trace=0, polar=False, levels=64, max_theta=180, color_bar=None, snapshot_path=None, show=True, figure=None
):
"""Create a Matplotlib figure contour based on a list of data.
Expand All @@ -1343,6 +1336,9 @@ def plot_contour(
The default value is ``None``.
show : bool, optional
Whether to show the plot or return the matplotlib object. Default is ``True``.
figure : :class:`matplotlib.pyplot.Figure`, optional
An existing Matplotlib `Figure` to which the plot is added.
If not provided, a new `Figure` and `Axes` object are created.
Returns
-------
Expand All @@ -1355,7 +1351,14 @@ def plot_contour(
else:
tr = tr[0]
projection = "polar" if polar else "rectilinear"
self.fig, self.ax = plt.subplots(subplot_kw={"projection": projection})

if not figure:
self.fig, self.ax = plt.subplots(subplot_kw={"projection": projection})
self.ax = plt.gca()
else:
self.fig = figure
self.ax = figure.add_subplot(111, polar=polar)

self.ax.set_xlabel(tr.x_label)
if polar:
self.ax.set_rticks(np.linspace(0, max_theta, 3))
Expand All @@ -1366,18 +1369,17 @@ def plot_contour(
ph = tr._spherical_data[2]
th = tr._spherical_data[1]
data_to_plot = tr._spherical_data[0]
plt.contourf(
contour = self.ax.contourf(
ph,
th,
data_to_plot,
levels=levels,
cmap="jet",
)
if color_bar:
cbar = plt.colorbar()
cbar = self.fig.colorbar(contour, ax=self.ax)
cbar.set_label(color_bar, rotation=270, labelpad=20)

self.ax = plt.gca()
self.ax.yaxis.set_label_coords(-0.1, 0.5)
self._plot(snapshot_path, show)
return self.fig
Expand Down
38 changes: 0 additions & 38 deletions tests/system/general/example_models/T49/rcs_metadata.json

This file was deleted.

38 changes: 0 additions & 38 deletions tests/system/general/example_models/T49/rcs_metadata_fake.json

This file was deleted.

Loading

0 comments on commit ccd0937

Please sign in to comment.