From 153fd83c654636e2b4dd80fa9a25e64334fce410 Mon Sep 17 00:00:00 2001 From: Stephan Vedder Date: Wed, 28 Feb 2024 10:10:05 +0100 Subject: [PATCH] Add instance frame retrieval --- Cargo.lock | 1 + server/Cargo.toml | 1 + server/src/actix/mod.rs | 1 + server/src/actix/multipart/writer.rs | 13 ++++++- server/src/actix/wado.rs | 54 +++++++++++++++++++++++++++- 5 files changed, 68 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 933ead9..0020f44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -820,6 +820,7 @@ dependencies = [ "dicom", "dicom-json", "dicom-object", + "dicom-pixeldata", "futures-util", "httparse", "local-waker", diff --git a/server/Cargo.toml b/server/Cargo.toml index c6aa878..38a6d92 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -18,6 +18,7 @@ derive_more = "0.99.17" dicom = "0.6.3" dicom-json = "0.1.1" dicom-object = "0.6.3" +dicom-pixeldata = { version = "0.2.2", features = ["image"] } futures-util = "0.3.30" httparse = "1.8.0" local-waker = "0.1.4" diff --git a/server/src/actix/mod.rs b/server/src/actix/mod.rs index aba4b0d..c2b6323 100644 --- a/server/src/actix/mod.rs +++ b/server/src/actix/mod.rs @@ -24,6 +24,7 @@ pub fn dicomweb_config(cfg: &mut actix_web::web::ServiceConfig) { .service(search_instances_series_level) .service(retrieve_instance) .service(retrieve_instance_metadata) + .service(retrieve_instance_frames) .service(retrieve_series) .service(retrieve_series_metadata) .service(retrieve_study) diff --git a/server/src/actix/multipart/writer.rs b/server/src/actix/multipart/writer.rs index 8120cc7..cb70458 100644 --- a/server/src/actix/multipart/writer.rs +++ b/server/src/actix/multipart/writer.rs @@ -25,7 +25,7 @@ impl MultipartWriter { } pub fn add(self: &mut Self, mut reader: impl Read, headers: &str) -> io::Result { - // writer for the result + // writer for our buffer let mut writer = std::io::BufWriter::new(&mut self.data); // write the boundary @@ -47,4 +47,15 @@ impl MultipartWriter { // write the content io::copy(&mut reader, &mut writer) } + + pub fn finish(self: &mut Self) { + // writer for our buffer + let mut writer = std::io::BufWriter::new(&mut self.data); + + // write the final boundary + writer.write_all(b"\r\n").unwrap(); + writer.write_all(b"--").unwrap(); + writer.write_all(self.boundary.as_bytes()).unwrap(); + writer.write_all(b"--").unwrap(); + } } diff --git a/server/src/actix/wado.rs b/server/src/actix/wado.rs index e3e8327..ba2f002 100644 --- a/server/src/actix/wado.rs +++ b/server/src/actix/wado.rs @@ -1,9 +1,10 @@ -use std::io::Write; +use std::{io::Write}; use actix_web::{get, web, HttpResponse, Responder}; use dicom::dictionary_std::tags; use dicom_json::DicomJson; use dicom_object::InMemDicomObject; +use dicom_pixeldata::PixelDecoder; use crate::{actix::MultipartWriter, DicomWebServer}; @@ -33,6 +34,9 @@ pub async fn retrieve_study( } } + // Finish the multipart stream + mp.finish(); + let content_type = format!( "multipart/related; type=application/dicom; boundary={}", mp.boundary @@ -94,6 +98,9 @@ pub async fn retrieve_series( } } + // Finish the multipart stream + mp.finish(); + let content_type = format!( "multipart/related; type=application/dicom; boundary={}", mp.boundary @@ -154,6 +161,9 @@ pub async fn retrieve_instance( return HttpResponse::InternalServerError().body(e.to_string()); } + // Finish the multipart stream + mp.finish(); + let content_type = format!( "multipart/related; type=application/dicom; boundary={}", mp.boundary @@ -184,11 +194,53 @@ pub async fn retrieve_instance_metadata( } } +#[get("/studies/{study_uid}/series/{series_uid}/instances/{instance_uid}/frames/{frame_list}")] +pub async fn retrieve_instance_frames( + callbacks: web::Data, + path: web::Path<(String, String, String, String)>, +) -> impl Responder { + let (study_uid, series_uid, instance_uid, _frame_list) = path.into_inner(); + let result = (callbacks.retrieve_instance)(&study_uid, &series_uid, &instance_uid); + + match result { + Ok(dcm_file) => { + // TODO: use the framelist to extract the frames from the pixel data + if let Ok(pixel_data) = dcm_file.decode_pixel_data() { + let mut mp = MultipartWriter::new(); + let mut data: Vec = Vec::new(); + + // Write the pixel data to memory and add it to our stream + if let Err(e) = data.write_all(&pixel_data.data()) { + return HttpResponse::InternalServerError().body(e.to_string()); + } + + if let Err(e) = mp.add(&*data, "Content-Type: application/octet-stream") { + return HttpResponse::InternalServerError().body(e.to_string()); + } + + // Finish the multipart stream + mp.finish(); + + let content_type = format!( + "multipart/related; type=application/octet-stream; boundary={}", + mp.boundary + ); + + return HttpResponse::Ok().content_type(content_type).body(mp.data); + } else { + return HttpResponse::InternalServerError().body("No pixel data found"); + } + } + Err(e) => return HttpResponse::InternalServerError().body(e.to_string()), + } +} + pub fn wado_config(cfg: &mut web::ServiceConfig) { cfg.service(retrieve_study) .service(retrieve_study_metadata) .service(retrieve_series) .service(retrieve_series_metadata) .service(retrieve_instance) + .service(retrieve_instance_frames) .service(retrieve_instance_metadata); }