diff --git a/doc/changelog.d/643.added.md b/doc/changelog.d/643.added.md new file mode 100644 index 000000000..0495abeca --- /dev/null +++ b/doc/changelog.d/643.added.md @@ -0,0 +1 @@ +Add export result as vtp files \ No newline at end of file diff --git a/src/ansys/speos/core/simulation.py b/src/ansys/speos/core/simulation.py index becacba8c..1d997e8d8 100644 --- a/src/ansys/speos/core/simulation.py +++ b/src/ansys/speos/core/simulation.py @@ -32,11 +32,10 @@ import warnings from ansys.api.speos.job.v2 import job_pb2 +from ansys.api.speos.job.v2.job_pb2 import Result from ansys.api.speos.scene.v2 import scene_pb2 as messages from ansys.api.speos.simulation.v1 import simulation_template_pb2 from ansys.speos.core.generic.general_methods import min_speos_version - -# from ansys.speos.core.geo_ref import GeoRef from ansys.speos.core.kernel.job import ProtoJob from ansys.speos.core.kernel.proto_message_utils import protobuf_message_to_str from ansys.speos.core.kernel.scene import ProtoScene @@ -265,7 +264,48 @@ def export(self, export_path: Union[str, Path]) -> None: "Selected simulation is not the first simulation feature, it can't be exported." ) - def compute_CPU(self, threads_number: Optional[int] = None) -> List[job_pb2.Result]: + def _export_vtp(self) -> List[Path]: + """Export the simulation results into vtp files. + + Returns + ------- + List[Path] + list of vtp paths. + + """ + vtp_files = [] + from ansys.speos.core import Face + from ansys.speos.core.sensor import Sensor3DIrradiance, SensorIrradiance + from ansys.speos.core.workflow.open_result import export_xm3_vtp, export_xmp_vtp + + sensor_paths = self.get(key="sensor_paths") + for feature in self._project._features: + if feature._name not in sensor_paths: + continue + match feature: + case SensorIrradiance(): + xmp_data = feature.get(key="result_file_name") + exported_vtp = export_xmp_vtp(self, xmp_data) + vtp_files.append(exported_vtp) + case Sensor3DIrradiance(): + xm3_data = feature.get(key="result_file_name") + geo_paths = feature.get(key="geo_paths") + geos_faces = [ + self._project.find(name=geo_path, feature_type=Face)[0]._face + for geo_path in geo_paths + ] + exported_vtp = export_xm3_vtp(self, geos_faces, xm3_data) + vtp_files.append(exported_vtp) + case _: + warnings.warn( + "feature {} result currently not supported".format(feature._name), + stacklevel=2, + ) + return vtp_files + + def compute_CPU( + self, threads_number: Optional[int] = None, export_vtp: Optional[bool] = False + ) -> tuple[list[Result], list[Path]] | list[Result]: """Compute the simulation on CPU. Parameters @@ -273,6 +313,8 @@ def compute_CPU(self, threads_number: Optional[int] = None) -> List[job_pb2.Resu threads_number : int, optional The number of threads used. By default, ``None``, means the number of processor available. + export_vtp: bool, optional + True to generate vtp from the simulation results. Returns ------- @@ -287,11 +329,21 @@ def compute_CPU(self, threads_number: Optional[int] = None) -> List[job_pb2.Resu ) self.result_list = self._run_job() + if export_vtp: + vtp_files = self._export_vtp() + return self.result_list, vtp_files return self.result_list - def compute_GPU(self) -> List[job_pb2.Result]: + def compute_GPU( + self, export_vtp: Optional[bool] = False + ) -> tuple[list[Result], list[Path]] | list[Result]: """Compute the simulation on GPU. + Parameters + ---------- + export_vtp: bool, optional + True to generate vtp from the simulation results. + Returns ------- List[ansys.api.speos.job.v2.job_pb2.Result] @@ -299,6 +351,9 @@ def compute_GPU(self) -> List[job_pb2.Result]: """ self._job.job_type = ProtoJob.Type.GPU self.result_list = self._run_job() + if export_vtp: + vtp_files = self._export_vtp() + return self.result_list, vtp_files return self.result_list def _run_job(self) -> List[job_pb2.Result]: diff --git a/src/ansys/speos/core/workflow/open_result.py b/src/ansys/speos/core/workflow/open_result.py index b36f77102..cf08c0c18 100644 --- a/src/ansys/speos/core/workflow/open_result.py +++ b/src/ansys/speos/core/workflow/open_result.py @@ -24,17 +24,19 @@ import os from pathlib import Path import tempfile -from typing import Union +from typing import List, Union import ansys.api.speos.file.v1.file_transfer as file_transfer_helper__v1 import ansys.api.speos.file.v1.file_transfer_pb2_grpc as file_transfer__v1__pb2_grpc +from ansys.api.speos.part.v1 import face_pb2 if os.name == "nt": from comtypes.client import CreateObject + import matplotlib.image as mpimg import matplotlib.pyplot as plt -from numpy import ndarray +import numpy from ansys.speos.core.simulation import ( SimulationDirect, @@ -43,6 +45,28 @@ ) +class _Speos3dData: + def __init__( + self, + x, + y, + z, + illuminance=0.0, + irradiance=0.0, + reflection=0.0, + transmission=0.0, + absorption=0.0, + ): + self.x = float(x) + self.y = float(y) + self.z = float(z) + self.illuminance = float(illuminance) + self.irradiance = float(irradiance) + self.reflection = float(reflection) + self.transmission = float(transmission) + self.absorption = float(absorption) + + def _find_correct_result( simulation_feature: Union[SimulationDirect, SimulationInverse, SimulationInteractive], result_name: str, @@ -77,7 +101,7 @@ def _find_correct_result( return file_path -def _display_image(img: ndarray): +def _display_image(img: numpy.ndarray): if img is not None: plt.imshow(img) plt.axis("off") # turns off axes @@ -142,3 +166,239 @@ def open_result_in_viewer( dpf_instance = CreateObject("HDRIViewer.Application") dpf_instance.OpenFile(file_path) dpf_instance.Show(1) + + def export_xmp_vtp( + simulation_feature: Union[SimulationDirect, SimulationInverse], + result_name: Union[str, Path], + ) -> Path: + """Export an XMP result into vtp file. + + Parameters + ---------- + simulation_feature : ansys.speos.core.simulation.Simulation + The simulation feature. + result_name: Union[str, Path] + file path of an XMP result. + + Returns + ------- + Path + file path of exported vtp file. + + """ + import pyvista as pv + + result_name = Path(result_name) + if not str(result_name).lower().endswith(".xmp"): + result_name = result_name.with_name(result_name.name + ".xmp") + file_path = _find_correct_result(simulation_feature, str(result_name)) + + if file_path == "": + raise ValueError( + "No result corresponding to " + + str(result_name) + + " is found in " + + simulation_feature._name + ) + + file_path = Path(file_path) + dpf_instance = CreateObject("XMPViewer.Application") + dpf_instance.OpenFile(str(file_path)) + dimension_x = dpf_instance.XWidth + dimension_y = dpf_instance.YHeight + resolution_x = dpf_instance.XNb + resolution_y = dpf_instance.YNb + tmp_txt = file_path.with_suffix(".txt") + dpf_instance.ExportTXT(str(tmp_txt)) + + file = tmp_txt.open("r") + content = file.readlines() + file.close() + skip_lines = 9 if "SeparatedByLayer" in content[7] else 8 + xmp_data = [] + if dpf_instance.Maptype == 2 and len(content[6].strip().split()) == 3: + # spectral data within number of data tables + spectral_tables = int(content[6].strip().split()[2]) + xmp_data = [ + [0 for _ in range(len(content[skip_lines].strip().split()))] + for _ in range(resolution_y) + ] + for _ in range(spectral_tables): + for i in range(resolution_y): + row = list(map(float, content[skip_lines].strip().split())) + for j in range(resolution_x): + xmp_data[i][j] += row[j] + skip_lines += 1 + # Skip one line between tables + skip_lines += 1 + else: + # not spectral data + for line in content[skip_lines : skip_lines + resolution_y]: + line_content = line.strip().split() + xmp_data.append(list(map(float, line_content))) + + # Create VTK ImageData structure + step_x = float(dimension_x) / resolution_x + step_y = float(dimension_y) / resolution_y + origin_x = -(resolution_x * step_x) / 2 + origin_y = -(resolution_y * step_y) / 2 + grid = pv.ImageData( + dimensions=(resolution_x, resolution_y, 1), + spacing=(step_x, step_y, 1), + origin=(origin_x, origin_y, 0), + ) + xmp_data = numpy.array(xmp_data) + if xmp_data.shape[1] == resolution_x: + if dpf_instance.UnitType == 0: + grid["Radiometric"] = numpy.ravel(xmp_data) + if dpf_instance.UnitType == 1: + grid["Photometric"] = numpy.ravel(xmp_data) + else: + grid["X"] = numpy.ravel(xmp_data[:, 0::4]) + grid["Photometric"] = numpy.ravel(xmp_data[:, 1::4]) + grid["Radiometric"] = numpy.ravel(xmp_data[:, 2::4]) + grid["Z"] = numpy.ravel(xmp_data[:, 3::4]) + vtp_meshes = grid.extract_surface() + # Export file to VTP + vtp_meshes.save(str(file_path.with_suffix(".vtp"))) + return file_path.with_suffix(".vtp") + + def export_xm3_vtp( + simulation_feature: Union[SimulationDirect, SimulationInverse], + geo_faces: List[face_pb2.Face], + result_name: Union[str, Path], + ) -> Path: + """Export an XMP result into vtp file. + + Parameters + ---------- + simulation_feature : ansys.speos.core.simulation.Simulation + The simulation feature. + geo_faces: List[face_pb2.Face] + list of face geometries. + result_name: Union[str, Path] + file path of an XMP result. + + Returns + ------- + Path + file path of exported vtp file. + + """ + import pyvista as pv + + result_name = Path(result_name) + if not str(result_name).lower().endswith(".xm3"): + result_name = result_name.with_name(result_name.name + ".xm3") + file_path = _find_correct_result(simulation_feature, str(result_name)) + + if file_path == "": + raise ValueError( + "No result corresponding to " + + str(result_name) + + " is found in " + + simulation_feature._name + ) + + file_path = Path(file_path) + dpf_instance = CreateObject("Xm3Viewer.Application") + dpf_instance.OpenFile(str(file_path)) + tmp_txt = file_path.with_suffix(".txt") + dpf_instance.Export(str(tmp_txt)) + + file = tmp_txt.open("r") + xm3_data = [] + content = file.readlines() + header = content[0].strip().split("\t") + illuminance_indices = [ + i for i, header_item in enumerate(header) if header_item == "Illuminance" + ] + irradiance_indices = [ + i for i, header_item in enumerate(header) if header_item == "Irradiance" + ] + reflection_indices = [ + i for i, header_item in enumerate(header) if "Reflection" in header_item + ] + transmission_indices = [ + i for i, header_item in enumerate(header) if "Transmission" in header_item + ] + absorption_indices = [ + i for i, header_item in enumerate(header) if "Absorption" in header_item + ] + + skip_line = 1 + try: + float(float(content[1].strip().split()[0])) + skip_line = 1 # only single layer + except ValueError: + skip_line = 2 # separated layer + for line in content[skip_line:]: + line_content = line.strip().split("\t") + xm3_data.append( + _Speos3dData( + x=float(line_content[0]), + y=float(line_content[1]), + z=float(line_content[2]), + illuminance=sum( + [ + float(item) + for i, item in enumerate(line_content) + if i in illuminance_indices + ], + 0.0, + ), + irradiance=sum( + [ + float(item) + for i, item in enumerate(line_content) + if i in irradiance_indices + ], + 0.0, + ), + reflection=sum( + [ + float(item) + for i, item in enumerate(line_content) + if i in reflection_indices + ], + 0.0, + ), + transmission=sum( + [ + float(item) + for i, item in enumerate(line_content) + if i in transmission_indices + ], + 0.0, + ), + absorption=sum( + [ + float(item) + for i, item in enumerate(line_content) + if i in absorption_indices + ], + 0.0, + ), + ) + ) + + vtp_meshes = None + for geo in geo_faces: + vertices = numpy.array(geo.vertices).reshape(-1, 3) + facets = numpy.array(geo.facets).reshape(-1, 3) + temp = numpy.full(facets.shape[0], 3) + temp = numpy.vstack(temp) + facets = numpy.hstack((temp, facets)) + if vtp_meshes is None: + vtp_meshes = pv.PolyData(vertices, facets) + else: + vtp_meshes = vtp_meshes.append_polydata(pv.PolyData(vertices, facets)) + + vtp_meshes["Illuminance [lx]"] = [item.illuminance for item in xm3_data] + vtp_meshes["Irradiance [W/m2]"] = [item.irradiance for item in xm3_data] + vtp_meshes["Reflection"] = [item.reflection for item in xm3_data] + vtp_meshes["Transmission"] = [item.transmission for item in xm3_data] + vtp_meshes["Absorption"] = [item.absorption for item in xm3_data] + # vtp_meshes = vtp_meshes.point_data_to_cell_data() + vtp_meshes.save(str(file_path.with_suffix(".vtp"))) + return file_path.with_suffix(".vtp") diff --git a/tests/core/test_simulation.py b/tests/core/test_simulation.py index af8d45d93..e8122a744 100644 --- a/tests/core/test_simulation.py +++ b/tests/core/test_simulation.py @@ -27,17 +27,19 @@ import pytest from ansys.api.speos.simulation.v1 import simulation_template_pb2 -from ansys.speos.core import GeoRef, Project, Speos -from ansys.speos.core.sensor import BaseSensor, SensorIrradiance +from ansys.speos.core import Body, GeoRef, Project, Speos +from ansys.speos.core.sensor import BaseSensor, Sensor3DIrradiance, SensorIrradiance from ansys.speos.core.simulation import ( SimulationDirect, SimulationInteractive, SimulationInverse, ) from ansys.speos.core.source import SourceLuminaire -from tests.conftest import test_path +from tests.conftest import config, test_path from tests.helper import does_file_exist, remove_file +IS_DOCKER = config.get("SpeosServerOnDocker") + def test_create_direct(speos: Speos): """Test creation of Direct Simulation.""" @@ -779,3 +781,359 @@ def test_export(speos: Speos): sim_second.export(export_path=str(Path(test_path) / "export_test")) remove_file(str(Path(test_path) / "export_test")) + + +@pytest.mark.skipif(IS_DOCKER, reason="COM API is only available locally") +def test_export_vtp(speos: Speos): + """Test export of xm3 and xmp as vtp files.""" + import numpy as np + import pyvista as pv + + from ansys.speos.core.workflow.open_result import _Speos3dData + + p = Project( + speos=speos, + path=str(Path(test_path) / "Prism.speos" / "Prism_3D.speos"), + ) + sim = p.find(name=".*", name_regex=True, feature_type=SimulationDirect)[0] + + ## ==== test 3d sensor photometric === + # verify illuminance, reflection, transmission, absorption are saved in vtp + # verify the vtp data is same as calculated + sensor_3d = p.find(name=".*", name_regex=True, feature_type=Sensor3DIrradiance)[0] + sensor_3d_geos = p.find(name="PrismBody", name_regex=True, feature_type=Body)[0]._geom_features + sensor_3d_mesh = [sensor_3d_geo._face for sensor_3d_geo in sensor_3d_geos] + sensor_3d.set_type_photometric() + sensor_3d.commit() + speos_results, vtp_results = sim.compute_CPU(export_vtp=True) + assert does_file_exist(vtp_results[1]) + + vtp_data = pv.read(vtp_results[1]).point_data + assert np.allclose(vtp_data.get("Reflection"), 0.0) is not True + assert np.allclose(vtp_data.get("Illuminance [lx]"), 0.0) is not True + assert np.allclose(vtp_data.get("Transmission"), 0.0) is not True + assert np.allclose(vtp_data.get("Absorption"), 0.0) is True + + export_data_xm3 = [result.path for result in speos_results if result.path.endswith(".xm3")][0] + export_data_xm3_txt = Path(export_data_xm3).with_suffix(".txt") + file = export_data_xm3_txt.open("r") + xm3_data = [] + content = file.readlines() + for line in content[1:]: + line_content = line.split() + xm3_data.append( + _Speos3dData( + x=line_content[0], + y=line_content[1], + z=line_content[2], + illuminance=0.0 if "Illuminance" not in content[0] else line_content[3], + reflection=0.0 if "Reflection" not in content[0] else line_content[4], + transmission=0.0 if "Transmission" not in content[0] else line_content[5], + absorption=0.0 if "Absorption" not in content[0] else line_content[6], + ) + ) + vtp_meshes = None + for geo in sensor_3d_mesh: + vertices = np.array(geo.vertices).reshape(-1, 3) + facets = np.array(geo.facets).reshape(-1, 3) + temp = np.full(facets.shape[0], 3) + temp = np.vstack(temp) + facets = np.hstack((temp, facets)) + if vtp_meshes is None: + vtp_meshes = pv.PolyData(vertices, facets) + else: + vtp_meshes = vtp_meshes.append_polydata(pv.PolyData(vertices, facets)) + + vtp_meshes["Illuminance [lx]"] = [item.illuminance for item in xm3_data] + vtp_meshes["Reflection"] = [item.reflection for item in xm3_data] + vtp_meshes["Transmission"] = [item.transmission for item in xm3_data] + vtp_meshes["Absorption"] = [item.absorption for item in xm3_data] + assert all( + np.isclose( + vtp_meshes.point_data.get("Illuminance [lx]"), + vtp_data.get("Illuminance [lx]"), + rtol=1e-5, + atol=1e-8, + ) + ) + assert all( + np.isclose( + vtp_meshes.point_data.get("Reflection"), + vtp_data.get("Reflection"), + rtol=1e-5, + atol=1e-8, + ) + ) + assert all( + np.isclose( + vtp_meshes.point_data.get("Transmission"), + vtp_data.get("Transmission"), + rtol=1e-5, + atol=1e-8, + ) + ) + assert all( + np.isclose( + vtp_meshes.point_data.get("Absorption"), + vtp_data.get("Absorption"), + rtol=1e-5, + atol=1e-8, + ) + ) + + ## === test 3d sensor photometric with radial integration === + # only illuminance value is saved in vtp file + p2 = Project( + speos=speos, + path=str(Path(test_path) / "Prism.speos" / "Prism_3D.speos"), + ) + sim = p2.find(name=".*", name_regex=True, feature_type=SimulationDirect)[0] + + sensor_3d = p2.find(name=".*", name_regex=True, feature_type=Sensor3DIrradiance)[0] + sensor_3d.set_type_photometric().set_integration_radial() + sensor_3d.commit() + speos_results, vtp_results = sim.compute_CPU(export_vtp=True) + assert does_file_exist(vtp_results[1]) + + vtp_data = pv.read(vtp_results[1]).point_data + assert np.allclose(vtp_data.get("Reflection"), 0.0) is True + assert np.allclose(vtp_data.get("Illuminance [lx]"), 0.0) is not True + assert np.allclose(vtp_data.get("Irradiance [W/m2]"), 0.0) is True + assert np.allclose(vtp_data.get("Transmission"), 0.0) is True + assert np.allclose(vtp_data.get("Absorption"), 0.0) is True + + ## === test 3d sensor radiometric === + # only irradiance, reflection, transmission, absorption value is saved in vtp file + # verify the vtp results are the same as calculated ones. + p3 = Project( + speos=speos, + path=str(Path(test_path) / "Prism.speos" / "Prism_3D.speos"), + ) + sim = p3.find(name=".*", name_regex=True, feature_type=SimulationDirect)[0] + + sensor_3d = p3.find(name=".*", name_regex=True, feature_type=Sensor3DIrradiance)[0] + sensor_3d_geos = p3.find(name="PrismBody", name_regex=True, feature_type=Body)[0]._geom_features + sensor_3d_mesh = [sensor_3d_geo._face for sensor_3d_geo in sensor_3d_geos] + sensor_3d.set_type_radiometric() + sensor_3d.commit() + speos_results, vtp_results = sim.compute_CPU(export_vtp=True) + assert does_file_exist(vtp_results[1]) + + vtp_data = pv.read(vtp_results[1]).point_data + assert np.allclose(vtp_data.get("Reflection"), 0.0) is not True + assert np.allclose(vtp_data.get("Irradiance [W/m2]"), 0.0) is not True + assert np.allclose(vtp_data.get("Transmission"), 0.0) is not True + assert np.allclose(vtp_data.get("Absorption"), 0.0) is True + assert np.allclose(vtp_data.get("Illuminance [lx]"), 0.0) is True + + export_data_xm3 = [result.path for result in speos_results if result.path.endswith(".xm3")][0] + export_data_xm3_txt = Path(export_data_xm3).with_suffix(".txt") + file = export_data_xm3_txt.open("r") + xm3_data = [] + content = file.readlines() + for line in content[1:]: + line_content = line.split() + xm3_data.append( + _Speos3dData( + x=line_content[0], + y=line_content[1], + z=line_content[2], + illuminance=0.0 if "Illuminance" not in content[0] else line_content[3], + irradiance=0.0 if "Irradiance" not in content[0] else line_content[3], + reflection=0.0 if "Reflection" not in content[0] else line_content[4], + transmission=0.0 if "Transmission" not in content[0] else line_content[5], + absorption=0.0 if "Absorption" not in content[0] else line_content[6], + ) + ) + vtp_meshes = None + for geo in sensor_3d_mesh: + vertices = np.array(geo.vertices).reshape(-1, 3) + facets = np.array(geo.facets).reshape(-1, 3) + temp = np.full(facets.shape[0], 3) + temp = np.vstack(temp) + facets = np.hstack((temp, facets)) + if vtp_meshes is None: + vtp_meshes = pv.PolyData(vertices, facets) + else: + vtp_meshes = vtp_meshes.append_polydata(pv.PolyData(vertices, facets)) + + vtp_meshes["Illuminance [lx]"] = [item.illuminance for item in xm3_data] + vtp_meshes["Irradiance [W/m2]"] = [item.irradiance for item in xm3_data] + vtp_meshes["Reflection"] = [item.reflection for item in xm3_data] + vtp_meshes["Transmission"] = [item.transmission for item in xm3_data] + vtp_meshes["Absorption"] = [item.absorption for item in xm3_data] + assert all( + np.isclose( + vtp_meshes.point_data.get("Illuminance [lx]"), + vtp_data.get("Illuminance [lx]"), + rtol=1e-5, + atol=1e-8, + ) + ) + assert all( + np.isclose( + vtp_meshes.point_data.get("Irradiance [W/m2]"), + vtp_data.get("Irradiance [W/m2]"), + rtol=1e-5, + atol=1e-8, + ) + ) + assert all( + np.isclose( + vtp_meshes.point_data.get("Reflection"), + vtp_data.get("Reflection"), + rtol=1e-5, + atol=1e-8, + ) + ) + assert all( + np.isclose( + vtp_meshes.point_data.get("Transmission"), + vtp_data.get("Transmission"), + rtol=1e-5, + atol=1e-8, + ) + ) + assert all( + np.isclose( + vtp_meshes.point_data.get("Absorption"), + vtp_data.get("Absorption"), + rtol=1e-5, + atol=1e-8, + ) + ) + + # === test 3d sensor colorimetric === + # verify if only illuminate data is saved in vtp + p4 = Project( + speos=speos, + path=str(Path(test_path) / "Prism.speos" / "Prism_3D.speos"), + ) + sim = p4.find(name=".*", name_regex=True, feature_type=SimulationDirect)[0] + + sensor_3d = p4.find(name=".*", name_regex=True, feature_type=Sensor3DIrradiance)[0] + sensor_3d.set_type_colorimetric() + sensor_3d.commit() + speos_results, vtp_results = sim.compute_CPU(export_vtp=True) + assert does_file_exist(vtp_results[1]) + + vtp_data = pv.read(vtp_results[1]).point_data + assert np.allclose(vtp_data.get("Reflection"), 0.0) is True + assert np.allclose(vtp_data.get("Illuminance [lx]"), 0.0) is not True + assert np.allclose(vtp_data.get("Irradiance [W/m2]"), 0.0) is True + assert np.allclose(vtp_data.get("Transmission"), 0.0) is True + assert np.allclose(vtp_data.get("Absorption"), 0.0) is True + + # === test irradiance xmp photometric === + # verify the result is photometric + p5 = Project( + speos=speos, + path=str(Path(test_path) / "Prism.speos" / "Prism.speos"), + ) + sim = p5.find(name=".*", name_regex=True, feature_type=SimulationDirect)[0] + sensor_irra = p5.find(name=".*", name_regex=True, feature_type=SensorIrradiance)[0] + sensor_irra.set_dimensions().set_x_sampling(10).set_y_sampling(10) + sensor_irra.set_type_photometric() + sensor_irra.commit() + speos_results, vtp_results = sim.compute_CPU(export_vtp=True) + assert does_file_exist(vtp_results[0]) + + vtp_data = pv.read(vtp_results[0]).point_data + assert np.allclose(vtp_data.get("Photometric"), 0.0) is not True + + export_data_xmp = [result.path for result in speos_results if result.path.endswith(".xmp")][0] + export_data_xmp_txt = Path(export_data_xmp).with_suffix(".txt") + file = export_data_xmp_txt.open("r") + content = file.readlines() + file.close() + skip_lines = 9 if "SeparatedByLayer" in content[7] else 8 + resolution_x = 10 + resolution_y = 10 + xmp_data = [] + if "2" not in content[0]: # not spectral data + for line in content[skip_lines : skip_lines + resolution_y]: + line_content = line.strip().split() + xmp_data.append(list(map(float, line_content))) + else: # spectral data within number of data tables + spectral_tables = int(content[6].strip().split()[2]) + xmp_data = [ + [0 for _ in range(len(content[skip_lines].strip().split()))] + for _ in range(resolution_y) + ] + for _ in range(spectral_tables): + for i in range(resolution_y): + row = list(map(float, content[skip_lines].strip().split())) + for j in range(resolution_x): + xmp_data[i][j] += row[j] + skip_lines += 1 + # Skip one line between tables + skip_lines += 1 + assert np.all( + np.isclose( + np.array(xmp_data), + vtp_data.get("Photometric").reshape((10, 10)).T, + rtol=1e-5, + atol=1e-8, + ) + ) + + ## === test irradiance xmp radiometric === + # verify the result is radiometric + p6 = Project( + speos=speos, + path=str(Path(test_path) / "Prism.speos" / "Prism.speos"), + ) + sim = p6.find(name=".*", name_regex=True, feature_type=SimulationDirect)[0] + sensor_irra = p6.find(name=".*", name_regex=True, feature_type=SensorIrradiance)[0] + sensor_irra.set_dimensions().set_x_sampling(10).set_y_sampling(10) + sensor_irra.set_type_radiometric() + sensor_irra.commit() + speos_results, vtp_results = sim.compute_CPU(export_vtp=True) + assert does_file_exist(vtp_results[0]) + + vtp_data = pv.read(vtp_results[0]).point_data + assert np.allclose(vtp_data.get("Radiometric"), 0.0) is not True + + ## === test irradiance colorimetric === + # verify it has x, photometric, radiometric, z value in vtp file + p7 = Project( + speos=speos, + path=str(Path(test_path) / "Prism.speos" / "Prism.speos"), + ) + sim = p7.find(name=".*", name_regex=True, feature_type=SimulationDirect)[0] + sensor_irra = p7.find(name=".*", name_regex=True, feature_type=SensorIrradiance)[0] + sensor_irra.set_dimensions().set_x_sampling(10).set_y_sampling(10) + sensor_irra.set_type_colorimetric() + sensor_irra.commit() + + speos_results, vtp_results = sim.compute_CPU(export_vtp=True) + assert does_file_exist(vtp_results[0]) + + vtp_data = pv.read(vtp_results[0]).point_data + assert np.allclose(vtp_data.get("X"), 0.0) is not True + assert np.allclose(vtp_data.get("Photometric"), 0.0) is not True + assert np.allclose(vtp_data.get("Radiometric"), 0.0) is not True + assert np.allclose(vtp_data.get("Z"), 0.0) is not True + + ## === test irradiance spectral === + # verify it has x, photometric, radiometric, z value in vtp file + # verify the summing up per spectral layer + p8 = Project( + speos=speos, + path=str(Path(test_path) / "Prism.speos" / "Prism.speos"), + ) + sim = p8.find(name=".*", name_regex=True, feature_type=SimulationDirect)[0] + sensor_irra = p8.find(name=".*", name_regex=True, feature_type=SensorIrradiance)[0] + sensor_irra.set_dimensions().set_x_sampling(10).set_y_sampling(10) + sensor_irra.set_type_spectral() + sensor_irra.commit() + speos_results, vtp_results = sim.compute_CPU(export_vtp=True) + assert does_file_exist(vtp_results[0]) + + vtp_data = pv.read(vtp_results[0]).point_data + assert np.allclose(vtp_data.get("Radiometric"), 0.0) is not True + + # # test radiance photometric + # # test radiance radiometric + # # test radiance colorimetric + # # test radiance spectral