init: tested on hangtuah to cloud

This commit is contained in:
mario
2025-05-08 12:07:19 +07:00
parent 4e95512339
commit dfe0c18be2
12 changed files with 1992 additions and 1 deletions

73
utils/cleanup.py Normal file
View 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
View 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
View 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
View 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")