docs(utils/opatio): added doxygen comments to python module

This commit is contained in:
2025-02-16 12:47:42 -05:00
parent 11b3811382
commit fde561cdef
2 changed files with 134 additions and 28 deletions

View File

@@ -13,31 +13,40 @@ import os
@dataclass
class Header:
magic: str
version: int
numTables: int
headerSize: int
indexOffset: int
creationDate: str
sourceInfo: str
comment: str
reserved: bytes
"""
@brief Structure to hold the header information of an OPAT file.
"""
magic: str #< Magic number to identify the file type
version: int #< Version of the OPAT file format
numTables: int #< Number of tables in the file
headerSize: int #< Size of the header
indexOffset: int #< Offset to the index section
creationDate: str #< Creation date of the file
sourceInfo: str #< Source information
comment: str #< Comment section
reserved: bytes #< Reserved for future use
@dataclass
class TableIndex:
X: float
Z: float
byteStart: int
byteEnd: int
sha256: bytes
"""
@brief Structure to hold the index information of a table in an OPAT file.
"""
X: float #< X composition value
Z: float #< Z composition value
byteStart: int #< Byte start position of the table
byteEnd: int #< Byte end position of the table
sha256: bytes #< SHA-256 hash of the table data
@dataclass
class OPATTable:
N_R: int
N_T: int
logR: Iterable[float]
logT: Iterable[float]
logKappa: Iterable[Iterable[float]]
"""
@brief Structure to hold the data of an OPAT table.
"""
N_R: int #< Number of R values
N_T: int #< Number of T values
logR: Iterable[float] #< Logarithm of R values
logT: Iterable[float] #< Logarithm of T values
logKappa: Iterable[Iterable[float]] #< Logarithm of Kappa values
defaultHeader = Header(
magic="OPAT",
@@ -58,19 +67,31 @@ class OpatIO:
@staticmethod
def validate_char_array_size(s: str, nmax: int) -> bool:
"""
@brief Validate the size of a character array.
@param s The string to validate.
@param nmax The maximum allowed size.
@return True if the string size is valid, False otherwise.
"""
if len(s) > nmax:
return False
return True
@staticmethod
def validate_logKappa(logKappa):
"""
@brief Validate the logKappa array.
@param logKappa The logKappa array to validate.
@throws ValueError if logKappa is not a non-empty 2D array.
@throws TypeError if logKappa is not a 2D array or iterable.
"""
if isinstance(logKappa, np.ndarray):
if logKappa.ndim == 2:
return
else:
raise ValueError("logKappa must be a non-empty 2D array")
if isintance(logKappa, collectionIterable) and all(isinstance(row, collectionIterable) for row in logKappa):
if isinstance(logKappa, collectionIterable) and all(isinstance(row, collectionIterable) for row in logKappa):
try:
first_row = next(iter(logKappa))
if all(isinstance(x, (int, float)) for x in first_row):
@@ -84,6 +105,13 @@ class OpatIO:
@staticmethod
def validate_1D(arr, name: str):
"""
@brief Validate a 1D array.
@param arr The array to validate.
@param name The name of the array.
@throws ValueError if the array is not 1D or not fully numeric.
@throws TypeError if the array is not a non-empty 1D array or iterable.
"""
if isinstance(arr, np.ndarray):
if arr.ndim == 1:
return
@@ -95,29 +123,60 @@ class OpatIO:
else:
raise ValueError(f"{name} must be fully numeric")
else:
raise TypeError(f"{name} must be a non-empty 2D array or iterable")
raise TypeError(f"{name} must be a non-empty 1D array or iterable")
@staticmethod
def compute_checksum(data: bytes) -> bytes:
"""
@brief Compute the SHA-256 checksum of the given data.
@param data The data to compute the checksum for.
@return The SHA-256 checksum.
"""
return hashlib.sha256(data).digest()
def set_version(self, version: int) -> int:
"""
@brief Set the version of the OPAT file.
@param version The version to set.
@return The set version.
"""
self.header.version = version
return self.header.version
def set_source(self, source: str) -> str:
"""
@brief Set the source information of the OPAT file.
@param source The source information to set.
@return The set source information.
@throws TypeError if the source string is too long.
"""
if not self.validate_char_array_size(source, 64):
raise TypeError(f"sourceInfo string ({source}) is too long ({len(source)}). Max length is 64")
self.header.sourceInfo = source
return self.header.sourceInfo
def set_comment(self, comment: str) -> str:
"""
@brief Set the comment of the OPAT file.
@param comment The comment to set.
@return The set comment.
@throws TypeError if the comment string is too long.
"""
if not self.validate_char_array_size(comment, 128):
raise TypeError(f"comment string ({comment}) is too long ({len(comment)}). Max length is 128")
self.header.comment = comment
return self.header.comment
def add_table(self, X: float, Z: float, logR: Iterable[float], logT: Iterable[float], logKappa: Iterable[Iterable[float]]):
"""
@brief Add a table to the OPAT file.
@param X The X composition value.
@param Z The Z composition value.
@param logR The logR values.
@param logT The logT values.
@param logKappa The logKappa values.
@throws ValueError if logKappa is not a non-empty 2D array or if logR and logT are not 1D arrays.
"""
self.validate_logKappa(logKappa)
self.validate_1D(logR, "logR")
self.validate_1D(logT, "logT")
@@ -142,6 +201,10 @@ class OpatIO:
def _header_bytes(self) -> bytes:
"""
@brief Convert the header to bytes.
@return The header as bytes.
"""
headerBytes = struct.pack(
"<4s H I I Q 16s 64s 128s 26s",
self.header.magic.encode('utf-8'),
@@ -157,6 +220,11 @@ class OpatIO:
return headerBytes
def _table_bytes(self, table: OPATTable) -> Tuple[bytes, bytes]:
"""
@brief Convert a table to bytes.
@param table The OPAT table.
@return A tuple containing the checksum and the table as bytes.
"""
logR = table.logR.flatten()
logT = table.logT.flatten()
logKappa = table.logKappa.flatten()
@@ -172,6 +240,12 @@ class OpatIO:
return (checksum, tableBytes)
def _tableIndex_bytes(self, tableIndex: TableIndex) -> bytes:
"""
@brief Convert a table index to bytes.
@param tableIndex The table index.
@return The table index as bytes.
@throws RuntimeError if the table index entry does not have 64 bytes.
"""
tableIndexBytes = struct.pack(
'<ddQQ',
tableIndex.X,
@@ -187,8 +261,12 @@ class OpatIO:
return tableIndexBytes
def __repr__(self) -> str:
"""
@brief Get the string representation of the OpatIO object.
@return The string representation.
"""
reprString = f"""OpatIO(
versoion: {self.header.version}
version: {self.header.version}
numTables: {self.header.numTables}
headerSize: {self.header.headerSize}
indexOffset: {self.header.indexOffset}
@@ -200,12 +278,19 @@ class OpatIO:
return reprString
def _format_table_as_string(self, table: OPATTable, X: float, Z: float) -> str:
"""
@brief Format a table as a string.
@param table The OPAT table.
@param X The X composition value.
@param Z The Z composition value.
@return The formatted table as a string.
"""
tableString: List[str] = []
# fixed width X and Z header per table
tableString.append(f"X: {X:<10.4f} Z: {Z:<10.4f}")
tableString.append("-" * 80)
# write logR accross the top (reserving one col for where logT will be)
logRRow = f"{"":<10}"
# write logR across the top (reserving one col for where logT will be)
logRRow = f"{'':<10}"
logRRowTrue = "".join(f"{r:<10.4f}" for r in table.logR)
tableString.append(logRRow + logRRowTrue)
for i, logT in enumerate(table.logT):
@@ -218,6 +303,11 @@ class OpatIO:
@staticmethod
def print_table_indexes(table_indexes: List[TableIndex]) -> str:
"""
@brief Print the table indexes.
@param table_indexes The list of table indexes.
@return The formatted table indexes as a string.
"""
if not table_indexes:
print("No table indexes found.")
return
@@ -231,6 +321,11 @@ class OpatIO:
return '\n'.join(tableRows)
def save_as_ascii(self, filename: str) -> str:
"""
@brief Save the OPAT file as an ASCII file.
@param filename The name of the file.
@return The name of the saved file.
"""
numericFMT = "{:.18e}"
currentStartByte: int = 256
tableIndexs: List[bytes] = []
@@ -268,6 +363,12 @@ class OpatIO:
f.write(self.print_table_indexes(tableIndexs))
def save(self, filename: str) -> str:
"""
@brief Save the OPAT file as a binary file.
@param filename The name of the file.
@return The name of the saved file.
@throws RuntimeError if the header does not have 256 bytes.
"""
tempHeaderBytes = self._header_bytes()
if len(tempHeaderBytes) != 256:
@@ -305,6 +406,12 @@ class OpatIO:
def loadOpat(filename: str) -> OpatIO:
"""
@brief Load an OPAT file.
@param filename The name of the file.
@return The loaded OpatIO object.
@throws RuntimeError if the header does not have 256 bytes.
"""
opat = OpatIO()
with open(filename, 'rb') as f:
headerBytes: bytes = f.read(256)
@@ -355,4 +462,3 @@ def loadOpat(filename: str) -> OpatIO:
opat.add_table(tableIndex.X, tableIndex.Z, logR, logT, logKappa)
return opat