import orthanc import csv import json from datetime import date, datetime from pathlib import Path BASE_DIR = Path("/var/lib/orthanc") ORTHANC_TRANSFERS_CSV_PATH = BASE_DIR / "transfers/transfers.csv" ##------------------------------ ## AutoRoute Function ##------------------------------ def AutoRouteToModality(modality: str, resourceId: str) -> bool: isRouteSuccessful = False try: orthanc.RestApiPost(f"/modalities/{modality}/store", resourceId) # Create COMPLETED label orthanc.RestApiPut(f"/studies/{resourceId}/labels/--COMPLETED", "{}") isRouteSuccessful = True except: # Exceptions output to log files by default in Orthanc # Create FAILED label orthanc.RestApiPut(f"/studies/{resourceId}/labels/--FAILED", "{}") finally: # Delete PENDING label orthanc.RestApiDelete(f"/studies/{resourceId}/labels/--PENDING") return isRouteSuccessful ##------------------------------ ## Onchange: Label and Autoroute ##------------------------------ ##------------------------------ ## Check for Stable Study updates and check DICOM metadata ##------------------------------ def OnChange(changeType: int, level: int, resourceId: str) -> None: if changeType == orthanc.ChangeType.STABLE_STUDY: studyMetadata = json.loads(orthanc.RestApiGet(f"/studies/{resourceId}")) firstInstanceId = json.loads(orthanc.RestApiGet(f"/studies/{resourceId}/instances"))[0]['ID'] firstInstanceMetadata = json.loads(orthanc.RestApiGet(f"/instances/{firstInstanceId}/metadata?expand")) ##------------------------------ ## Verbose Log Notification ##------------------------------ orthanc.LogInfo(f"[STABLE STUDY RECEIVED] {studyMetadata}") ##------------------------------ ## Recording DICOM metadata ##------------------------------ ##------------------------------ ## Required metadata ##------------------------------ if firstInstanceMetadata['Origin'] == "DicomProtocol": today = date.today().isoformat() calledAET = firstInstanceMetadata['CalledAET'].upper() remoteAET = firstInstanceMetadata['RemoteAET'] remoteIP = firstInstanceMetadata['RemoteIP'] patientName = studyMetadata['PatientMainDicomTags']['PatientName'] accession = studyMetadata['MainDicomTags']['AccessionNumber'] studyInstanceUID = studyMetadata['MainDicomTags']['StudyInstanceUID'] studyDate = studyMetadata['MainDicomTags']['StudyDate'] dob = studyMetadata['PatientMainDicomTags']['PatientBirthDate'] ##------------------------------ ## Optional Data ##------------------------------ #This section defines the DICOM metadata that is either optional or has failed as the metadata is not included by some Sites #Study Description try: studyDescription = studyMetadata['MainDicomTags']['StudyDescription'] except KeyError: studyDescription = "UNKNOWN" #InstituitionName try: institutionName = studyMetadata['MainDicomTags']['InstitutionName'] except KeyError: institutionName = "UNKNOWN" ##------------------------------ ## Write to CSV - Input ##------------------------------ Input_data_to_append = ['Received', today, calledAET, remoteAET, remoteIP, patientName, accession, studyInstanceUID, studyDate, dob, studyDescription, institutionName] csvfile = open(ORTHANC_TRANSFERS_CSV_PATH,'a',newline='') writer = csv.writer(csvfile) writer.writerows([Input_data_to_append]) csvfile.close() ##------------------------------ ## Labelling Sites and transfer Date ##------------------------------ orthanc.RestApiPut(f"/studies/{resourceId}/labels/--PENDING", "{}") orthanc.RestApiPut(f"/studies/{resourceId}/labels/FROM_{remoteAET}", "{}") orthanc.RestApiPut(f"/studies/{resourceId}/labels/{calledAET}", "{}") orthanc.RestApiPut(f"/studies/{resourceId}/labels/{today}", "{}") ##------------------------------ ## Set Routing Flags ##------------------------------ # Has Autoroute been completed? success = False # Autoroute (Default True) routingRequired = True ##------------------------------ ## Destination List ##------------------------------ match calledAET: ##------------------------------ ## Current Implementation ##------------------------------ ##------------------------------ ## Facility 1 ##------------------------------ case "PACSMODALITY1": success = AutoRouteToModality("Dest1", resourceId) case "PACSMODALITY2": success = AutoRouteToModality("Dest2", resourceId) case "PACSMODALITY3": success = AutoRouteToModality("Dest3", resourceId) case "PACSMODALITY4": success = AutoRouteToModality("Dest4", resourceId) ##------------------------------ ##Intended for Orthanc ##------------------------------ case "ORTHANC": success = True routingRequired = False # Delete PENDING label orthanc.RestApiDelete(f"/studies/{resourceId}/labels/--PENDING") # Create COMPLETED label orthanc.RestApiPut(f"/studies/{resourceId}/labels/--COMPLETED", "{}") # Create ACTION label orthanc.RestApiPut(f"/studies/{resourceId}/labels/---ACTION", "{}") ##------------------------------ ## No Match - Do not AutoRoute ##------------------------------ case _: # Delete PENDING label orthanc.RestApiDelete(f"/studies/{resourceId}/labels/--PENDING") # Create INVALID_CALLED_AET label orthanc.RestApiPut(f"/studies/{resourceId}/labels/--INVALID_CALLED_AET", "{}") # Create FAILED label orthanc.RestApiPut(f"/studies/{resourceId}/labels/--FAILED", "{}") orthanc.LogWarning(f"[AUTO-ROUTE JOB FAILURE] Called AET: {calledAET} was improperly specified, auto-route job was not created as destination could not be determined") ##------------------------------ ## Log Outcome ##------------------------------ if success: if routingRequired == True: orthanc.LogWarning(f"[AUTO-ROUTE JOB COMPLETED] Accession {accession} with Study Instance UID {studyInstanceUID} for Patient {patientName} auto-route job created from {remoteAET}:{remoteIP} to {calledAET}") Input_data_to_append = ['Completed', today, calledAET, remoteAET, remoteIP, patientName, accession, studyInstanceUID, studyDate, dob, studyDescription, institutionName] csvfile = open(ORTHANC_TRANSFERS_CSV_PATH,'a',newline='') writer = csv.writer(csvfile) writer.writerows([Input_data_to_append]) csvfile.close() else: orthanc.LogWarning(f"[AUTO-ROUTE JOB NOT REQUIRED SENT TO ORTHANC ONLY] Accession {accession} with Study Instance UID {studyInstanceUID} for Patient {patientName} received from {remoteAET}:{remoteIP} to {calledAET}") Input_data_to_append = ['Failed', today, calledAET, remoteAET, remoteIP, patientName, accession, studyInstanceUID, studyDate, dob, studyDescription, institutionName] csvfile = open(ORTHANC_TRANSFERS_CSV_PATH,'a',newline='') writer = csv.writer(csvfile) writer.writerows([Input_data_to_append]) csvfile.close() else: # When not successful orthanc.LogWarning(f"[AUTO-ROUTE JOB FAILED] ERROR: Accession {accession} with Study Instance UID {studyInstanceUID} for Patient {patientName} auto-route job FAILED from {remoteAET}:{remoteIP} to {calledAET}") orthanc.RegisterOnChangeCallback(OnChange) ##------------------------------