"""
IO module for the NICE toolbox.
"""
import copy
import logging
import os
from pathlib import Path
from ..utils import check_and_exception as exc
from ..utils import system as oslab_sys
[docs]class IO:
"""
The IO class handles input/output operations and folder management for the
NICE toolbox.
Args:
config (dict): A dictionary containing the configuration parameters.
Attributes:
out_folder (str): The path to the output folder.
log_level (str): The log level.
out_sub_folder (str): The path to the output sub-folder.
data_folder (str): The path to the data folder.
data_input_folder (str): The path to the input data folder.
calibration_file (str): The path to the calibration file.
conda_path (str): The path to the Conda installation directory.
algorithm_names (list): A list of algorithm names.
detector_out_folder (str): The path to the detector output folder.
detector_visualization_folder (str): The path to the detector visualization
folder.
detector_additional_output_folder (str): The path to the detector additional
output folder.
detector_run_config_path (str): The path to the detector run configuration file.
detector_final_result_folder (str): The path to the detector final result
folder.
"""
def __init__(self, config):
"""
Initializes IO by creating the output folder and setting the log level.
Args:
config (dict): A dictionary containing configuration parameters.
Raises:
OSError: If there is an error creating the output folder or the base data
folder.
"""
# create folders
self.out_folder = config["out_folder"]
try:
os.makedirs(self.out_folder, exist_ok=True)
except OSError:
logging.exception("Failed creating the output folder.")
raise
try:
os.makedirs(os.path.dirname(config["data_folder"]), exist_ok=True)
except OSError:
logging.exception(f"Failed creating the base data folder {config['data_folder']}.")
raise
self.log_level = config["log_level"]
self.code_folder = Path(config["code_folder"])
[docs] def get_log_file_level(self):
"""
Returns the path of the log file and the log level.
Returns:
tuple: A tuple containing the path of the log file and the log level.
"""
return os.path.join(self.out_folder, "nicetoolbox.log"), self.log_level
[docs] def get_config_file(self):
"""
Returns the path of the configuration file.
Returns:
str: The path of the configuration file.
"""
return self.out_folder
[docs] def initialization(self, config, algorithm_names):
"""
Initializes the necessary variables and folders for data processing.
Args:
config (dict): A dictionary containing the configuration parameters.
algorithm_names (list): A list of algorithm names.
Returns:
None
"""
self.out_sub_folder = config["out_sub_folder"]
if config["process_data_to"] == "data_folder":
self.nice_input_folder = Path(config["data_folder"])
self.create_folders()
# check the given io-config
self.check_config(config)
# get the relevant config entries
self.data_source_folder = Path(config["data_input_folder"])
self.calibration_file = config["path_to_calibrations"]
self.conda_path = config["conda_path"]
self.csv_folder = config["csv_out_folder"]
self.algorithm_names = algorithm_names
self.detector_out_folder = config["detector_out_folder"]
self.detector_visualization_folder = config["detector_visualization_folder"]
self.detector_additional_output_folder = config["detector_additional_output_folder"]
self.detector_run_config_path = config["detector_run_config_path"]
self.detector_final_result_folder = config["detector_final_result_folder"]
[docs] def get_data_source_folder(self) -> Path:
"""
Returns the folder path to the original dataset source data. (E.g. storing mp4/avi files)
Returns:
str: The path to the source data folder.
"""
return self.data_source_folder
[docs] def get_calibration_file(self):
"""
Returns the calibration file path.
Returns:
str: The path of the calibration file.
"""
return self.calibration_file
[docs] def get_conda_path(self):
"""
Returns the path to the Conda installation directory.
Returns:
str: The path to the Conda installation directory.
"""
return self.conda_path
[docs] def get_inference_path(self, component_name, detector_name):
"""
Get the file path for the inference script of a given detector.
Args:
detector_name (str): The name of the detector.
Returns:
str: The file path for the inference script.
Raises:
FileNotFoundError: If the inference script file does not exist.
"""
filepath = os.path.join(
self.code_folder,
"nicetoolbox",
"detectors",
"method_detectors",
component_name,
f"{detector_name}_inference.py",
)
try:
exc.file_exists(filepath)
except FileNotFoundError:
logging.exception(f"Detector inference file {filepath} does not exist!")
raise
return filepath
[docs] def get_venv_path(self, detector_name, env_name):
"""
Get the file path of the virtual environment for the given detector and
environment name.
Args:
detector_name (str): The name of the detector.
env_name (str): The name of the environment.
Returns:
str: The file path of the virtual environment.
Raises:
FileNotFoundError: If the virtual environment does not exist.
"""
os_type = oslab_sys.detect_os_type()
if os_type == "linux":
filepath = os.path.join(self.code_folder, "envs", env_name, "bin/activate")
elif os_type == "windows":
filepath = os.path.join(self.code_folder, "envs", env_name, "Scripts", "activate")
try:
exc.file_exists(filepath)
except FileNotFoundError:
logging.exception(
f"Virtual environment file {filepath} for detector = " f"'{detector_name}' does not exist!"
)
raise
return filepath
[docs] def get_output_folder(self, token):
"""
Returns the output folder based on the given name and token.
Args:
token (str): The token specifying the type of output folder
('tmp', 'output', 'main', or 'csv').
Returns:
str: The path to the output folder.
Raises:
NotImplementedError: If the token is not 'tmp', 'output', 'main', or 'csv'.
"""
if token == "output":
return self.out_sub_folder
if token == "main":
return self.out_folder
if token == "csv":
os.makedirs(self.csv_folder, exist_ok=True)
return self.csv_folder
raise NotImplementedError(
f"IO return output folder: Token '{token}' unknown! " f"Supported are 'tmp', 'output', 'main', 'csv'."
)
[docs] def get_detector_output_folder(self, component, algorithm, token):
"""
Get the output folder path for a specific component, algorithm, and token.
Args:
component (str): The name of the component.
algorithm (str): The name of the algorithm.
token (str): The token indicating the type of output folder.
Returns:
str: The path of the output folder.
Raises:
NotImplementedError: If the algorithm or token is not supported.
"""
if not any([m_name in algorithm for m_name in self.algorithm_names]):
raise NotImplementedError(
f"IO return component output folder: Algorithm '{algorithm}' unknown! "
f"Supported are: {self.algorithm_names}."
)
if token == "output":
folder_name = copy.deepcopy(self.detector_out_folder)
elif token == "visualization":
folder_name = copy.deepcopy(self.detector_visualization_folder)
elif token == "additional":
folder_name = copy.deepcopy(self.detector_additional_output_folder)
elif token == "result":
folder_name = copy.deepcopy(self.detector_final_result_folder)
elif token == "run_config":
folder_name = copy.deepcopy(self.detector_run_config_path)
else:
raise NotImplementedError(
f"IO return output folder: Token '{token}' unknown! "
f"Supported are 'output', 'visualization', "
f"'additional', 'tmp', 'result'."
)
folder_name = folder_name.replace("<cur_component_name>", component).replace("<cur_algorithm_name>", algorithm)
os.makedirs(folder_name, exist_ok=True)
return folder_name
[docs] def create_folders(self):
"""
Creates the necessary output and data folders.
This method creates the output folder and data folder if they don't already
exist. If the folders cannot be created, an exception is raised.
Raises:
OSError: If the output folder or data folder cannot be created.
"""
# create output folders
try:
os.makedirs(self.out_sub_folder, exist_ok=True)
except OSError:
logging.exception("Failed creating the output folder.")
raise
# create the data folders
try:
os.makedirs(self.nice_input_folder, exist_ok=True)
except OSError:
logging.exception("Failed creating the data folder.")
raise
[docs] def check_config(self, config):
"""
Check the validity of the given configuration.
Args:
config (dict): The configuration dictionary.
Raises:
TypeError: If the 'process_data_to' value is not a string.
ValueError: If the 'process_data_to' value is not 'data_folder'.
ValueError: If any of the detector input folders are invalid.
OSError: If any of the detector input folders are not accessible.
"""
# check the config['process_data_to'] input
try:
exc.check_options(config["process_data_to"], str, ["data_folder"])
except (TypeError, ValueError):
logging.exception("Unsupported 'process_data_to' in io. " "Valid options: 'data_folder'.")
raise
# check all given detector output folders
def check_base_folder(folder_name, token, description):
try:
exc.check_token_in_filepath(folder_name, token, description)
except ValueError:
logging.exception("The given detector input folder is invalid.")
raise
base = folder_name.split(token)[0][:-1]
try:
_ = sorted(os.listdir(base))
except OSError:
logging.exception(f"'{base}' is not an accessible directory.")
raise
check_base_folder(config["detector_out_folder"], "<cur_component_name>", "detector_out_folder")
check_base_folder(
config["detector_visualization_folder"],
"<cur_component_name>",
"detector_visualization_folder",
)
check_base_folder(
config["detector_additional_output_folder"],
"<cur_component_name>",
"detector_additional_output_folder",
)
check_base_folder(
config["detector_final_result_folder"],
"<cur_component_name>",
"detector_final_result_folder",
)