init: tested on hangtuah to cloud
This commit is contained in:
73
utils/cleanup.py
Normal file
73
utils/cleanup.py
Normal file
@@ -0,0 +1,73 @@
|
||||
"""
|
||||
DICOM file cleanup utility to ensure temporary DICOM files are removed even on script termination.
|
||||
"""
|
||||
import os
|
||||
import shutil
|
||||
import atexit
|
||||
import signal
|
||||
from config import settings
|
||||
from utils.logger import main_logger as logger
|
||||
|
||||
# Global registry of directories to clean up
|
||||
_cleanup_dirs = set()
|
||||
|
||||
def register_cleanup_dir(directory):
|
||||
"""
|
||||
Register a directory for cleanup when the script exits.
|
||||
|
||||
Args:
|
||||
directory (str): Path to the directory to be cleaned up
|
||||
"""
|
||||
global _cleanup_dirs
|
||||
if os.path.exists(directory) and os.path.isdir(directory):
|
||||
_cleanup_dirs.add(directory)
|
||||
logger.debug(f"Registered directory for cleanup: {directory}")
|
||||
|
||||
def cleanup_dicom_files():
|
||||
"""
|
||||
Remove all registered DICOM directories.
|
||||
This function is called when the script exits, ensuring cleanup even on abnormal termination.
|
||||
"""
|
||||
global _cleanup_dirs
|
||||
logger.info(f"Running cleanup for {len(_cleanup_dirs)} directories")
|
||||
|
||||
for directory in _cleanup_dirs:
|
||||
try:
|
||||
if os.path.exists(directory):
|
||||
shutil.rmtree(directory)
|
||||
logger.info(f"Cleanup: Removed DICOM files from {directory}")
|
||||
except Exception as e:
|
||||
logger.error(f"Cleanup: Failed to remove DICOM files from {directory}: {str(e)}")
|
||||
|
||||
# Clear the registry after cleanup
|
||||
_cleanup_dirs.clear()
|
||||
|
||||
def register_exit_handlers():
|
||||
"""
|
||||
Register cleanup handlers for various exit scenarios.
|
||||
"""
|
||||
# Register for normal exit
|
||||
atexit.register(cleanup_dicom_files)
|
||||
|
||||
# Register for signals
|
||||
signal.signal(signal.SIGINT, signal_handler) # Ctrl+C
|
||||
signal.signal(signal.SIGTERM, signal_handler) # Termination request
|
||||
|
||||
logger.info("Registered cleanup handlers for script exit")
|
||||
|
||||
def signal_handler(sig, frame):
|
||||
"""
|
||||
Handle termination signals by performing cleanup before exit.
|
||||
|
||||
Args:
|
||||
sig: Signal number
|
||||
frame: Current stack frame
|
||||
"""
|
||||
signal_name = {
|
||||
signal.SIGINT: 'SIGINT (Ctrl+C)',
|
||||
signal.SIGTERM: 'SIGTERM'
|
||||
}.get(sig, f'Signal {sig}')
|
||||
|
||||
logger.info(f"Received {signal_name}, cleaning up...")
|
||||
cleanup_dicom_files()
|
||||
exit(0)
|
||||
86
utils/dicom_utils.py
Normal file
86
utils/dicom_utils.py
Normal file
@@ -0,0 +1,86 @@
|
||||
"""
|
||||
General utility functions for DICOM operations.
|
||||
"""
|
||||
import os
|
||||
import json
|
||||
from datetime import datetime
|
||||
from utils.logger import main_logger as logger
|
||||
|
||||
def create_directory_if_not_exists(directory_path):
|
||||
"""
|
||||
Create directory if it doesn't exist.
|
||||
|
||||
Args:
|
||||
directory_path (str): Path to directory
|
||||
"""
|
||||
if not os.path.exists(directory_path):
|
||||
os.makedirs(directory_path, exist_ok=True)
|
||||
logger.info(f"Created directory: {directory_path}")
|
||||
|
||||
def save_json_data(data, filename, directory):
|
||||
"""
|
||||
Save data as JSON to specified directory.
|
||||
|
||||
Args:
|
||||
data (dict): Data to save
|
||||
filename (str): Filename without path
|
||||
directory (str): Directory to save to
|
||||
|
||||
Returns:
|
||||
str: Full path to saved file
|
||||
"""
|
||||
create_directory_if_not_exists(directory)
|
||||
file_path = os.path.join(directory, filename)
|
||||
|
||||
with open(file_path, 'w') as f:
|
||||
json.dump(data, f, indent=2)
|
||||
|
||||
logger.info(f"Saved JSON data to {file_path}")
|
||||
return file_path
|
||||
|
||||
def parse_dicom_date(date_str):
|
||||
"""
|
||||
Parse DICOM date format (YYYYMMDD) to Python datetime.
|
||||
|
||||
Args:
|
||||
date_str (str): DICOM formatted date string
|
||||
|
||||
Returns:
|
||||
datetime: Python datetime object
|
||||
"""
|
||||
if not date_str or len(date_str) != 8:
|
||||
return None
|
||||
|
||||
try:
|
||||
return datetime.strptime(date_str, '%Y%m%d')
|
||||
except ValueError:
|
||||
logger.error(f"Invalid DICOM date format: {date_str}")
|
||||
return None
|
||||
|
||||
def parse_dicom_datetime(datetime_str):
|
||||
"""
|
||||
Parse DICOM datetime format (YYYYMMDDHHMMSS) to Python datetime.
|
||||
|
||||
Args:
|
||||
datetime_str (str): DICOM formatted datetime string
|
||||
|
||||
Returns:
|
||||
datetime: Python datetime object
|
||||
"""
|
||||
if not datetime_str:
|
||||
return None
|
||||
|
||||
# Handle various DICOM datetime formats
|
||||
try:
|
||||
if len(datetime_str) == 14: # YYYYMMDDHHMMSS
|
||||
return datetime.strptime(datetime_str, '%Y%m%d%H%M%S')
|
||||
elif len(datetime_str) == 12: # YYYYMMDDHHMM
|
||||
return datetime.strptime(datetime_str, '%Y%m%d%H%M')
|
||||
elif len(datetime_str) == 8: # YYYYMMDD
|
||||
return datetime.strptime(datetime_str, '%Y%m%d')
|
||||
else:
|
||||
logger.warning(f"Unexpected DICOM datetime format: {datetime_str}")
|
||||
return None
|
||||
except ValueError:
|
||||
logger.error(f"Invalid DICOM datetime format: {datetime_str}")
|
||||
return None
|
||||
53
utils/error_handler.py
Normal file
53
utils/error_handler.py
Normal file
@@ -0,0 +1,53 @@
|
||||
"""
|
||||
Error handling and retry mechanisms for DICOM operations.
|
||||
"""
|
||||
import time
|
||||
import functools
|
||||
from retry import retry
|
||||
from utils.logger import main_logger as logger
|
||||
from config import settings
|
||||
|
||||
# Define custom exceptions
|
||||
class DicomConnectionError(Exception):
|
||||
"""Error when failing to establish DICOM association."""
|
||||
pass
|
||||
|
||||
class DicomQueryError(Exception):
|
||||
"""Error when C-FIND operation fails."""
|
||||
pass
|
||||
|
||||
class DicomRetrieveError(Exception):
|
||||
"""Error when C-GET operation fails."""
|
||||
pass
|
||||
|
||||
class DicomStoreError(Exception):
|
||||
"""Error when C-STORE operation fails."""
|
||||
pass
|
||||
|
||||
def dicom_retry(exception_types=(Exception,)):
|
||||
"""
|
||||
Decorator to retry DICOM operations with exponential backoff.
|
||||
|
||||
Args:
|
||||
exception_types: Tuple of exception types to retry on.
|
||||
|
||||
Returns:
|
||||
Decorated function with retry logic.
|
||||
"""
|
||||
def decorator(func):
|
||||
@functools.wraps(func)
|
||||
@retry(
|
||||
exceptions=exception_types,
|
||||
tries=settings.MAX_RETRIES,
|
||||
delay=settings.RETRY_DELAY,
|
||||
backoff=2,
|
||||
logger=logger
|
||||
)
|
||||
def wrapper(*args, **kwargs):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except exception_types as e:
|
||||
logger.error(f"Error in {func.__name__}: {str(e)}")
|
||||
raise
|
||||
return wrapper
|
||||
return decorator
|
||||
54
utils/logger.py
Normal file
54
utils/logger.py
Normal file
@@ -0,0 +1,54 @@
|
||||
"""
|
||||
Logging configuration and utility functions.
|
||||
"""
|
||||
import os
|
||||
import logging
|
||||
from logging.handlers import RotatingFileHandler
|
||||
import sys
|
||||
from config import settings
|
||||
|
||||
def setup_logger(name, log_file=None):
|
||||
"""
|
||||
Set up a logger with both console and file handlers.
|
||||
|
||||
Args:
|
||||
name (str): Logger name
|
||||
log_file (str, optional): Log file path. If None, uses the default from settings.
|
||||
|
||||
Returns:
|
||||
logging.Logger: Configured logger
|
||||
"""
|
||||
if log_file is None:
|
||||
log_file = settings.LOG_FILE
|
||||
|
||||
# Create the logs directory if it doesn't exist
|
||||
os.makedirs(os.path.dirname(log_file), exist_ok=True)
|
||||
|
||||
logger = logging.getLogger(name)
|
||||
logger.setLevel(getattr(logging, settings.LOG_LEVEL))
|
||||
|
||||
# Create formatter
|
||||
formatter = logging.Formatter(settings.LOG_FORMAT, datefmt='%Y-%m-%d %H:%M:%S')
|
||||
|
||||
# Create console handler
|
||||
console_handler = logging.StreamHandler(sys.stdout)
|
||||
console_handler.setLevel(getattr(logging, settings.LOG_LEVEL))
|
||||
console_handler.setFormatter(formatter)
|
||||
|
||||
# Create file handler
|
||||
file_handler = RotatingFileHandler(
|
||||
log_file,
|
||||
maxBytes=settings.MAX_LOG_SIZE,
|
||||
backupCount=settings.BACKUP_COUNT
|
||||
)
|
||||
file_handler.setLevel(getattr(logging, settings.LOG_LEVEL))
|
||||
file_handler.setFormatter(formatter)
|
||||
|
||||
# Add handlers to logger
|
||||
logger.addHandler(console_handler)
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
return logger
|
||||
|
||||
# Create main application logger
|
||||
main_logger = setup_logger("LOG")
|
||||
Reference in New Issue
Block a user