DICOM Server rs v1.0.0 published

Announcing dicom-server-rs v0.1.0: A High-Performance DICOM Store SCP in Rust We are excited to announce the initial release of dicom-server-rs, a lightweight, high-performance DICOM server (Store SCP) built entirely in Rust. In the world of medical imaging, reliability and speed are non-negotiable. By leveraging the Rust ecosystem, dicom-server-rs provides a modern alternative for receiving and managing DICOM files with a focus on safety and efficiency. 🚀 Key Features Native Rust Implementation: Built using the dicom-rs ecosystem for robust parsing and protocol handling. ...

January 6, 2026

How to Build Scalable Cloud DICOM-WEB Services | DICOM Cloud

How to Build Scalable Cloud DICOM-WEB Services Learn how to build distributed DICOM-WEB services using open-source projects. Overall Architecture Apache Kafka as message queue. RedPanda can be used as alternative during development. Apache Doris as data warehouse. Provides storage for DicomStateMeta, DicomImageMeta, and WadoAccessLog, offering query and statistical analysis. PostgreSQL as database. Provides data storage and indexing functions. Stores only patient, study, and series level metadata to fully leverage ACID properties of relational databases. Can be scaled later with Citus. Redis as cache. Provides data caching functionality. Nginx as reverse proxy server. Provides load balancing, static files, and TLS termination. Files received by the storage service are first stored locally, then sent to the message queue via Kafka. ...

November 19, 2025

How to Use Multi-Related Stream to Upload DICOM Files | Cloud DICOM-WEB Service

How to Use Multi-Related Stream to Upload DICOM Files Implementing Multi-File Upload with CURL Scripts or .NET Core Applications During STOW-RS development, to test RESTful API interfaces, we created a CURL script that uploads multiple DICOM files. It’s important to note that RESTful interfaces require uploads to be in multipart/related format with Content-Type: multipart/related;boundary=DICOM_BOUNDARY;type=application/dicom. CURL Script Implementation #!/bin/bash # Check parameters if [ $# -eq 0 ]; then echo "Usage: $0 <dicom_directory>" echo "Example: $0 ~/amprData" exit 1 fi DICOM_DIR="$1" # Check if directory exists if [ ! -d "$DICOM_DIR" ]; then echo "Error: Directory '$DICOM_DIR' does not exist" exit 1 fi BOUNDARY="DICOM_BOUNDARY" TEMP_FILE="multipart_request_largdata.tmp" # Check for DICOM files DICOM_FILES=($(find "$DICOM_DIR" -type f -name "*.dcm")) if [ ${#DICOM_FILES[@]} -eq 0 ]; then echo "Warning: No DICOM files found in '$DICOM_DIR'" exit 0 fi echo "Found ${#DICOM_FILES[@]} DICOM files" # 1. Initialize file (without JSON part) > "$TEMP_FILE" # 2. Loop through all DICOM files (first file doesn't need prefix separator) for i in "${!DICOM_FILES[@]}"; do dicom_file="${DICOM_FILES[$i]}" # Add prefix separator for all files except the first if [ $i -gt 0 ]; then printf -- "\r\n--%s\r\n" "$BOUNDARY" >> "$TEMP_FILE" else # First file needs starting separator printf -- "--%s\r\n" "$BOUNDARY" >> "$TEMP_FILE" fi printf -- "Content-Type: application/dicom\r\n\r\n" >> "$TEMP_FILE" # Append DICOM file content cat "$dicom_file" >> "$TEMP_FILE" echo "Added file: $(basename "$dicom_file")" done # 3. Write ending separator for request body printf -- "\r\n--%s--\r\n" "$BOUNDARY" >> "$TEMP_FILE" # 4. Calculate file size CONTENT_LENGTH=$(wc -c < "$TEMP_FILE" | tr -d ' ') echo "Total content length: $CONTENT_LENGTH bytes" # 5. Send request curl -X POST http://localhost:9000/stow-rs/v1/studies \ -H "Content-Type: multipart/related; boundary=$BOUNDARY; type=application/dicom" \ -H "Accept: application/json" \ -H "x-tenant: 1234567890" \ -H "Content-Length: $CONTENT_LENGTH" \ --data-binary @"$TEMP_FILE" # 6. Clean up temporary file rm "$TEMP_FILE" echo "Upload completed" .NET Core Implementation using System.Text; namespace MakeMultirelate { public class ConstructPostRequest : IDisposable { private const string Boundary = "DICOM_BOUNDARY"; private readonly HttpClient _httpClient = new(); public async Task SendDicomFilesAsync(List<string> dicomFilePaths, string url, string tenantId) { // Estimate memory stream size for performance optimization long estimatedSize = 0; foreach (var filePath in dicomFilePaths) { if (!File.Exists(filePath)) throw new FileNotFoundException($"DICOM file not found: {filePath}"); // Get file size and accumulate var fileInfo = new FileInfo(filePath); estimatedSize += fileInfo.Length; } // Add estimated size for boundaries and headers (approximately 200 bytes per file for separators and headers) estimatedSize += dicomFilePaths.Count * 200; // Add end boundary size estimatedSize += Boundary.Length + 10; // Create memory stream to build multipart content with estimated size initialization using var memoryStream = new MemoryStream((int)Math.Min(estimatedSize, int.MaxValue)); // Build multipart content foreach (var filePath in dicomFilePaths) { if (!File.Exists(filePath)) throw new FileNotFoundException($"DICOM file not found: {filePath}"); // Add separator and header var separator = Encoding.UTF8.GetBytes($"\r\n--{Boundary}\r\n"); var header = Encoding.UTF8.GetBytes("Content-Type: application/dicom\r\n\r\n"); if (memoryStream.Length == 0) { // First part doesn't need leading separator separator = Encoding.UTF8.GetBytes($"--{Boundary}\r\n"); } await memoryStream.WriteAsync(separator, 0, separator.Length); await memoryStream.WriteAsync(header, 0, header.Length); // Read and add DICOM file content var fileBytes = await File.ReadAllBytesAsync(filePath); await memoryStream.WriteAsync(fileBytes, 0, fileBytes.Length); } // Add ending separator var endBoundary = Encoding.UTF8.GetBytes($"\r\n--{Boundary}--\r\n"); await memoryStream.WriteAsync(endBoundary, 0, endBoundary.Length); // Prepare request content var content = new ByteArrayContent(memoryStream.ToArray()); content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("multipart/related"); content.Headers.ContentType.Parameters.Add( new System.Net.Http.Headers.NameValueHeaderValue("boundary", Boundary)); content.Headers.ContentType.Parameters.Add( new System.Net.Http.Headers.NameValueHeaderValue("type", "application/dicom")); // Set request headers _httpClient.DefaultRequestHeaders.Clear(); _httpClient.DefaultRequestHeaders.Add("Accept", "application/json"); _httpClient.DefaultRequestHeaders.Add("x-tenant", tenantId); // Send request var response = await _httpClient.PostAsync(url, content); // Process response var responseContent = await response.Content.ReadAsStringAsync(); Console.WriteLine($"Status Code: {response.StatusCode}"); Console.WriteLine($"Response: {responseContent}"); } // Overloaded function: Recursively find DICOM files based on directory public async Task SendDicomFilesAsync(string dicomDirectory, string url, string tenantId) { if (!Directory.Exists(dicomDirectory)) throw new DirectoryNotFoundException($"DICOM directory not found: {dicomDirectory}"); // Recursively find all files with .dcm extension var dicomFiles = Directory.GetFiles(dicomDirectory, "*.dcm", SearchOption.AllDirectories); // If no files found, throw exception if (dicomFiles.Length == 0) throw new FileNotFoundException($"No DICOM files found in directory: {dicomDirectory}"); // Call original method to send files await SendDicomFilesAsync(dicomFiles.ToList(), url, tenantId); } public void Dispose() { _httpClient.Dispose(); } } } Key Implementation Points Multipart/Related Format Content-Type must be multipart/related;boundary=DICOM_BOUNDARY;type=application/dicom Each DICOM file requires proper boundary delimiters First file needs a starting boundary, subsequent files need separator boundaries ...

