Skip to content

Commit

Permalink
small fixes and improvements to geometry and tools files
Browse files Browse the repository at this point in the history
  • Loading branch information
bbean23 committed Jan 24, 2025
1 parent c052d26 commit c891984
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 34 deletions.
12 changes: 10 additions & 2 deletions opencsp/common/lib/tool/file_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,11 @@ def path_components(input_dir_body_ext: str):
See also: body_ext_given_file_dir_body_ext()
"""

dir = os.path.dirname(input_dir_body_ext)
body_ext = os.path.basename(input_dir_body_ext)
body, ext = os.path.splitext(body_ext)

return dir, body, ext


Expand Down Expand Up @@ -591,6 +593,13 @@ def create_directories_if_necessary(input_dir):
except FileExistsError:
# probably just created this directory in another thread
pass
except Exception as ex:
lt.error_and_raise(
RuntimeError,
"Error in file_tools.create_directories_if_necessary(): "
+ f"failed to create directory '{input_dir}' with error {ex}",
ex,
)


def create_subdir_path(base_dir: str, dir_name: str):
Expand Down Expand Up @@ -841,8 +850,7 @@ def rename_directory(input_dir: str, output_dir: str):

def copy_and_delete_file(input_dir_body_ext: str, output_dir_body_ext: str, copystat=True):
"""
Like rename_file(), but with more surety that the file will still exist in
case the computer crashes while the file is in the process of being moved.
Like rename_file(), but it works across file systems.
See also: copy_file(), rename_file()
Expand Down
22 changes: 1 addition & 21 deletions opencsp/common/lib/tool/image_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import sys
from typing import Callable, TypeVar

import exiftool
import numpy as np
from PIL import Image

Expand Down Expand Up @@ -301,28 +302,11 @@ def getsizeof_approx(img: Image) -> int:
return object_size + image_data_size


def _import_exiftool():
# TODO should exiftool be added to requirements.txt?
# This also requires the installation of Phil Harvey's ExifTool.
# https://pypi.org/project/PyExifTool/
# https://exiftool.org/
try:
import exiftool
except ImportError:
lt.error_and_raise(ImportError, "Error in image_tools._import_exiftool(): " +
"exiftool is not currently installed as a standard part of OpenCSP." +
" To use exif information with OpenCSP, please follow the installation instructions at " +
"https://pypi.org/project/PyExifTool/#getting-pyexiftool.")


def get_exif_value(
data_dir: str, image_path_name_exts: str | list[str], exif_val: str = "EXIF:ISO", parser: Callable[[str], T] = int
) -> T | list[T]:
"""Returns the exif_val Exif information on the given images, if they have such
information. If not, then None is returned for those images."""
_import_exiftool()
import exiftool

# build the list of files
if isinstance(image_path_name_exts, str):
files = [ft.join(data_dir, image_path_name_exts)]
Expand Down Expand Up @@ -365,10 +349,6 @@ def get_exif_value(


def set_exif_value(data_dir: str, image_path_name_ext: str, exif_val: str, exif_name: str = "EXIF:ISO"):
# TODO should exiftool be added to requirements.txt?
_import_exiftool()
import exiftool

with exiftool.ExifToolHelper() as et:
et.set_tags(
ft.join(data_dir, image_path_name_ext),
Expand Down
95 changes: 84 additions & 11 deletions opencsp/common/lib/tool/log_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
import re
import socket
import sys
from typing import Callable
from typing import Callable, Literal

import numpy as np

# Don't import any other opencsp libraries here. Log tools _must_ be able to be
# imported before any other opencsp code. Instead, if there are other
Expand Down Expand Up @@ -327,12 +329,18 @@ def critical(*vargs, **kwargs) -> int:
return 0


def error_and_raise(exception_class: Exception.__class__, msg: str) -> None:
def error_and_raise(exception_class: Exception.__class__, msg: str, base_exception: Exception = None) -> None:
"""Logs the given message at the "error" level and raises the given exception, also with this message.
Args:
exception_class (Exception.__class__): An exception class. See below for built-in exception types.
msg (str): The message to go along with the exception.
Parameters
----------
exception_class: Exception.__class__
An exception class. See below for built-in exception types.
msg: str
The message to go along with the exception.
base_exception: Exception
The exception that caused this code to be called, if any. This will be
added onto the newly created exception_class' history.
Example::
Expand Down Expand Up @@ -411,19 +419,30 @@ def error_and_raise(exception_class: Exception.__class__, msg: str) -> None:
"""
msg = str(msg) # Ensure that message is a string, to enable concatenation.
error(msg)

try:
e = exception_class(msg)
except Exception as exc:
raise RuntimeError(msg) from exc
raise e

if base_exception is not None:
raise e from base_exception
else:
raise e


def critical_and_raise(exception_class: Exception.__class__, msg: str) -> None:
def critical_and_raise(exception_class: Exception.__class__, msg: str, base_exception: Exception = None) -> None:
"""Logs the given message at the "critical" level and raises the given exception, also with this message.
Args:
exception_class (Exception.__class__): An exception class. See error_and_raise() for a description of built-in exceptions.
msg (str): The message to go along with the exception.
Parameters
----------
exception_class: Exception.__class__
An exception class. See error_and_raise() for a description of built-in exceptions.
msg: str
The message to go along with the exception.
base_exception: Exception
The exception that caused this code to be called, if any. This will be
added onto the newly created exception_class' history.
Example::
Expand All @@ -433,11 +452,16 @@ def critical_and_raise(exception_class: Exception.__class__, msg: str) -> None:
"""
msg = str(msg) # Ensure that message is a string, to enable concatenation.
critical(msg)

try:
e = exception_class(msg)
except Exception as exc:
raise RuntimeError(msg) from exc
raise e

if base_exception is not None:
raise e from base_exception
else:
raise e


def log_and_raise_value_error(local_logger, msg) -> None:
Expand All @@ -452,3 +476,52 @@ def log_and_raise_value_error(local_logger, msg) -> None:
"""
error(msg)
raise ValueError(msg)


def log_progress(
percentage: int | float, carriage_return: bool | Literal['auto'] = 'auto', prev_percentage: int = None
):
"""Prints the current progress as a progress bar and number.
Parameters
----------
percentage : int | float
The current progress. If an integer, than the range is clipped to 0-100.
If a float, then the range is clipped to 0-1, unless >1 then it is cast
to an integer.
carriage_return : bool | 'auto', optional
If True, then a carriage return '\r' is printed instead of a newline, which will cause the next line printed to overwrite this line.
This can be used to "draw" the progress interactively in the terminal.
If 'auto', then this will be True when percentage != 100.
By default 'auto'.
prev_percentage: int, optional
If not None, then this is compared to the given percentage. If they are the same then nothing is printed.
Returns
-------
percentage: int
The value printed, in the range 0-100. Can be passed into the next call as prev_percentage.
"""
if isinstance(percentage, int):
percentage = int(np.clip(percentage, 0, 100))
if prev_percentage is not None:
if prev_percentage == percentage:
# don't print again
return percentage

if carriage_return == 'auto':
carriage_return = percentage != 100

sval = "|" + ("=" * percentage) + (" " * (100 - percentage)) + f"| {percentage}%"
if carriage_return:
info(sval, end='\r')
else:
info(sval)

return percentage

else: # isinstance(percentage, float)
if percentage > 1.0:
return log_progress(int(np.round(percentage)), carriage_return, prev_percentage)
else:
return log_progress(int(np.round(percentage * 100)), carriage_return, prev_percentage)

0 comments on commit c891984

Please sign in to comment.