Skip to content

Commit

Permalink
feat(rendered): Support rendered resources
Browse files Browse the repository at this point in the history
  • Loading branch information
feliwir committed Jan 7, 2025
1 parent 2de782d commit e282b88
Show file tree
Hide file tree
Showing 7 changed files with 499 additions and 31 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

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

7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ version = "0.3.0-beta.1"
description = "A robust DICOMweb server with swappable backend"
edition = "2021"
rust-version = "1.74.0"
categories = ["multimedia", "network-programming", "web-programming::http-server"]
categories = [
"multimedia",
"network-programming",
"web-programming::http-server",
]
keywords = ["dicom", "dicomweb", "healthcare", "medical"]
repository = "https://github.com/UMEssen/DICOM-RST"
license = "MIT"
Expand All @@ -20,6 +24,7 @@ s3 = []
# DICOM processing
dicom = "0.8.0"
dicom-json = "0.8.0"
dicom-pixeldata = { version = "0.8.0", features = ["image"] }
sentry = { version = "0.35.0", features = ["tracing"] }

# Serialization
Expand Down
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,23 @@ https://www.dicomstandard.org/using/dicomweb/retrieve-wado-rs-and-wado-uri

#### Rendered Resources

❌ Rendered Resourced are not supported.
| Description | Path | Support Status |
|------------------|-----------------------------------------------------------------|:--------------:|
| Study Instances | `studies/{study}/rendered` ||
| Series Instances | `studies/{study}/series/{series}/rendered` ||
| Instance | `studies/{study}/series/{series}/instances/{instance}/rendered` ||

For rendering the first instance with pixeldata is used

#### Thumbnail Resources

❌ Thumbnail Resources are not supported.
| Description | Path | Support Status |
|------------------|------------------------------------------------------------------|:--------------:|
| Study Instances | `studies/{study}/thumbnail` ||
| Series Instances | `studies/{study}/series/{series}/thumbnail` ||
| Instance | `studies/{study}/series/{series}/instances/{instance}/thumbnail` ||

The thumbnail resources just perform a 303 redirect to their rendered counterparts

#### Bulkdata Resources

Expand Down
102 changes: 85 additions & 17 deletions src/api/wado/routes.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
use crate::api::wado::RetrieveInstanceRequest;
use crate::api::wado::{RenderedRequest, RetrieveInstanceRequest, ThumbnailRequest};
use crate::backend::dimse::wado::DicomMultipartStream;
use crate::backend::ServiceProvider;
use crate::types::UI;
use crate::AppState;
use axum::body::Body;
use axum::http::header::{CONTENT_DISPOSITION, CONTENT_TYPE};
use axum::http::{Response, StatusCode};
use axum::response::IntoResponse;
use axum::http::{Response, StatusCode, Uri};
use axum::response::{IntoResponse, Redirect};
use axum::routing::get;
use axum::Router;
use dicom_pixeldata::image::ImageFormat;
use futures::{StreamExt, TryStreamExt};
use std::pin::Pin;
use tracing::{error, instrument};
Expand Down Expand Up @@ -96,6 +97,45 @@ async fn instance_resource(
}
}

async fn rendered_resource(
provider: ServiceProvider,
request: RenderedRequest,
) -> impl IntoResponse {
if let Some(wado) = provider.wado {
let response = wado.render(request).await;

match response {
Ok(response) => {
let image = response.image;

// Write the image to a buffer (JPEG)
let mut img_buf = Vec::new();
if let Err(err) =
image.write_to(&mut std::io::Cursor::new(&mut img_buf), ImageFormat::Jpeg)
{
error!("{err:?}");
return (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into_response();
}

Response::builder()
.header(CONTENT_TYPE, "image/jpeg")
.body(Body::from(img_buf))
.unwrap()
}
Err(err) => {
error!("{err:?}");
(StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into_response()
}
}
} else {
(
StatusCode::SERVICE_UNAVAILABLE,
"WADO-RS endpoint is disabled",
)
.into_response()
}
}

#[instrument(skip_all)]
async fn study_instances(
provider: ServiceProvider,
Expand Down Expand Up @@ -132,32 +172,60 @@ async fn instance_metadata() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}

async fn rendered_study() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
#[instrument(skip_all)]
async fn rendered_study(provider: ServiceProvider, request: RenderedRequest) -> impl IntoResponse {
rendered_resource(provider, request).await
}

async fn rendered_series() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
#[instrument(skip_all)]
async fn rendered_series(provider: ServiceProvider, request: RenderedRequest) -> impl IntoResponse {
rendered_resource(provider, request).await
}

async fn rendered_instance() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
#[instrument(skip_all)]
async fn rendered_instance(
provider: ServiceProvider,
request: RenderedRequest,
) -> impl IntoResponse {
rendered_resource(provider, request).await
}

async fn rendered_frames() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
#[instrument(skip_all)]
async fn rendered_frames(provider: ServiceProvider, request: RenderedRequest) -> impl IntoResponse {
rendered_resource(provider, request).await
}

async fn study_thumbnail() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
async fn study_thumbnail(request: ThumbnailRequest, uri: Uri) -> impl IntoResponse {
// Redirect to the /rendered endpoint
Redirect::to(&format!(
"/aets/{aet}/studies/{study}/rendered?{query}",
aet = request.query.aet,
study = request.query.study_instance_uid,
query = uri.query().unwrap_or_default()
))
}

async fn series_thumbnail() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
async fn series_thumbnail(request: ThumbnailRequest, uri: Uri) -> impl IntoResponse {
// Redirect to the /rendered endpoint
Redirect::to(&format!(
"/aets/{aet}/studies/{study}/series/{series}/rendered?{query}",
aet = request.query.aet,
study = request.query.study_instance_uid,
series = request.query.series_instance_uid.unwrap_or_default(),
query = uri.query().unwrap_or_default()
))
}

async fn instance_thumbnail() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
async fn instance_thumbnail(request: ThumbnailRequest, uri: Uri) -> impl IntoResponse {
// Redirect to the /rendered endpoint
Redirect::to(&format!(
"/aets/{aet}/studies/{study}/series/{series}/instances/{instance}/rendered?{query}",
aet = request.query.aet,
study = request.query.study_instance_uid,
series = request.query.series_instance_uid.unwrap_or_default(),
instance = request.query.sop_instance_uid.unwrap_or_default(),
query = uri.query().unwrap_or_default()
))
}

async fn frame_thumbnail() -> impl IntoResponse {
Expand Down
Loading

0 comments on commit e282b88

Please sign in to comment.