Appearance
Lua Examples
Complete lua.yml snippets and multi-file setups for common scenarios. For API reference, see Lua Filter.
Storage Examples
Anonymize all images
Strip patient information from every image that passes through.
yaml
- Description: Strip PHI
Script: |
dataset:Set('PatientName', 'ANONYMOUS')
dataset:Set('PatientID', '00000')
dataset:Remove('PatientBirthDate')
dataset:Remove('PatientAddress')
dataset:Remove('ReferringPhysicianName')Route CT images to an AI server
Use conditions to match modality, then add a routing destination.
yaml
- Description: Route CT to AI
Script: |
route:Add('AI_SERVER')
Conditions:
- Tag: 0008,0060
MatchExpression: CTFan-out with per-destination anonymization
Send mammography to multiple destinations with different PHI policies. The original goes to MAMMO_PACS unmodified. The AI vendor gets an anonymized copy. The teaching archive gets fully de-identified data with new UIDs.
yaml
- Description: Distribute mammography
Script: |
route:Add('MAMMO_PACS')
route:Add('AI_VENDOR', function(ds)
ds:Set('PatientName', 'ANONYMOUS')
ds:Set('PatientID', '00000')
ds:Set('InstitutionName', 'REDACTED')
end)
route:Add('TEACHING_ARCHIVE', function(ds)
ds:Set('PatientName', 'TEACHING')
ds:Set('PatientID', uid())
ds:Set('StudyInstanceUID', uid())
ds:Set('SeriesInstanceUID', uid())
ds:Set('SOPInstanceUID', uid())
ds:Remove('PatientBirthDate')
ds:Remove('PatientAddress')
ds:Remove('ReferringPhysicianName')
end)
Conditions:
- Tag: 0008,0060
MatchExpression: MGDrop and reroute
Redirect vascular ultrasound to a specialized PACS. The original destination is dropped so only the rerouted copy is sent.
yaml
- Description: Redirect vascular US
Script: |
route:Add('VASCULAR_PACS')
route:Drop()
Conditions:
- Tag: 0008,0060
MatchExpression: US
- Tag: 0008,1030
MatchExpression: '*VASCULAR*'Enrich images based on source scanner
Tag images with the originating scanner so downstream systems can identify where the study was acquired.
yaml
- Description: Tag by source scanner
Script: |
local sources = {
CT_SCANNER_1 = 'CT Room 1 - Main Building',
CT_SCANNER_2 = 'CT Room 2 - Emergency',
MR_SCANNER = 'MRI Suite A',
}
local label = sources[file.sourceAeTitle]
if label then
dataset:Set('StationName', label)
endStudy-level image counting with conditional routing
Count images per study and route large studies to a dedicated archive. Uses scoped state to track counts across images.
yaml
- Description: Route large studies
Script: |
study.image_count = (study.image_count or 0) + 1
if study.image_count > 500 then
route:Add('LARGE_STUDY_ARCHIVE')
endShared library for reusable anonymization
Factor common logic into a library file and call it from multiple entries. include() loads each file at most once per image.
yaml
- Description: Anonymize for research
Script: |
include('libs/phi.lua')
route:Add('RESEARCH_ARCHIVE', function(ds)
strip_phi(ds)
ds:Set('PatientID', uid())
end)Destination-specific enrichment
Only runs when preparing images for AI_SERVER. Adds institutional metadata the AI vendor requires.
yaml
- Description: Enrich for AI server
Script: |
dataset:Set('InstitutionName', 'GENERAL HOSPITAL')
dataset:Set('InstitutionalDepartmentName', 'RADIOLOGY')
AeTitles:
- AI_SERVERValidate required tags
Validate that required tags are present before sending to an external system. OnError: fail ensures the image is held rather than sent incomplete.
yaml
- Description: Validate required tags
Script: |
local required = {'PatientID', 'PatientName', 'Modality', 'StudyInstanceUID'}
for _, tag in ipairs(required) do
if dataset:Get(tag) == nil then
error('fail: missing required tag ' .. tag)
end
end
AeTitles:
- EXTERNAL_PACS
OnError: failTimestamp injection and string normalization
Normalize study descriptions to uppercase and stamp images with the processing date.
yaml
- Description: Normalize and timestamp
Script: |
local desc = dataset:Get('StudyDescription') or ''
dataset:Set('StudyDescription', string.upper(desc))
dataset:Set('ContentDate', os.date('%Y%m%d'))
dataset:Set('ContentTime', os.date('%H%M%S'))Queue-aware routing
Only route to the AI server when it isn't backed up. Checks destination health before routing.
yaml
- Description: Route to AI if healthy
Script: |
local failed = queue:countByDestination('AI_SERVER', 'Failed')
local pending = queue:countByDestination('AI_SERVER', 'New')
if failed > 20 then
print('AI_SERVER has ' .. failed .. ' failures, skipping')
elseif pending > 100 then
print('AI_SERVER backlog at ' .. pending .. ', skipping')
else
route:Add('AI_SERVER')
end
Conditions:
- Tag: 0008,0060
MatchExpression: CTFlag partial studies
Use study.queue to detect partially processed studies and tag them for operator review.
yaml
- Description: Flag partial studies
Script: |
local total = study.queue:totalCount()
local failed = study.queue:countByState('Failed')
local prepared = study.queue:countByState('Prepared')
if failed > 0 and prepared > 0 then
dataset:Set('ImageComments',
'PARTIAL_STUDY: ' .. failed .. ' of ' .. total .. ' failed')
endQuery Examples
Worklist: broaden the query upstream, filter results
Stash the requested station in session, widen the upstream request, then suppress results that don't match.
yaml
- Affects: [worklist_query]
Description: Broaden worklist query
Script: |
session.requestedStation = dataset:Get('ScheduledProcedureStepSequence[1].ScheduledStationAETitle')
dataset:Set('ScheduledProcedureStepSequence[1].ScheduledStationAETitle', 'WL_PROXY')
- Affects: [worklist_result]
Description: Filter worklist results
Script: |
local resultStation = dataset:Get('ScheduledProcedureStepSequence[1].ScheduledStationAETitle')
if resultStation ~= session.requestedStation then
response:drop()
endWorklist: suppress cancelled or hidden orders
yaml
- Affects: [worklist_result]
Description: Drop cancelled and hidden orders
Script: |
local accession = dataset:Get('AccessionNumber') or ''
if accession == 'CANCELLED' or accession:sub(1, 4) == 'HID_' then
response:drop()
endQR: suppress results from a specific institution
yaml
- Affects: [qr_find_result]
Description: Hide results from restricted site
Script: |
if dataset:Get('InstitutionName') == 'RESTRICTED_SITE' then
response:drop()
endQR: deduplicate results by accession using session
Suppress duplicate study results when the same accession number has already appeared in this query.
yaml
- Affects: [qr_find_result]
Description: Deduplicate by accession
Script: |
local acc = dataset:Get('AccessionNumber')
if acc and acc ~= '' then
if session['seen_' .. acc] then
response:drop()
else
session['seen_' .. acc] = true
end
endQR: enrich results with a computed field
yaml
- Affects: [qr_find_result]
Description: Tag result source
Script: |
dataset:Set('InstitutionName', query.nodeAeTitle .. '_PROXY')Multi-File Project Examples
Queue-Aware Routing with Shared Libraries
Uses a shared PHI library, a station label library, and an external routing script that checks queue health.
File layout:
config-dir/
├── config.yml
├── nodes.yml
├── lua.yml
├── libs/
│ ├── phi.lua
│ └── station-labels.lua
└── scripts/
└── route-smart.luayaml
# lua.yml
- Description: Anonymize for viewer
Script: |
include('libs/phi.lua')
strip_phi(dataset)
dataset:Set('PatientID', uid())
AeTitles:
- VIEWER_TEST
- Description: Smart CT routing
Script: scripts/route-smart.lua
Conditions:
- Tag: 0008,0060
MatchExpression: CTlua
-- libs/phi.lua
function strip_phi(ds)
ds:Set('PatientName', 'ANONYMOUS')
ds:Set('PatientID', '00000')
ds:Remove('PatientBirthDate')
ds:Remove('PatientAddress')
ds:Remove('ReferringPhysicianName')
ds:Remove('InstitutionName')
ds:Remove('InstitutionAddress')
endlua
-- libs/station-labels.lua
STATION_LABELS = {
CT_SCANNER_1 = 'CT Room 1 - Main Building',
CT_SCANNER_2 = 'CT Room 2 - Emergency',
MR_SCANNER = 'MRI Suite A',
US_SCANNER = 'Ultrasound Bay 3',
}
function label_station(ds)
local label = STATION_LABELS[file.sourceAeTitle]
if label then
ds:Set('StationName', label)
end
endlua
-- scripts/route-smart.lua
include('libs/station-labels.lua')
label_station(dataset)
local failed = queue:countByDestination('ARCHIVE', 'Failed')
local backlog = queue:countByDestination('ARCHIVE', 'New')
if failed > 50 then
log:warn('ARCHIVE has', failed, 'failures — holding')
error('retry: destination has too many failures')
elseif backlog > 200 then
log:warn('ARCHIVE backlog at', backlog, '— routing with warning')
dataset:Set('ImageComments', 'ROUTED_UNDER_BACKLOG:' .. backlog)
end
route:Add('ARCHIVE')
local total = study.queue:totalCount()
local studyFailed = study.queue:countByState('Failed')
if studyFailed > 0 then
log:warn('Study has', studyFailed, 'of', total, 'items failed')
dataset:Set('ImageComments',
'PARTIAL_STUDY: ' .. studyFailed .. '/' .. total .. ' failed')
endFan-Out with De-Identification
Route mammography to three destinations: clinical PACS unmodified, research with basic anonymization, cloud AI with full de-identification and new UIDs.
yaml
# lua.yml
- Description: Distribute mammography
Script: |
include('libs/phi.lua')
route:Add('RESEARCH', function(ds)
strip_phi(ds)
ds:Set('PatientID', uid())
end)
route:Add('CLOUD_AI', function(ds)
strip_phi(ds)
ds:Set('PatientName', 'DEIDENTIFIED')
ds:Set('PatientID', uid())
ds:Set('StudyInstanceUID', uid())
ds:Set('SeriesInstanceUID', uid())
ds:Set('SOPInstanceUID', uid())
end)
log:info('Mammography fan-out:',
file.sourceAeTitle, '->', file.destinationAeTitle,
'+ RESEARCH + CLOUD_AI')
Conditions:
- Tag: 0008,0060
MatchExpression: MG