Uploading Data¶
Uploading imaging data to XNAT is one of the most common operations you will perform with xnatctl. Whether you are ingesting a single session from a scanner export or batch-loading thousands of DICOM files from an archive migration, xnatctl provides several upload pathways that balance speed, reliability, and operational requirements.
At the transport level, XNAT accepts imaging data through two independent
mechanisms: the REST Import API (HTTP-based) and the DICOM network
protocol (C-STORE). The REST Import API is the most common path and is what
xnatctl session upload uses by default. The DICOM network protocol is a
separate pathway that speaks native DICOM to an XNAT DICOM Receiver – this is
what xnatctl session upload-dicom uses. Both ultimately land data in the same
XNAT archive, but they differ in how data is packaged, transmitted, and routed.
The simplest upload command looks like this:
$ xnatctl session upload /path/to/DICOM_ROOT \
-P MYPROJECT -S MYSUBJECT -E MYSESSION
This uploads a directory of DICOM files to XNAT using the default method. The rest of this guide explains the available methods, tuning options, and the full lifecycle from upload through verification. For background on XNAT’s data hierarchy, prearchive staging area, and the difference between IDs and labels, see Key Concepts.
Which Upload Method Should You Use?¶
xnatctl supports three distinct upload methods. The right choice depends on the size of your dataset, whether you need resumability, and whether your XNAT administrator requires the native DICOM protocol. The table below summarizes the key differences to help you decide.
Criteria |
REST DICOM-zip (default) |
REST gradual-DICOM ( |
DICOM C-STORE ( |
|---|---|---|---|
Command |
|
|
|
Transport |
HTTP (REST API) |
HTTP (REST API) |
DICOM network protocol |
How files are sent |
Bundled into archive batches (tar/zip), one request per batch |
One HTTP request per DICOM file |
One C-STORE association per file (parallelizable) |
Speed |
Fast (fewer HTTP requests, compressed payloads) |
Slower (many small HTTP requests) |
Moderate (native protocol, depends on network) |
Resumability |
No – a failed batch must be re-sent entirely |
Yes – individual files succeed or fail independently |
Yes – individual files succeed or fail independently |
Requirements |
XNAT 1.7+ with Import Service enabled |
XNAT 1.7+ with Import Service enabled |
XNAT DICOM Receiver/SCP enabled; |
Best for |
Most users and most datasets |
Very large datasets where partial progress matters, or debugging imports |
Sites where the XNAT admin requires native DICOM ingestion |
For most workflows, the default DICOM-zip mode is the right choice. It minimizes the number of HTTP requests by bundling files into compressed archives, which reduces overhead and is typically the fastest path. If you are uploading a very large dataset (tens of thousands of files) and want the ability to resume after a failure without re-sending files that already succeeded, use gradual-DICOM. Use DICOM C-STORE only when your XNAT deployment requires native DICOM protocol ingestion.
REST Import API: DICOM-zip (Compressed Archive Upload)¶
The default upload mode in xnatctl bundles your DICOM files into compressed
archives and sends them to XNAT’s
Image Session Import Service API
using the DICOM-zip import handler. This approach is efficient because it
reduces the total number of HTTP requests: instead of one request per file, you
send one request per batch, where each batch contains many DICOM files packed
into a single archive.
Under the hood, xnatctl splits the files in your source directory into N batches
(where N equals the --workers count), compresses each batch into a tar or zip
archive, and uploads the batches in parallel. This means increasing --workers
both increases upload parallelism and creates more (smaller) batches.
Basic Directory Upload¶
To upload a directory of DICOM files, provide the path along with the project, subject, and session identifiers.
$ xnatctl session upload /path/to/DICOM_ROOT \
-P MYPROJECT -S MYSUBJECT -E MYSESSION
Note
xnatctl scans directories recursively and includes extensionless files, which is common for scanner DICOM exports. You do not need to flatten your directory structure before uploading.
You can also upload a pre-built archive file (zip or tar) directly.
$ xnatctl session upload /path/to/session_data.zip \
-P MYPROJECT -S MYSUBJECT -E MYSESSION
Tuning Workers and Archive Format¶
By default, xnatctl uses 4 parallel workers. For large datasets or fast network connections, you can increase this to speed up the upload. Each worker handles one batch, so more workers means more concurrent uploads and smaller individual batches.
$ xnatctl session upload /path/to/DICOM_ROOT \
-P MYPROJECT -S MYSUBJECT -E MYSESSION \
--workers 8
You can also select the archive format used for batching via --mode. The
default is tar, but zip is available if your XNAT instance handles it
better.
$ xnatctl session upload /path/to/DICOM_ROOT \
-P MYPROJECT -S MYSUBJECT -E MYSESSION \
--workers 8 \
--mode zip
Direct Archive vs Prearchive¶
When data arrives at XNAT via the Import API, it can go to one of two places: the permanent archive or the prearchive (a temporary staging area). The choice depends on your workflow and how much review you need before data becomes part of the official dataset.
Direct archive (--direct-archive, the default) sends data straight into
the permanent archive, bypassing the prearchive entirely. This is the right
choice for automated pipelines, re-uploads of validated data, or any situation
where you are confident the data is correct. Direct archive is supported on XNAT
1.8.3 and later.
Prearchive (--prearchive) stages data in XNAT’s temporary holding area
first. A human operator (or automated script) must then review and explicitly
archive the session before it appears in the main data hierarchy. This is useful
when you want to inspect DICOM headers, verify subject assignments, or check scan
completeness before committing data. See Key Concepts for a full explanation
of the prearchive workflow.
# Direct archive (default) -- data goes straight into the permanent archive
$ xnatctl session upload /path/to/DICOM_ROOT \
-P MYPROJECT -S MYSUBJECT -E MYSESSION \
--direct-archive
# Prearchive -- data stages for review before archiving
$ xnatctl session upload /path/to/DICOM_ROOT \
-P MYPROJECT -S MYSUBJECT -E MYSESSION \
--prearchive
See the XNAT documentation for more on direct archive behavior: Using Direct-to-Archive Uploading.
Warning
Direct archive mode requires XNAT 1.8.3 or later. If your server runs an
older version, uploads with --direct-archive may fail or silently route
to the prearchive. Check with your XNAT administrator if you are unsure.
Authentication for Parallel Uploads¶
Directory uploads are parallelized across worker threads. Each worker needs valid
credentials to authenticate its HTTP requests. If you have a cached session token
(from xnatctl auth login), xnatctl reuses it across all workers
automatically.
If you do not have a cached session token, xnatctl will prompt for credentials or you can provide them explicitly. Credential sources follow the standard priority: CLI args > environment variables > profile config > interactive prompt.
$ xnatctl session upload /path/to/DICOM_ROOT \
-P MYPROJECT -S MYSUBJECT -E MYSESSION \
--username myuser \
--password mypassword
Tip
For scripted or automated uploads, set XNAT_USER and XNAT_PASS
environment variables to avoid interactive prompts. Alternatively, run
xnatctl auth login beforehand to cache a session token.
REST Import API: gradual-DICOM (File-by-File)¶
The --mode gradual flag switches from batch-archive upload to file-by-file
upload using the gradual-DICOM import handler. Instead of bundling files into
archives, xnatctl sends one HTTP request per DICOM file to XNAT’s Import
Service.
This mode is slower than DICOM-zip because of the per-file HTTP overhead, but it has a significant advantage: each file succeeds or fails independently. If an upload is interrupted partway through, the files that already succeeded are safely in XNAT, and you only need to re-send the remainder. This makes gradual-DICOM a better choice for very large datasets (tens of thousands of files) where a single batch failure would be costly to retry.
Because each file is an independent request, you can increase --workers to a
higher value than you would use for DICOM-zip uploads. The overhead per request
is small, so more parallelism helps compensate for the per-file latency.
$ xnatctl session upload /path/to/DICOM_ROOT \
-P MYPROJECT -S MYSUBJECT -E MYSESSION \
--mode gradual \
--workers 16
Note
With --mode gradual, progress output reports every 100 files to avoid flooding
your terminal. For full per-file details, use --output json.
DICOM C-STORE¶
The upload-dicom command sends DICOM files over the native DICOM network
protocol (C-STORE) to an XNAT DICOM Receiver (SCP). Unlike the REST-based
methods above, this pathway does not use HTTP at all – it speaks the same
protocol that scanners and PACS systems use to transfer images.
Use C-STORE when your XNAT administrator has configured a DICOM Receiver and requires data to arrive through that pathway, or when you need to replicate the same ingestion path that your scanner uses. Where data lands (prearchive vs archive) depends on the XNAT server’s DICOM receiver configuration and routing rules, not on any flag you pass to xnatctl.
Requirements¶
DICOM C-STORE has two prerequisites that the REST methods do not:
XNAT must have a DICOM Receiver/SCP enabled and reachable at a specific host, port, and Application Entity Title (AET). Your XNAT administrator can provide these values.
You must install the optional DICOM dependencies (pydicom and pynetdicom):
$ pip install "xnatctl[dicom]"
Example¶
To send a directory of DICOM files via C-STORE, provide the DICOM Receiver’s connection details.
$ xnatctl session upload-dicom /path/to/DICOM_ROOT \
--host xnat.example.org \
--port 8104 \
--called-aet XNAT \
--calling-aet XNATCTL \
--workers 4
You can also set connection parameters via environment variables to avoid repeating them in every command:
XNAT_DICOM_HOST– DICOM Receiver hostnameXNAT_DICOM_PORT– DICOM Receiver port (default: 104)XNAT_DICOM_CALLED_AET– Called AE TitleXNAT_DICOM_CALLING_AET– Calling AE Title (default: XNATCTL)
Tip
Use --dry-run to verify your connection parameters before sending any
data:
$ xnatctl session upload-dicom /path/to/DICOM_ROOT \
--host xnat.example.org \
--port 8104 \
--called-aet XNAT \
--dry-run
Managing the Prearchive After Upload¶
If your upload lands in the prearchive – either because you used
--prearchive, because your XNAT server’s project settings route imports
there, or because DICOM C-STORE routing rules directed data to staging – you
need to explicitly archive the session before it appears in the main data
hierarchy.
The prearchive workflow is: upload -> review in prearchive -> archive to permanent storage (or delete if the data is incorrect). This extra step gives you a chance to verify subject assignments, check scan completeness, and catch any header issues before data becomes part of the official dataset. See Key Concepts for a full explanation of the prearchive.
List Prearchive Sessions¶
After uploading, check whether your data arrived in the prearchive.
$ xnatctl prearchive list --project MYPROJECT
Archive a Prearchive Session¶
Once you are satisfied the data is correct, move it from the prearchive into the permanent archive.
$ xnatctl prearchive archive MYPROJECT 20240115_120000 Session1 \
--subject MYSUBJECT \
--label MYSESSION
Delete a Prearchive Session¶
If the data is incorrect or was uploaded by mistake, you can remove it from the prearchive without it ever entering the archive.
$ xnatctl prearchive delete MYPROJECT 20240115_120000 Session1 --yes
Uploading Non-DICOM Resources¶
Not all data in XNAT is DICOM. You may need to attach NIfTI files, BIDS
datasets, quality-control snapshots, or other derived outputs to sessions or
scans. Use xnatctl resource upload for these cases. When you upload a
directory, xnatctl zips it locally and extracts it server-side; single files are
uploaded directly.
Note
xnatctl resource upload PUTs files directly to the resource catalog and
bypasses XNAT project-level DICOM anonymization scripts and pipelines.
Use xnatctl session upload or xnatctl session upload-exam when
anonymization is required.
Upload a Scanner Exam-Root Directory¶
Some scanner exports include a single “exam-root” directory containing both
DICOM files and non-DICOM items like PDFs, screenshots, or vendor metadata.
Use xnatctl session upload-exam to upload the DICOM content and attach the
top-level non-DICOM items as session resources in the same workflow.
Top-level non-DICOM directories become session resources named after the
directory (Resources/<dirname>). Top-level non-DICOM files are grouped into
Resources/MISC.
For example, top-level Physio and Protocol directories become
Resources/Physio and Resources/Protocol.
$ xnatctl session upload-exam /path/to/EXAM_ROOT \
-P MYPROJECT -S MYSUBJECT -E MYSESSION \
--dry-run
By default, session upload-exam uploads the DICOM content first and then
waits for the session to appear in the permanent archive before attaching
the top-level non-DICOM items as resources. This makes the attach step more
reliable on servers where the import request can return before the session is
fully available in the archive.
You can disable the wait (fail fast) or tune the timeout with --wait. The
default is --wait 900 (15 minutes). Set --wait 0 to skip waiting entirely:
# Do not wait for the session to appear in the archive
$ xnatctl session upload-exam /path/to/EXAM_ROOT \
-P MYPROJECT -S MYSUBJECT -E MYSESSION \
--wait 0
# Wait up to 30 minutes
$ xnatctl session upload-exam /path/to/EXAM_ROOT \
-P MYPROJECT -S MYSUBJECT -E MYSESSION \
--wait 1800
Note
session upload-exam can upload DICOM to the prearchive (--prearchive),
but attaching resources requires the session to be in the permanent archive.
If the session remains in the prearchive, the default archive wait will time
out. In that case, upload the DICOM now with --skip-resources (optionally
add --wait 0 if you want fail-fast behavior) and later attach
resources with --attach-only after you archive the session.
Upload a Directory to a Session Resource¶
This uploads the entire directory as a named resource on the session.
$ xnatctl resource upload XNAT_E00001 BIDS ./bids_dataset
Upload a File to a Session Resource¶
You can also upload individual files.
$ xnatctl resource upload XNAT_E00001 NIFTI ./t1w.nii.gz
Upload to a Scan Resource¶
To attach files to a specific scan within a session, use the --scan flag.
$ xnatctl resource upload XNAT_E00001 SNAPSHOTS ./qc_pngs --scan 1
Verifying Your Upload¶
After uploading, you should confirm that your data arrived in XNAT and that the scan count matches your expectations. Where you look depends on whether the data went to the permanent archive or the prearchive.
Check the Archive¶
If you used --direct-archive (the default for REST uploads), your session
should appear in the session listing immediately after the upload completes.
$ xnatctl session list -P MYPROJECT
To verify individual scan counts, inspect the session detail view.
$ xnatctl scan list -E MYSESSION -P MYPROJECT
Compare the number of scans returned against what you expect from your source data (e.g., the number of series directories in your DICOM export).
Check the Prearchive¶
If your data does not appear in session list, it may be in the prearchive.
$ xnatctl prearchive list --project MYPROJECT
Note
Data in the prearchive is not yet part of the permanent archive. It will not
appear in session list or scan list until you explicitly archive it
with xnatctl prearchive archive.
Common Reasons Data Might Not Appear¶
If your upload command completed successfully but you cannot find the data in either the archive or the prearchive, consider these possibilities:
Data is still in the prearchive. This is the most common reason. Check with
xnatctl prearchive list --project MYPROJECT.Data went to a different project. If the DICOM headers contain a different project identifier than what you specified, XNAT may have routed the session elsewhere. Ask your XNAT admin to check the server logs.
Import errors on the server. XNAT may have rejected some or all files due to unparsable DICOM headers or validation failures. Check the XNAT server’s import logs (accessible through the web UI under Administration > Event Service or the prearchive error log).
The session merged with an existing session. If a session with the same subject and session label already exists, XNAT may have merged the upload into the existing session rather than creating a new one. Check the existing session’s scan list to see if new scans appeared.
Troubleshooting¶
Upload Succeeds but Data Not in Archive¶
The most likely explanation is that data landed in the prearchive. Check with:
$ xnatctl prearchive list --project MYPROJECT
If you see your session there, archive it with xnatctl prearchive archive.
If you intended direct archive, verify that you passed --direct-archive
(which is the default) and that your XNAT server supports it (XNAT 1.8.3+).
Some XNAT project-level settings can override the upload request and force data
into the prearchive regardless of the Direct-Archive flag.
Timeout on Large Uploads¶
If uploads of large datasets fail with timeout errors, you can increase the HTTP timeout in your profile configuration or via environment variable. The default timeout for upload operations is 6 hours (21600 seconds), which should be sufficient for most datasets. If you need more, set a higher value.
# In ~/.config/xnatctl/config.yaml
profiles:
production:
url: https://xnat.example.org
timeout: 43200 # 12 hours
Alternatively, use the XNAT_TIMEOUT environment variable:
$ XNAT_TIMEOUT=43200 xnatctl session upload /path/to/DICOM_ROOT \
-P MYPROJECT -S MYSUBJECT -E MYSESSION
“Already Exists” Errors¶
If XNAT reports that a session or scan already exists, the behavior depends on
the --overwrite flag. By default, xnatctl uses --overwrite delete, which
replaces existing data. If you want to append new scans to an existing session
without removing old data, use --overwrite append. To prevent any changes to
existing sessions, use --overwrite none.
$ xnatctl session upload /path/to/DICOM_ROOT \
-P MYPROJECT -S MYSUBJECT -E MYSESSION \
--overwrite append
DICOM C-STORE Connection Issues¶
C-STORE failures are almost always caused by incorrect connection parameters or network configuration. Verify the following with your XNAT administrator:
Wrong port. The default DICOM port is 104, but many XNAT installations use a non-standard port such as 8104. Confirm the correct port.
Wrong Called AE Title. The Called AET must match what is configured on the XNAT DICOM Receiver. A mismatch causes the receiver to reject the association.
Firewall or network issues. Ensure that the DICOM Receiver port is open and reachable from your machine. Try
telnet xnat.example.org 8104(or the appropriate port) to test basic connectivity.Missing DICOM dependencies. C-STORE requires the
xnatctl[dicom]extra. If you see an import error mentioning pydicom or pynetdicom, install it withpip install "xnatctl[dicom]".
Tip
Use --dry-run with upload-dicom to print the connection parameters
that xnatctl would use, without actually sending any data. This is a quick
way to verify your configuration.