December 11, 2025

Rust Implementation of DICOM Medical Imaging Systems: Type-Safe Database Design | Building Scalable Cloud DICOM-WEB Services

Rust Implementation of DICOM Medical Imaging Systems: Type-Safe Database Design This project demonstrates how to build a robust, type-safe database access layer in Rust, specifically designed for healthcare applications requiring strict data validation. Through abstract interfaces and concrete implementation separation, the system can easily scale to support more database types while ensuring code maintainability and testability. Core Design Concepts 1. Type-Safe Data Structures Several key type-safe wrappers are defined in the project: ...

November 20, 2025

Preparations for Building Scalable Cloud DICOM-WEB Services

Preparations for Building Scalable Cloud DICOM-WEB Services This article introduces the architectural design of a DICOM medical imaging system developed using Rust, which employs a modern technology stack including PostgreSQL as the primary index database, Apache Doris for log storage, RedPanda as the message queue, and Redis for caching. The system design supports both standalone operation and distributed scaling, fully leveraging the safety and performance advantages of the Rust programming language. ...

November 19, 2025

How to Use fo-dicom to Build DICOM C-Store SCU Tool for Batch Sending DICOM Files

How to Use fo-dicom to Build DICOM C-Store SCU Tool for Batch Sending DICOM Files Building a DICOM C-Store SCU (Service Class User) tool is essential for medical imaging applications that require sending DICOM files to storage systems. This tutorial will guide you through creating a robust batch DICOM file sender using fo-dicom, a powerful open-source DICOM library written in C#. Fo-DICOM provides comprehensive features for working with DICOM data, including support for reading, writing, and manipulating DICOM files. This guide demonstrates how to create a DICOM C-Store SCU tool capable of batch sending DICOM files for testing and verification purposes in medical imaging environments. ...

November 19, 2025