import math
import os
import struct
from ansys_optical_automation.post_process.dpf_base import DataProcessingFramework
Photopic_Conversion_wavelength = [
380,
390,
400,
410,
420,
430,
440,
450,
460,
470,
480,
490,
500,
507,
510,
520,
530,
540,
550,
555,
560,
570,
580,
590,
600,
610,
620,
630,
640,
650,
660,
670,
680,
690,
700,
710,
720,
730,
740,
750,
760,
770,
]
Photopic_Conversion_value = [
0.027,
0.082,
0.27,
0.826,
2.732,
7.923,
15.709,
25.954,
40.98,
62.139,
94.951,
142.078,
220.609,
303.464,
303.464,
484.93,
588.746,
651.582,
679.551,
683,
679.585,
650.216,
594.21,
517.031,
430.973,
343.549,
260.223,
180.995,
119.525,
73.081,
41.663,
21.856,
11.611,
5.607,
2.802,
1.428,
0.715,
0.355,
0.17,
0.082,
0.041,
0.02,
]
[docs]class DpfRay:
"""
this class defines the ray property
"""
def __init__(self, x, y, z, l_dir, m_dir, n_dir, wavelength, e):
self.__cardinal_coordinate_x = x
self.__cardinal_coordinate_y = y
self.__cardinal_coordinate_Z = z
self.__vector_radiation_l = l_dir
self.__vector_radiation_m = m_dir
self.__vector_radiation_n = n_dir
self.__ray_wavelength = wavelength
self.__ray_energy = e
@property
def coordinate_x(self) -> float:
"""
return ray cardinal_coordinate_x
Returns:
cardinal_coordinate_x
-------
"""
return self.__cardinal_coordinate_x
@property
def coordinate_y(self) -> float:
"""
return ray cardinal_coordinate_y
Returns:
cardinal_coordinate_y
-------
"""
return self.__cardinal_coordinate_y
@property
def coordinate_z(self) -> float:
"""
return ray cardinal_coordinate_z
Returns:
cardinal_coordinate_z
-------
"""
return self.__cardinal_coordinate_Z
@property
def radiation_l(self) -> float:
"""
return ray vector_radiation_l
Returns:
vector_radiation_l
-------
"""
return self.__vector_radiation_l
@property
def radiation_m(self) -> float:
"""
return ray vector_radiation_m
Returns:
vector_radiation_m
-------
"""
return self.__vector_radiation_m
@property
def radiation_n(self) -> float:
"""
return ray vector_radiation_n
Returns:
vector_radiation_n
-------
"""
return self.__vector_radiation_n
@property
def wavelength(self) -> float:
"""
return ray wavelength
Returns:
wavelength
-------
"""
return self.__ray_wavelength
@property
def energy(self) -> float:
"""
return ray energy
Returns:
energy
-------
"""
return self.__ray_energy
[docs]class DpfRayfile(DataProcessingFramework):
"""
this class contains method to read extract ray data from given binary rayfile
"""
conversion_extension = {".ray": ".sdf", ".dat": ".ray", ".sdf": ".ray"}
def __init__(self, file_path):
DataProcessingFramework.__init__(self, extension=list(self.conversion_extension.keys()))
self.__ray_numb = 0
self.__watt_value = 0
self.__lumen_value = 0
self.__rays = []
self.identifier = 0
self.description = "description"
self.source_flux = 0
if file_path is not None:
self.open_file(file_path)
self.__binary = self.__is_binary()
self.load_content()
def __is_binary(self):
"""this method checks if a file is binary
Returns
-------
Boolean
"""
with open(self.file_path, "rb") as f:
for block in f:
if b"\0" in block:
f.close()
return True
f.close()
return False
def __photopic_conversion(self, wavelength):
"""This method computes photopic to Radiometric conversion factor at the given wavelength
Parameters
----------
wavelength : int
wavelength in nm
"""
start = 0
end = len(Photopic_Conversion_wavelength)
pos = 0
while start < end:
mid = (start + end) // 2
# print(mid, Photopic_Conversion_wavelength[mid])
if Photopic_Conversion_wavelength[mid] == wavelength:
pos = mid
break
if Photopic_Conversion_wavelength[mid] > wavelength:
end = mid
else:
start = mid + 1
pos = pos if pos != 0 else start
start_wave = Photopic_Conversion_wavelength[pos]
end_wave = Photopic_Conversion_wavelength[pos + 1]
start_photopic = Photopic_Conversion_value[pos]
end_photopic = Photopic_Conversion_value[pos + 1]
return ((wavelength - start_wave) / (end_wave - start_wave)) * (end_photopic - start_photopic) + start_photopic
[docs] def set_ray_count(self, raynumber):
"""
redfine raynumber
Parameters
----------
raynumber : int
Returns
-------
"""
self.__ray_numb = raynumber
[docs] def load_content(self):
"""
this method load the information from rayfile provided
Returns
-------
"""
rayfile_type = self.file_path.split(".")[-1]
if rayfile_type == "ray":
content_size = os.fstat(self.dpf_instance.fileno()).st_size - 28
if content_size % 32 != 0:
msg = "Provided rayfile is not generated from speos"
raise ValueError(msg)
self.__ray_numb = int(content_size / 32)
self.__watt_value = struct.unpack("f", self.dpf_instance.read(4))[0]
self.dpf_instance.read(4 * 5)
self.__lumen_value = struct.unpack("f", self.dpf_instance.read(4))[0]
for ray_idx in range(self.__ray_numb):
x = struct.unpack("f", self.dpf_instance.read(4))[0]
y = struct.unpack("f", self.dpf_instance.read(4))[0]
z = struct.unpack("f", self.dpf_instance.read(4))[0]
l_dir = struct.unpack("f", self.dpf_instance.read(4))[0]
m_dir = struct.unpack("f", self.dpf_instance.read(4))[0]
n_dir = struct.unpack("f", self.dpf_instance.read(4))[0]
wav = round(struct.unpack("f", self.dpf_instance.read(4))[0] * 0.001, 3)
e = struct.unpack("f", self.dpf_instance.read(4))[0]
if wav <= 0:
msg = "Error: ray wavelength of ray of " + str(ray_idx) + "cannot be <= 0"
raise ValueError(msg)
raylen = math.sqrt(l_dir * l_dir + m_dir * m_dir + n_dir * n_dir)
if abs(raylen - 1) > 1e-3:
msg = "Error: Vector length of " + str(ray_idx) + "the ray is unusual (" + str(raylen) + ")"
raise ValueError(msg)
if e < 0:
msg = "Error: ray power of " + str(m_dir) + "th ray is < 0"
raise ValueError(msg)
elif e == 0:
print("The " + str(ray_idx) + " th ray has 0 flux! \n This Ray was removed from data")
self.__ray_numb -= 1
else:
self.__rays.append(DpfRay(x, y, z, l_dir, m_dir, n_dir, wav, e))
self.dpf_instance.close()
elif (rayfile_type == "dat" or rayfile_type == "sdf") and self.__binary:
self.identifier = int.from_bytes(
self.dpf_instance.read(4), byteorder="little"
) # Format version ID, current value is 1010
self.__ray_numb = int.from_bytes(
self.dpf_instance.read(4), byteorder="little"
) # The number of rays in the file
self.description = self.dpf_instance.read(100).decode() # A text description of the source
self.source_flux = struct.unpack("f", self.dpf_instance.read(4))[
0
] # The total flux in watts of this source
ray_set_flux = struct.unpack("f", self.dpf_instance.read(4))[
0
] # The flux in watts represented by this Ray Set
wavelength = round(struct.unpack("f", self.dpf_instance.read(4))[0], 3)
# The wavelength in micrometers,
# 0 if a composite,converted to nanometer since this is the speos' source file format.
self.dpf_instance.read(18 * 4) # Unused data
ray_format_type = int.from_bytes(self.dpf_instance.read(4), byteorder="little")
# The ray_format_type must be either 0 for flux only format(.dat), or 2 for the spectral color format(.sdf).
flux_type = int.from_bytes(
self.dpf_instance.read(4), byteorder="little"
) # If and only if the ray_format_type is 0, then the flux_type is 0 for watts, and 1 for lumens.
# For the spectral color format(.sdf), the flux must be in watts, and the wavelength in micrometers.
self.dpf_instance.read(4 * 2) # Unused data
content_size = os.fstat(self.dpf_instance.fileno()).st_size - self.dpf_instance.tell()
if ray_format_type == 0:
if content_size % (7 * 4) != 0 or content_size // (7 * 4) != self.__ray_numb:
msg = "Warning: Zemax file may be wrong format. File size does not match ray numbers said in header"
raise ValueError(msg)
wavelength = wavelength if wavelength != 0 else 0.550
photopic_conversion = self.__photopic_conversion(wavelength * 1000)
# float(input(
# 'You can find a luminous efficacy table here: '
# 'http://hyperphysics.phy-astr.gsu.edu/hbase/vision/efficacy.html '
# 'Please enter the Photopic Conversion value for this wavelength, 683 for wavelength at 550nm: '))
if flux_type == 0:
self.__watt_value = ray_set_flux
self.__lumen_value = ray_set_flux * photopic_conversion
elif flux_type == 1:
self.__watt_value = ray_set_flux / photopic_conversion
self.__lumen_value = ray_set_flux
else:
msg = "flux_type is in wrong format"
raise TypeError(msg)
elif ray_format_type == 2:
if content_size % (8 * 4) != 0 or content_size // (8 * 4) != self.__ray_numb:
msg = "Zemax file may be wrong format. File size does not match ray numbers said in header."
raise TypeError(msg)
self.__watt_value = ray_set_flux
else:
msg = "ray_format_type " + str(ray_format_type) + " is in wrong format"
raise TypeError(msg)
for ray_idx in range(self.__ray_numb):
x = struct.unpack("f", self.dpf_instance.read(4))[0]
y = struct.unpack("f", self.dpf_instance.read(4))[0]
z = struct.unpack("f", self.dpf_instance.read(4))[0]
l_dir = struct.unpack("f", self.dpf_instance.read(4))[0]
m_dir = struct.unpack("f", self.dpf_instance.read(4))[0]
n_dir = struct.unpack("f", self.dpf_instance.read(4))[0]
wav = wavelength if wavelength != 0 else 550
e = struct.unpack("f", self.dpf_instance.read(4))[0]
if ray_format_type == 2:
wav = round(struct.unpack("f", self.dpf_instance.read(4))[0], 3)
if wav <= 0:
msg = "Error: ray wavelength cannot be <= 0"
raise ValueError(msg)
raylen = math.sqrt(l_dir * l_dir + m_dir * m_dir + n_dir * n_dir)
if abs(raylen - 1) > 1e-3:
msg = "Error: Vector length of " + str(m_dir) + "th ray is unusual (" + str(raylen) + ")"
raise ValueError(msg)
# Check ray energy:
if e < 0:
msg = "Error: ray power of " + str(m_dir) + "th ray is < 0"
raise ValueError(msg)
elif e == 0:
print("The " + str(ray_idx) + " the ray has 0 flux! \n This Ray was removed from data")
self.__ray_number -= 1
else:
self.__rays.append(DpfRay(x, y, z, l_dir, m_dir, n_dir, wav, e))
self.dpf_instance.close()
else:
if not self.__binary:
msg = "Non binary files not supported \n Filepath:" + self.file_path
raise TypeError(msg)
else:
msg = (
"Provided file type is not supported \n Filepath: "
+ self.file_path
+ "\n"
+ "For Speos rayfile you can try to open the file with the RayfileEditor and save the file"
)
raise TypeError(msg)
@property
def radiometric_power(self) -> float:
"""
this method return the Radiometric Power value
Returns:
Radiometric Power value
-------
"""
return self.__watt_value
@property
def photometric_power(self) -> float:
"""
this method return Photometric Power value
Returns:
Photometric Power value
-------
"""
return self.__lumen_value
@property
def rays_number(self) -> int:
"""
this method return the number of rays
Returns:
number of rays in rayfile
-------
"""
return self.__ray_numb
@property
def rays(self) -> [DpfRay]:
"""
this method return a list of rays
Returns:
a list of rays
-------
"""
return self.__rays
[docs] def export_to_zemax(self):
"""
this method convert the rayfile into zemax format
Returns
-------
None
"""
outfile = self.export_file()
zemax_spectrum_file = open(outfile, "wb")
zemax_spectrum_file.write(struct.pack("<I", 1010)) # Identifier
zemax_spectrum_file.write(struct.pack("<I", self.rays_number)) # NbrRays
description = "Converted from SPEOS .ray file."
# if len(description) > 100:
# description = description[:100]
# else:
# description = description.ljust(100)
description = description.ljust(100)
zemax_spectrum_file.write(description.encode("ascii")) # Description
zemax_spectrum_file.write(struct.pack("f", self.radiometric_power)) # SourceFlux = radiometric flux in SPOES
zemax_spectrum_file.write(struct.pack("f", self.radiometric_power)) # RaySetFlux = radiometric flux in SPOES
zemax_spectrum_file.write(struct.pack("f", 0)) # Wavelength
zemax_spectrum_file.write(struct.pack("f", 0)) # InclinationBeg
zemax_spectrum_file.write(struct.pack("f", 0)) # InclinationEnd
zemax_spectrum_file.write(struct.pack("f", 0)) # AzimuthBeg
zemax_spectrum_file.write(struct.pack("f", 0)) # AzimuthEnd
zemax_spectrum_file.write(struct.pack("<I", 4)) # DimensionUnits: M=0, IN=1, CM=2, FT=3, MM=4
zemax_spectrum_file.write(struct.pack("f", 0)) # LocX
zemax_spectrum_file.write(struct.pack("f", 0)) # LocY
zemax_spectrum_file.write(struct.pack("f", 0)) # LocZ
zemax_spectrum_file.write(struct.pack("f", 0)) # RotX
zemax_spectrum_file.write(struct.pack("f", 0)) # RotY
zemax_spectrum_file.write(struct.pack("f", 0)) # RotZ
zemax_spectrum_file.write(struct.pack("f", 0)) # ScaleX
zemax_spectrum_file.write(struct.pack("f", 0)) # ScaleY
zemax_spectrum_file.write(struct.pack("f", 0)) # ScaleZ
zemax_spectrum_file.write(struct.pack("f", 0)) # unused1 (float)
zemax_spectrum_file.write(struct.pack("f", 0)) # unused2 (float)
zemax_spectrum_file.write(struct.pack("f", 0)) # unused3 (float)
zemax_spectrum_file.write(struct.pack("f", 0)) # unused4 (float)
zemax_spectrum_file.write(
struct.pack("<I", 2)
) # ray_format_type is color since the SPEOS ray file always contains wavelength
zemax_spectrum_file.write(struct.pack("<I", 0)) # flux_type is Watts
zemax_spectrum_file.write(struct.pack("<I", 0)) # reversed1 (int)
zemax_spectrum_file.write(struct.pack("<I", 0)) # reversed2 (int)
for ray in self.rays:
zemax_spectrum_file.write(struct.pack("f", ray.coordinate_x)) # x
zemax_spectrum_file.write(struct.pack("f", ray.coordinate_y)) # y
zemax_spectrum_file.write(struct.pack("f", ray.coordinate_z)) # z
zemax_spectrum_file.write(struct.pack("f", ray.radiation_l)) # l
zemax_spectrum_file.write(struct.pack("f", ray.radiation_m)) # m
zemax_spectrum_file.write(struct.pack("f", ray.radiation_n)) # n
zemax_spectrum_file.write(struct.pack("f", ray.energy)) # flux (Watts)
zemax_spectrum_file.write(struct.pack("f", ray.wavelength)) # wavelength
zemax_spectrum_file.close()
[docs] def export_to_speos(self):
"""
this method convert the rayfile into speos format
Returns
-------
None
"""
outfile = self.export_file()
speos_ray_file = open(outfile, "wb")
speos_ray_file.write(struct.pack("f", self.radiometric_power))
speos_ray_file.write(struct.pack("f", 2.0))
speos_ray_file.write(struct.pack("f", 2.0))
speos_ray_file.write(struct.pack("f", 2.0))
speos_ray_file.write(struct.pack("f", 2.0))
speos_ray_file.write(struct.pack("f", 2.0))
speos_ray_file.write(struct.pack("f", self.photometric_power))
for ray in self.rays:
speos_ray_file.write(struct.pack("f", ray.coordinate_x))
speos_ray_file.write(struct.pack("f", ray.coordinate_y))
speos_ray_file.write(struct.pack("f", ray.coordinate_z))
speos_ray_file.write(struct.pack("f", ray.radiation_l))
speos_ray_file.write(struct.pack("f", ray.radiation_m))
speos_ray_file.write(struct.pack("f", ray.radiation_n))
speos_ray_file.write(struct.pack("f", ray.wavelength * 1000))
speos_ray_file.write(struct.pack("f", ray.energy))
speos_ray_file.close()
[docs] def export_file(self, export_folder_dir=None, convert=False):
"""
this method generates a file to be exported
Parameters
----------
export_folder_dir : str ,optional
defines path where to export the rayfile
convert : Boolean , optional
defines if the export is a conversion, default value is False
Returns
-------
outfile: str
output file
"""
input_file_folder = os.path.dirname(self.file_path)
input_file_name = os.path.basename(self.file_path).split(".")[0]
input_file_extension = os.path.splitext(self.file_path)[1].lower()[0:]
output_file_path = ""
if export_folder_dir is not None:
self.valid_dir(export_folder_dir)
output_file_path = os.path.join(export_folder_dir, input_file_name)
else:
output_file_path = os.path.join(input_file_folder, input_file_name)
if convert:
if input_file_extension in self.conversion_extension:
exported_file_extension = self.conversion_extension[input_file_extension]
else:
exported_file_extension = input_file_extension
msg = "Provided file extension " + exported_file_extension + " is not supported"
raise TypeError(msg)
else:
if input_file_extension in self.conversion_extension:
exported_file_extension = input_file_extension
else:
exported_file_extension = input_file_extension
msg = "Provided file extension" + exported_file_extension + "is not supported"
raise TypeError(msg)
outfile = output_file_path + exported_file_extension
outfile_num = 1
while os.path.isfile(outfile):
outfile = output_file_path + "_" + str(outfile_num) + exported_file_extension
outfile_num += 1
return outfile