Skip to content

Commit 7b32335

Browse files
authored
Merge pull request #27 from mbits-imaging/mwwl-proposal
feat(mwl): Implement MWL-RS proposal
2 parents 5dc3e36 + ef210c5 commit 7b32335

8 files changed

Lines changed: 534 additions & 1 deletion

File tree

src/api/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use axum::Router;
33

44
mod aets;
55
mod home;
6+
pub mod mwl;
67
pub mod qido;
78
pub mod stow;
89
pub mod wado;
@@ -16,7 +17,8 @@ pub fn routes(base_path: &str) -> Router<AppState> {
1617
Router::new()
1718
.merge(qido::routes())
1819
.merge(wado::routes())
19-
.merge(stow::routes()),
20+
.merge(stow::routes())
21+
.merge(mwl::routes()),
2022
);
2123

2224
// axum no longer supports nesting at the root

src/api/mwl/mod.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
mod routes;
2+
mod service;
3+
4+
pub use routes::routes;
5+
pub use service::*;
6+
7+
use dicom::core::Tag;
8+
use dicom::dictionary_std::tags;
9+
10+
/// <https://dicom.nema.org/medical/dicom/current/output/chtml/part04/sect_K.6.html#table_K.6-1>
11+
pub const WORKITEM_SEARCH_TAGS: &[Tag] = &[
12+
// Scheduled Procedure Step
13+
tags::SCHEDULED_PROCEDURE_STEP_SEQUENCE,
14+
tags::SCHEDULED_STATION_AE_TITLE,
15+
tags::SCHEDULED_PROCEDURE_STEP_START_DATE,
16+
tags::SCHEDULED_PROCEDURE_STEP_START_TIME,
17+
tags::MODALITY,
18+
tags::SCHEDULED_PERFORMING_PHYSICIAN_NAME,
19+
tags::SCHEDULED_PROCEDURE_STEP_DESCRIPTION,
20+
tags::SCHEDULED_STATION_NAME,
21+
tags::SCHEDULED_PROCEDURE_STEP_LOCATION,
22+
tags::REFERENCED_DEFINED_PROTOCOL_SEQUENCE,
23+
tags::REFERENCED_SOP_CLASS_UID,
24+
tags::REFERENCED_SOP_INSTANCE_UID,
25+
// Requested Procedure
26+
tags::REQUESTED_PROCEDURE_ID,
27+
tags::REQUESTED_PROCEDURE_DESCRIPTION,
28+
tags::REQUESTED_PROCEDURE_CODE_SEQUENCE,
29+
tags::STUDY_INSTANCE_UID,
30+
tags::STUDY_DATE,
31+
tags::STUDY_TIME,
32+
// Patient Identification
33+
tags::PATIENT_NAME,
34+
tags::PATIENT_ID,
35+
tags::ISSUER_OF_PATIENT_ID,
36+
// Patient Demographics
37+
tags::PATIENT_BIRTH_DATE,
38+
tags::PATIENT_SEX,
39+
];

src/api/mwl/routes.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
use crate::backend::ServiceProvider;
2+
use crate::AppState;
3+
use axum::http::header;
4+
use axum::http::StatusCode;
5+
use axum::response::IntoResponse;
6+
use axum::routing::get;
7+
use axum::Router;
8+
use axum_extra::extract::Query;
9+
use axum_streams::StreamBodyAs;
10+
use dicom::object::InMemDicomObject;
11+
use dicom_json::DicomJson;
12+
use futures::TryStreamExt;
13+
use tracing::instrument;
14+
15+
use super::{MwlQueryParameters, MwlRequestHeaderFields, MwlSearchError, MwlSearchRequest};
16+
17+
/// HTTP Router for the Modality Worklist.
18+
///
19+
/// <https://www.dicomstandard.org/news-dir/current/docs/sups/sup246.pdf>
20+
#[rustfmt::skip]
21+
pub fn routes() -> Router<AppState> {
22+
Router::new()
23+
.route("/modality-scheduled-procedure-steps", get(all_workitems))
24+
}
25+
26+
// MWL-RS implementation
27+
async fn mwl_handler(provider: ServiceProvider, request: MwlSearchRequest) -> impl IntoResponse {
28+
if let Some(mwl) = provider.mwl {
29+
let response = mwl.search(request).await;
30+
let matches: Result<Vec<InMemDicomObject>, MwlSearchError> =
31+
response.stream.try_collect().await;
32+
33+
match matches {
34+
Ok(matches) => {
35+
if matches.is_empty() {
36+
StatusCode::NO_CONTENT.into_response()
37+
} else {
38+
let json: Vec<DicomJson<InMemDicomObject>> =
39+
matches.into_iter().map(DicomJson::from).collect();
40+
41+
axum::response::Response::builder()
42+
.status(StatusCode::OK)
43+
.header(header::CONTENT_TYPE, mime::APPLICATION_JSON.as_ref())
44+
.body(StreamBodyAs::json_array(futures::stream::iter(json)))
45+
.unwrap()
46+
.into_response()
47+
}
48+
}
49+
Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into_response(),
50+
}
51+
} else {
52+
(
53+
StatusCode::SERVICE_UNAVAILABLE,
54+
"MWL-RS endpoint is disabled",
55+
)
56+
.into_response()
57+
}
58+
}
59+
60+
#[instrument(skip_all)]
61+
async fn all_workitems(
62+
provider: ServiceProvider,
63+
Query(parameters): Query<MwlQueryParameters>,
64+
) -> impl IntoResponse {
65+
let request = MwlSearchRequest {
66+
parameters,
67+
headers: MwlRequestHeaderFields::default(),
68+
};
69+
mwl_handler(provider, request).await
70+
}

0 commit comments

Comments
 (0)