Appearance
Control Flow Nodes
Control flow nodes manage job execution and state within workflows.
Control Node Types
- Perform - Execute an action
- Suspend - Suspend job for manual intervention
- Discard - Discard job and remove from queue
- Update - Update workflow state
- Resume - Resume suspended jobs
Perform Nodes
Perform nodes execute actions defined in the <Actions> section.
Basic Syntax
xml
<Perform action="ActionName"/>Attributes
action (Required)
The name of the action to execute.
xml
<Perform action="QueryWorklist"/>onError (Optional)
Controls how the workflow handles errors when the action fails.
Valid Values:
Hold- Stop processing and hold the job (files retained for retry) (default)Ignore- Log the error but continue processingSuspend- Pause the job for manual intervention with resume capabilityDiscard- Terminate the job and delete all files
Default: Hold
xml
<!-- Hold on error (default) - job stops, files retained -->
<Perform action="SendToPACS"/>
<!-- Ignore errors - continue even if action fails -->
<Perform action="OptionalNotification" onError="Ignore"/>
<!-- Suspend on error - allow manual intervention and resume -->
<Perform action="QueryWorklist" onError="Suspend"/>
<!-- Discard on error - terminate job completely -->
<Perform action="ValidateRequired" onError="Discard"/>Error Handling Examples:
xml
<!-- Critical action - must succeed -->
<Perform action="SendToPACS" onError="Hold"/>
<!-- Optional notification - don't stop on failure -->
<Perform action="SendEmail" onError="Ignore"/>
<!-- Query failure - allow retry after fixing data -->
<Perform action="QueryWorklist" onError="Suspend"/>
<!-- Invalid data - discard immediately -->
<Perform action="ValidatePatientID" onError="Discard"/>Example: Sequential Actions
xml
<Workflow>
<Perform action="ParseFilename"/>
<Perform action="QueryWorklist"/>
<Perform action="SetMetadata"/>
<Perform action="SendToPACS"/>
</Workflow>Actions execute in the order specified.
Example: Conditional Execution
xml
<Workflow>
<Perform action="QueryWorklist"/>
<If field="QUERY_FOUND" value="true">
<Perform action="SetMetadata"/>
<Perform action="SendToPACS"/>
</If>
</Workflow>Actions inside conditionals only execute when the condition is true.
Example: Multiple Actions
xml
<Workflow>
<Perform action="TrimImage"/>
<Perform action="RotateImage"/>
<Perform action="ResizeImage"/>
<Perform action="SaveToArchive"/>
<Perform action="SendToPACS"/>
<Perform action="PrintToFilm"/>
</Workflow>Nested Query Configuration
Perform nodes can contain nested Query elements to override action parameters for specific workflow executions. This allows reusing a Query action with different parameters.
xml
<Actions>
<!-- Define base query action -->
<Query name="QueryWorklist" type="Worklist"
calledAE="RIS" callingAE="PRINTER"
host="192.168.1.200" port="104">
<DcmTag tag="0010,0020">#{PatientID}</DcmTag>
</Query>
</Actions>
<Workflow>
<!-- Use base query with additional parameter -->
<Perform action="QueryWorklist">
<Query>
<DcmTag tag="0010,0030">#{BirthDate}</DcmTag>
</Query>
</Perform>
</Workflow>The nested Query element creates a temporary clone of the action with the specified parameters merged in, allowing workflow-specific customization without defining multiple similar actions.
Supported Nested Configurations:
- WorklistQuery
- StudyQuery
- PatientQuery
Suspend Nodes
Suspend nodes pause job processing, requiring manual intervention to resume.
Basic Syntax
xml
<Suspend/>When to Use Suspend
Use suspend nodes when:
- Manual review is required
- Data validation fails
- Patient matching is ambiguous
- Processing cannot continue automatically
- Quality control checks are needed
Attributes
The maxRetries attribute limits how many times a job can be suspended before falling through to the next workflow node.
| Attribute | Required | Description |
|---|---|---|
resumeAction | No | Action to re-execute when the job is resumed |
maxRetries | No | Maximum number of suspend/resume cycles. Default: unlimited (-1). When exhausted, the node falls through instead of suspending. |
Example:
xml
<Suspend resumeAction="RetryWorklist" maxRetries="5"/>Retry Tracking (.meta files)
When maxRetries is set, the system tracks retry count in a JSON companion file stored alongside the job:
json
{"retries": 3, "lastAttempt": "2026-03-15T14:30:00"}The .meta file is:
- Created on the first retry attempt
- Updated each time the job is suspended again
- Deleted when a
ManualQueryaction re-parks the job (giving it a fresh retry budget)
Example: No Patient Match
xml
<Workflow>
<Perform action="QueryWorklist"/>
<If field="QUERY_FOUND" value="false">
<!-- No patient match - manual review needed -->
<Suspend/>
</If>
</Workflow>Example: Multiple Matches
xml
<Workflow>
<Perform action="QueryWorklist"/>
<If field="QUERY_PARTIAL" value="true">
<!-- Multiple matches - user must select -->
<Perform action="NotifyMultipleMatches"/>
<Suspend/>
</If>
</Workflow>Example: Missing Required Data
xml
<Workflow>
<Perform action="ParseFilename"/>
<If field="TAG_VALUE(0010,0020)" value="^$">
<!-- No patient ID - cannot process -->
<Suspend/>
</If>
</Workflow>Example: Validation Failure
xml
<Workflow>
<Perform action="ValidateData"/>
<If field="VALIDATION_FAILED" value="true">
<!-- Data validation failed -->
<Perform action="NotifyValidationFailure"/>
<Suspend/>
</If>
</Workflow>Resuming Suspended Jobs
Suspended jobs remain in the queue and can be resumed:
- Manually through the Control Application
- Automatically after fixing the issue (e.g., adding patient to worklist)
- By modifying the job data and resuming
Resume Mechanics
When a job is resumed, the workflow execution follows these steps:
- Workflow restarts from the beginning - The entire workflow is re-executed from the first node
- Actions are skipped until resume point - All Perform nodes before the resume action are skipped
- Resume action executes - When the workflow reaches the action specified in
resumeActionattribute of the Suspend node:- The resumed flag is cleared
- The resume action executes normally
- Normal execution continues - All subsequent nodes after the resume action execute normally
Example with resume flow:
xml
<Actions>
<ParseJobFileName name="ParseID"><!-- config --></ParseJobFileName>
<Query name="QueryWorklist"><!-- config --></Query>
<SetTag name="AddMetadata"><!-- config --></SetTag>
<Store name="SendToPACS"><!-- config --></Store>
</Actions>
<Workflow>
<Perform action="ParseID"/> <!-- Step 1: Executes on first run, skipped on resume -->
<Perform action="QueryWorklist"/> <!-- Step 2: Executes on first run, THIS IS RESUME POINT -->
<If field="QUERY_FOUND" value="false">
<Suspend resumeAction="QueryWorklist"/> <!-- Suspends here, sets resume to QueryWorklist -->
</If>
<Perform action="AddMetadata"/> <!-- Step 3: Only executes after successful resume -->
<Perform action="SendToPACS"/> <!-- Step 4: Only executes after successful resume -->
</Workflow>Resume flow:
- Job runs, ParseID executes
- QueryWorklist executes, finds no match
- Job suspends at Suspend node with
resumeAction="QueryWorklist" - User adds patient to worklist and resumes job
- Workflow restarts: ParseID is skipped (already executed)
- QueryWorklist executes again (resume point), now finds match
- AddMetadata executes normally
- SendToPACS executes normally
Important Notes:
- Suspend nodes require
resumeActionattribute specifying which action to resume from - Resumed jobs cannot be suspended again by Suspend nodes (prevents infinite loops)
- Resumed jobs cannot be discarded by Discard nodes (job has been manually approved)
Discard Nodes
Discard nodes remove jobs from the queue immediately.
Basic Syntax
xml
<Discard/>When to Use Discard
Use discard nodes when:
- Job is invalid and cannot be processed
- Data is corrupt or incomplete
- Job does not meet minimum requirements
- Processing is not possible or appropriate
Example: No Patient ID
xml
<Workflow>
<Perform action="ParseFilename"/>
<If field="TAG_VALUE(0010,0020)" value="^$">
<!-- No patient ID - cannot process -->
<Discard/>
</If>
</Workflow>Example: Invalid File Format
xml
<Workflow>
<If field="PRINTED_FILE_NAME" value=".txt">
<!-- Text files not supported -->
<Discard/>
</If>
<Perform action="ProcessDocument"/>
</Workflow>Example: Test Jobs
xml
<Workflow>
<If field="TAG_VALUE(0010,0010)" value="^TEST\^PATIENT$">
<!-- Test patient - discard -->
<Discard/>
</If>
<Perform action="SendToPACS"/>
</Workflow>Difference: Suspend vs Discard
Suspend:
- Job remains in queue
- Can be resumed later
- Use when issue might be resolvable
- Requires manual intervention
Discard:
- Job is permanently removed
- Cannot be recovered
- Use when job is invalid or unprocessable
- No manual intervention possible
Update Nodes
Update nodes modify action configuration during workflow execution.
Basic Syntax
xml
<Update action="ActionName" type="ActionType">
<!-- Action-specific configuration -->
</Update>Supported Action Types:
print- Update Print action configuration
Note: Currently, only Print actions are supported for Update nodes. Other action types will cause an error.
Example: Update Print Settings
xml
<Actions>
<Print name="PrintToFilm"
calledAE="FILM_PRINTER" callingAE="PRINTER"
host="192.168.1.50" port="104">
<BasicFilmSessionAttributes>
<NumberOfCopies>1</NumberOfCopies>
</BasicFilmSessionAttributes>
</Print>
</Actions>
<Workflow>
<!-- Update print action to print 3 copies for STAT jobs -->
<If field="CLIENT_HOST_NAME" value="STAT-WORKSTATION">
<Update action="PrintToFilm" type="print">
<BasicFilmSessionAttributes>
<NumberOfCopies>3</NumberOfCopies>
</BasicFilmSessionAttributes>
</Update>
</If>
<Perform action="PrintToFilm"/>
</Workflow>Update nodes allow dynamic action configuration changes that are applied during workflow execution.
Resume Actions
Resume actions are special actions that can resume suspended jobs programmatically.
Example: Automatic Resume After Time
xml
<Actions>
<!-- This action might be triggered externally -->
<Resume name="ResumeAfterDelay"/>
</Actions>Resume actions are typically triggered:
- By external systems
- After scheduled tasks complete
- When data becomes available
- Through API calls
Common Control Flow Patterns
Validate-Process-Route
xml
<Workflow>
<!-- Validate -->
<Perform action="ParseFilename"/>
<If field="TAG_VALUE(0010,0020)" value="^$">
<Discard/>
</If>
<!-- Process -->
<Perform action="QueryWorklist"/>
<If field="QUERY_FOUND" value="false">
<Suspend/>
</If>
<!-- Route -->
<Perform action="SetMetadata"/>
<Perform action="SendToPACS"/>
</Workflow>Try-Fallback-Suspend
xml
<Workflow>
<!-- Try primary -->
<Perform action="SendToPrimaryPACS"/>
<If field="STORE_SUCCEEDED" tag="SendToPrimaryPACS" value="false">
<!-- Try fallback -->
<Perform action="SendToBackupPACS"/>
<If field="STORE_SUCCEEDED" tag="SendToBackupPACS" value="false">
<!-- Both failed - suspend -->
<Perform action="NotifyFailure"/>
<Suspend/>
</If>
</If>
</Workflow>Process-Validate-Retry
xml
<Workflow>
<Perform action="ProcessData"/>
<Perform action="ValidateResult"/>
<If field="VALIDATION_PASSED" value="false">
<Update field="RETRY_COUNT" value="1"/>
<If field="RETRY_COUNT" value="3">
<!-- Max retries - suspend -->
<Suspend/>
</If>
<!-- Retry processing -->
<Perform action="ProcessData"/>
</If>
</Workflow>Auto-Retry with ManualQuery Fallback
xml
<Perform action="AutoWorklist" onError="Ignore"/>
<If field="QUERY_FOUND" value="false">
<Suspend resumeAction="RetryWorklist" maxRetries="3"/>
<!-- Falls through here after 3 failed retries -->
<Perform action="ParkForManualMatch"/>
</If>Where ParkForManualMatch is a ManualQuery action that moves the job to queue/manual/ for manual matching via the Queue Dashboard. After three unsuccessful suspend/resume cycles, the Suspend node falls through and ParkForManualMatch executes, giving operators a way to resolve the job manually.
Conditional Routing with Fallback
xml
<Workflow>
<Perform action="QueryWorklist"/>
<If field="QUERY_FOUND" value="true">
<!-- Normal path -->
<Perform action="SendToModalityPACS"/>
</If>
<If field="QUERY_FOUND" value="false">
<!-- Check if test patient -->
<If field="TAG_VALUE(0010,0020)" value="^TEST">
<!-- Discard test -->
<Discard/>
</If>
<!-- Not test - suspend for review -->
<Suspend resumeAction="QueryWorklist"/>
</If>
</Workflow>Multi-Destination with Error Handling
xml
<Workflow>
<Perform action="QueryWorklist"/>
<If field="QUERY_FOUND" value="true">
<!-- Send to multiple destinations -->
<Perform action="SendToPrimaryPACS"/>
<Perform action="SendToBackupPACS"/>
<Perform action="SaveToArchive"/>
<!-- Check if at least one succeeded -->
<If field="STORE_SUCCEEDED" tag="SendToPrimaryPACS" value="false">
<If field="STORE_SUCCEEDED" tag="SendToBackupPACS" value="false">
<!-- Both stores failed - suspend -->
<Perform action="NotifyStorageFailure"/>
<Suspend/>
</If>
</If>
</If>
</Workflow>Error Handling Best Practices
- Always handle failure cases - Don't leave conditional paths incomplete
- Suspend over Discard - Suspend when in doubt; discarded jobs are lost
- Notify before Suspend - Send alerts before suspending for visibility
- Use meaningful state - Update node values should be descriptive
- Document decision points - Use XML comments for complex logic
- Test all paths - Ensure every workflow path has been tested
Complete Example
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE DicomPrinter SYSTEM "config.dtd">
<DicomPrinter>
<Actions>
<!-- Data extraction -->
<ParseJobFileName name="ExtractPatientID">
<Pattern>(\d+)_.*\.pdf</Pattern>
<DcmTag tag="0010,0020" group="1"/>
</ParseJobFileName>
<!-- Patient lookup -->
<Query name="FindWorklist" type="Worklist"
calledAE="RIS" callingAE="PRINTER"
host="192.168.1.200" port="104">
<DcmTag tag="0010,0020">#{PatientID}</DcmTag>
</Query>
<!-- Metadata -->
<SetTag name="AddMetadata">
<DcmTag tag="0008,0020" value="#{Date}"/>
<DcmTag tag="0008,0080" value="Medical Center"/>
</SetTag>
<!-- Storage -->
<Store name="SendToPrimaryPACS"
calledAE="PRIMARY_PACS" callingAE="PRINTER"
host="192.168.1.100" port="104"/>
<Store name="SendToBackupPACS"
calledAE="BACKUP_PACS" callingAE="PRINTER"
host="192.168.1.200" port="104"/>
<Save name="SaveToArchive">
<Directory>E:\Archive\#{PatientID}\#{StudyDate}</Directory>
<Filename>#{SeriesNumber}-#{InstanceNumber}.dcm</Filename>
</Save>
<!-- Notifications -->
<Notify name="NotifyNoMatch"/>
<Notify name="NotifyMultipleMatches"/>
<Notify name="NotifyAllStoreFailed"/>
</Actions>
<Workflow>
<!-- Step 1: Extract patient ID -->
<Perform action="ExtractPatientID" onError="Discard"/>
<!-- Step 2: Validate patient ID exists -->
<If field="TAG_VALUE(0010,0020)" value="^$">
<!-- No patient ID - cannot process -->
<Discard/>
</If>
<!-- Step 3: Query worklist -->
<Perform action="FindWorklist" onError="Suspend"/>
<!-- Step 4: Handle query results -->
<If field="QUERY_PARTIAL" value="true">
<!-- Multiple matches - needs manual selection -->
<Perform action="NotifyMultipleMatches" onError="Ignore"/>
<Suspend resumeAction="FindWorklist"/>
</If>
<If field="QUERY_FOUND" value="false">
<!-- No match - suspend for manual review -->
<Perform action="NotifyNoMatch" onError="Ignore"/>
<Suspend resumeAction="FindWorklist"/>
</If>
<!-- Step 5: Process matched patient -->
<If field="QUERY_FOUND" value="true">
<!-- Add metadata -->
<Perform action="AddMetadata"/>
<!-- Send to primary PACS -->
<Perform action="SendToPrimaryPACS" onError="Hold"/>
<!-- Send to backup PACS -->
<Perform action="SendToBackupPACS" onError="Ignore"/>
<!-- Save to local archive -->
<Perform action="SaveToArchive" onError="Ignore"/>
<!-- Check if at least one store succeeded -->
<If field="STORE_SUCCEEDED" tag="SendToPrimaryPACS" value="false">
<If field="STORE_SUCCEEDED" tag="SendToBackupPACS" value="false">
<!-- Both stores failed - critical error -->
<Perform action="NotifyAllStoreFailed" onError="Ignore"/>
<Suspend resumeAction="SendToPrimaryPACS"/>
</If>
</If>
</If>
</Workflow>
</DicomPrinter>