Container Service: how to manage input and output in a container built from scratch

9 views
Skip to first unread message

Filippo Manini

unread,
Oct 10, 2025, 6:50:20 AM (3 days ago) Oct 10
to xnat_discussion

Hello everyone,

Here’s my setup:

  • XNAT: 1.8.10

  • Container Service: 3.5.0

  • OHIF Viewer: 3.7.1

  • OS: Linux

  • Installation: via Docker using NRG XNAT Docker Compose

I’m trying to understand how to properly manage input and output within a custom container developed for use with the XNAT Container Service.

My goal is to build something similar to dcm2niix: a container that takes a folder containing DICOM files as input, performs some processing (for example, modifying a parameter), and then saves the resulting DICOM files as output.

I’m starting with a simplified version, just to understand the workflow before implementing the final functionality.
I’ve attached the files I’m using for these initial tests.

These are the commands I use to build and test the container outside of XNAT:

docker build -t xnat/custom-container . docker run -v C:\Users\Downloads\PathExample:/input xnat/custom-container

I’d like to know the correct way to make XNAT mount and handle the input/output folders within the container, and whether there are specific best practices to follow when defining the command.json.

command.json
Dockerfile
xnat_custom_container.py

John Flavin

unread,
Oct 10, 2025, 11:59:24 AM (3 days ago) Oct 10
to xnat_di...@googlegroups.com
Hi Filippo,

There isn't any documentation on best practices for writing commands (at least not that I remember). That's something I always meant to write a guide for but I never did. And of course we wanted to make a UI for constructing commands but that was too massive of an undertaking at the time and hasn't ever come back around.

Anyway, my advice is to use examples. Look through the https://github.com/NrgXnat/docker-images repo (which you’re already aware of) and the radiologics fork https://github.com/radiologics/docker-images (which has a lot of the same images but adds several more). There are likely other repos out there that other groups have made which could provide inspiration.

And when I say "inspiration" I mean just copy and paste. Find a command that does something sort of like what you need, copy it to a new file, then edit until it does what you need. I recommend starting with the debug command because it shows you how to start from most of the different XNAT objects (project, subject, etc.) and mount input and output files.

Good luck! And keep asking questions as they come up.

John Flavin

--
You received this message because you are subscribed to the Google Groups "xnat_discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to xnat_discussi...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/xnat_discussion/c2758583-a8e8-40c6-96a9-0a2074859413n%40googlegroups.com.

Filippo Manini

unread,
7:08 AM (9 hours ago) 7:08 AM
to xnat_discussion

Hi John,

thank you for your valuable advice — it really helped!

I’ve managed to read the files inside the DICOM folder and create an output folder called NEW-DICOM, but this folder doesn’t get populated with the expected files (whereas when I run the same container outside of XNAT, the files are generated correctly).

Below you can find my code and the Container Execution Log.
I’ve compared it with the dcm2niix example (which I used as a reference), and they look quite similar to me.

Dockerfile

FROM python:3.13.2-alpine
RUN mkdir /input && mkdir /output
ADD xnat_custom_container.py ./
CMD ["python3", "xnat_custom_container.py", "/input", "/output"]

command.json

{
    "name": "xnat-custom-container",
    "description": "Custom XNAT container for processing medical images",
    "image": "xnat/custom-container:latest",
    "type": "docker",
    "command-line": "python xnat_custom_container.py /input /output",
    "mounts": [
        {
            "name": "dicom-in",
            "writable": false,
            "path": "/input"
        },
        {
            "name": "dicom-out",
            "writable": true,
            "path": "/output"
        }
    ],
    "environment-variables": {},
    "ports": {},
    "inputs": [],
    "outputs": [
        {
            "name": "new-dicom",
            "description": "The new-dicom files",
            "required": true,
            "mount": "dicom-out"
        }
    ],
    "xnat": [
        {
            "name": "custom-container-scan",
            "label": "custom-container",
            "description": "Run custom-container on a Scan",
            "contexts": [
                "xnat:imageScanData"
            ],
            "external-inputs": [
                {
                    "name": "scan",
                    "description": "Input scan",
                    "type": "Scan",
                    "matcher": "'DICOM' in @.resources[*].label",
                    "required": true,
                    "load-children": true
                }
            ],
            "derived-inputs": [
                {
                    "name": "scan-dicoms",
                    "description": "The dicom resource on the scan",
                    "type": "Resource",
                    "matcher": "@.label == 'DICOM'",
                    "required": true,
                    "provides-files-for-command-mount": "dicom-in",
                    "load-children": true,
                    "derived-from-wrapper-input": "scan",
                    "multiple": false
                }
            ],
            "output-handlers": [
                {
                    "name": "new-dicom-resource",
                    "accepts-command-output": "new-dicom",
                    "as-a-child-of": "scan",
                    "type": "Resource",
                    "label": "NEW-DICOM",
                    "tags": []
                }
            ]
        }
    ],
    "container-labels": {},
    "generic-resources": {},
    "ulimits": {},
    "secrets": []
}

xnat_custom_container.py

import sys
from pathlib import Path
import shutil

if len(sys.argv) < 3:
    print("No input path provided")
    sys.exit(1)

input_path = Path(sys.argv[1])
output_path = Path(sys.argv[2])

if not input_path.exists():
    print(f"Input path '{input_path}' does not exist.")
    sys.exit(1)
if not output_path.exists():
    print(f"Output path '{output_path}' does not exist.")
    sys.exit(1)
elif input_path.is_dir():
    print(f"Listing files in directory: '{input_path}'")
    for item in input_path.iterdir():
        if item.is_file():
            new_file_path = output_path / f"new_{item.name}"
            shutil.copy(item, new_file_path)
            print(f"  - Copied '{item}' to '{new_file_path}'")
else:
    print(f"Path '{input_path}' is not a file or a directory.")

sys.exit(0)

image.png

Immagine 2025-10-13 130618.png

Do you have any idea what I might be missing or doing wrong?

Thanks again for your time and help!

Filippo

John Flavin

unread,
3:31 PM (1 hour ago) 3:31 PM
to xnat_di...@googlegroups.com
Nothing in your command jumps out at me as obviously wrong. What you've posted could work. (I do see one small error: your dockerfile CMD uses python3 whereas the command's command-line uses python. But that's likely fine as I'm guessing they alias to the same thing.)

Can you check for errors in the logs? I would check in this order:
  1. The container stdout/stderr logs. There are buttons to view those on the container instance UI (what you took your screenshots of).
  2. The container service logs
  3. The XNAT logs. I think it would be in... application.log? I'm not entirely sure. You can keep a tail -f *.log open on the whole XNAT logs directory while you are running the container to see whatever gets logged at that time. 

Reply all
Reply to author
Forward
0 new messages