view Sphinx/source/plugins/python/storage-area-3.py @ 1298:bf8c8edded61 default tip

added screenshots
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 10 Sep 2026 07:55:36 +0200
parents a737d4cca0d3
children
line wrap: on
line source

# Compared to the default Orthanc storage, this storage plugin 
# uses a flat folder structure but prefixes all file paths with 
# the PatientID.  This is a sample only and should not be used 
# in production.

import orthanc
from typing import Tuple, Optional
import json
import os
import re

orthanc_full_configuration = json.loads(orthanc.GetConfiguration())

root_storage_directory = orthanc_full_configuration.get('StorageDirectory', orthanc_full_configuration.get('IndexDirectory', os.curdir))

orthanc.LogWarning(f"Python Storage: root directory: {root_storage_directory}")

def normalize_filename(s: str, replacement_char: str = "_") -> str:
    # Replace invalid characters with the replacement character
    s = re.sub(r'[\\/*?:"<>|]', replacement_char, s)
    # Replace spaces with the replacement character
    s = re.sub(r'\s+', replacement_char, s)
    # Remove leading/trailing replacement characters
    s = s.strip(replacement_char)
    # Ensure the string is not empty
    if not s:
        s = "undefined"
    return s

def get_relative_path(patient_id: str, uuid: str, content_type: orthanc.ContentType) -> str:
    return f"{normalize_filename(patient_id)} - {uuid}{get_extension(content_type)}"

def get_absolute_path(relative_path: str) -> str:
    return os.path.join(root_storage_directory, relative_path)

def get_extension(content_type: orthanc.ContentType):
    if content_type == orthanc.ContentType.DICOM:
        return ".dcm"
    elif content_type == orthanc.ContentType.DICOM_AS_JSON:
        return ".json"
    elif content_type == orthanc.ContentType.DICOM_UNTIL_PIXEL_DATA:
        return ".dcm.head"
    else:
        return ".unk"

def StorageCreate(uuid: str, 
                  content_type: orthanc.ContentType, 
                  compression_type: orthanc.CompressionType, 
                  content: bytes, 
                  dicom_instance: orthanc.DicomInstance) -> Tuple[orthanc.ErrorCode, Optional[bytes]]:
    # orthanc.LogInfo("---- StorageCreate")
    
    instance_tags = json.loads(dicom_instance.GetInstanceJson())
    patient_id = instance_tags["0010,0010"]["Value"]
    try:
        relative_path = get_relative_path(patient_id, uuid, content_type)
        path = get_absolute_path(relative_path)
        with open(path, "wb") as f:
            f.write(content)
        
        # orthanc.LogInfo(f"Successfully created file {path} for {uuid}, wrote {len(content)} bytes")
        str_custom_data = relative_path

        return orthanc.ErrorCode.SUCCESS, str_custom_data.encode('utf-8')
    except IOError as e:
        orthanc.LogError(f"Failed to create storage for {uuid}: {str(e)}")
        return orthanc.ErrorCode.PLUGIN, None
    except Exception as e:
        orthanc.LogError(f"Unexpected error creating storage for {uuid}: {str(e)}")
        return orthanc.ErrorCode.PLUGIN, None

def StorageReadRange(uuid: str, 
                     content_type: orthanc.ContentType, 
                     range_start: int,
                     size: int,
                     custom_data: bytes) -> Tuple[orthanc.ErrorCode, Optional[bytes]]:
    # orthanc.LogInfo("---- StorageReadRange")
    str_custom_data = custom_data.decode('utf-8')
    # orthanc.LogInfo(f"---- CustomData: {str_custom_data}")
    try:
        relative_path = str_custom_data
        path = get_absolute_path(relative_path)

        with open(path, "rb") as f:
            # Move to the start position
            f.seek(range_start)
            # Read the specified number of bytes
            data = f.read(size)
            
        # orthanc.LogInfo(f"Read {len(data)} bytes from position {range_start}")
        return orthanc.ErrorCode.SUCCESS, data
    except FileNotFoundError:
        orthanc.LogError(f"File {path} not found for {uuid}")
        return orthanc.ErrorCode.UNKNOWN_RESOURCE, None
    except Exception as e:
        orthanc.LogError(f"Error reading file {path} for {uuid}: {str(e)}")
        return orthanc.ErrorCode.PLUGIN, None

def StorageRemove(uuid: str,
                  content_type: orthanc.ContentType,
                  custom_data: bytes) -> orthanc.ErrorCode:
    # orthanc.LogInfo("---- StorageRemove")
    
    str_custom_data = custom_data.decode('utf-8')
    # orthanc.LogInfo(f"---- CustomData: {str_custom_data}")
    try:
        relative_path = str_custom_data
        path = get_absolute_path(relative_path)
        if os.path.exists(path):
            os.remove(path)
            # orthanc.LogInfo(f"Successfully removed file {path} for {uuid}")
        else:
            orthanc.LogWarning(f"Storage file not found for removal: {path}")
        
        return orthanc.ErrorCode.SUCCESS
    except IOError as e:
        orthanc.LogError(f"Failed to remove {path} for {uuid}: {str(e)}")
        return orthanc.ErrorCode.PLUGIN
    except Exception as e:
        orthanc.LogError(f"Unexpected error removing {path} for {uuid}: {str(e)}")
        return orthanc.ErrorCode.PLUGIN


orthanc.RegisterStorageArea3(StorageCreate, StorageReadRange, StorageRemove)