Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- STOW-RS (DIMSE backend) additionally offers the uncompressed transfer syntaxes and decompresses instances if the PACS does not accept their transfer syntax (#83). Can be disabled with `stow-rs.uncompressed-fallback: false`.
- JPEG 2000 and JPEG-LS pixel data can now be decoded, e.g. when transcoding WADO-RS responses. Building from source requires a C++ compiler and CMake.
- STOW-RS responses include a `FailureReason` for each failed instance.

### Changed

- STOW-RS responds with `202 Accepted` if some instances failed and with `409 Conflict` if all instances failed, instead of `200 OK`.
- Failures to store an instance are logged as warnings.

### Fixed

- STOW-RS (DIMSE backend) no longer reports an instance as stored if the PACS returned a failure status in the C-STORE-RSP.

## [0.3.4] - 2026-10-27

### Added
Expand Down
94 changes: 83 additions & 11 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ mimalloc = ["dep:mimalloc"]
# DICOM processing
dicom = "0.10.0"
dicom-json = "0.10.0"
dicom-pixeldata = { version = "0.10.0", features = ["image"] }
dicom-pixeldata = { version = "0.10.0", features = ["image", "openjpeg-sys", "charls"] }
sentry = { version = "0.45.0", features = ["tracing"] }

# Serialization
Expand Down
4 changes: 4 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
FROM lukemathwalker/cargo-chef:latest-rust-1.94.1 AS chef
# cmake builds CharLS (JPEG-LS codec)
RUN apt-get update \
&& apt-get install -y --no-install-recommends cmake \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app

FROM chef AS planner
Expand Down
3 changes: 2 additions & 1 deletion Dockerfile-s3
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
FROM rust:1.94.1-alpine3.23 AS builder
RUN apk add musl-dev openssl-dev openssl-libs-static
# g++, cmake and make build CharLS (JPEG-LS codec)
RUN apk add musl-dev openssl-dev openssl-libs-static g++ cmake make

WORKDIR /app

Expand Down
1 change: 1 addition & 0 deletions deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ unsound = "all"
ignore = [
{ id = "RUSTSEC-2021-0153", reason = "encoding is used by dicom-encoding (dicom-rs); no maintained replacement upstream yet" },
{ id = "RUSTSEC-2024-0436", reason = "paste is used by rav1e (image, via dicom-pixeldata); proc-macro only" },
{ id = "RUSTSEC-2026-0440", reason = "astral-tokio-tar is used by testcontainers (dev-dependency only); no testcontainers release without it yet" },
]

[licenses]
Expand Down
9 changes: 9 additions & 0 deletions docs/topics/backend-dimse.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,15 @@ sequenceDiagram
end
```

Each instance is offered in its own transfer syntax and, unless
[`stow-rs.uncompressed-fallback`](configuration.md#dicomweb.stow-rs.uncompressed-fallback) is disabled,
additionally in the uncompressed transfer syntaxes.
If the PACS does not accept the original transfer syntax, the instance is decompressed before sending it.

Instances that could not be stored are listed in the `FailedSOPSequence` of the response.
The `FailureReason` contains the status of the C-STORE-RSP, `0xC122` if the instance could not be
converted to an accepted transfer syntax, or `0x0110` for other errors (e.g. a rejected association).

### Resources

| Resource | URI Template | Support Status |
Expand Down
10 changes: 10 additions & 0 deletions docs/topics/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ aets:
timeout: 3000
stow-rs:
timeout: 3000
uncompressed-fallback: true
```

Each AET (regardless of the backend) has additional settings specific to the DICOMweb endpoints.
Expand Down Expand Up @@ -244,6 +245,15 @@ Each AET (regardless of the backend) has additional settings specific to the DIC
This is the timeout for a single operation (e.g. receiving a DIMSE-C response primitive).
If you want to set a timeout for the total execution time, use the <code>server.http.request-timeout</code> option instead.
</def>
<def title="stow-rs.uncompressed-fallback" id="dicomweb.stow-rs.uncompressed-fallback">
<b>DIMSE-backend only:</b>
Whether to also offer the uncompressed transfer syntaxes (Explicit and Implicit VR Little Endian) when storing an instance.
Instances are sent in their original transfer syntax whenever the PACS accepts it.
If the PACS only accepts the uncompressed transfer syntaxes, the instance is decompressed before sending it.
Supported compressions are JPEG, JPEG Lossless, JPEG-LS, JPEG 2000 and RLE.
If disabled, instances in a transfer syntax that the PACS does not accept fail to store.
Defaults to <code>true</code>.
</def>
</deflist>

## S3 Backend Config
Expand Down
10 changes: 2 additions & 8 deletions src/api/stgcmt/service.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub use crate::api::stow::FailedInstance;
use crate::api::stow::InstanceReference;
use crate::types::{UI, US};
use crate::types::UI;
use async_trait::async_trait;
use axum::body::Body;
use axum::http::{Response, StatusCode};
Expand Down Expand Up @@ -106,13 +107,6 @@ pub struct InstanceLocation {
pub series_instance_uid: UI,
}

/// An instance for which storage has not been committed.
#[derive(Debug, Clone)]
pub struct FailedInstance {
pub reference: InstanceReference,
pub failure_reason: US,
}

/// The result of a storage commitment request, once known.
/// Storage Commitment Response Module.
/// <https://dicom.nema.org/medical/dicom/current/output/chtml/part18/chapter_J.html#sect_J.2>
Expand Down
3 changes: 2 additions & 1 deletion src/api/stow/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,11 @@ async fn studies(
instances: receiver.boxed(),
};
let response = stow.store(request).await?;
let status = response.status_code();
let json = DicomJson::from(InMemDicomObject::from(response));

Ok(Response::builder()
.status(StatusCode::OK)
.status(status)
.header(CONTENT_TYPE, mime::APPLICATION_JSON.as_ref())
.body(Body::from(serde_json::to_string(&json).unwrap()))
.unwrap())
Expand Down
Loading
Loading