Medical Imaging Certificate Management - Secure Healthcare System Authentication

Certificate Management System for Medical Imaging The cert_helper.rs module provides comprehensive certificate management capabilities for securing medical imaging systems, implementing Public Key Infrastructure (PKI) with X.509 certificates and OpenSSL integration. Security Architecture Certificate Authority (CA) Management Implements a complete CA system for issuing and managing certificates within medical imaging environments. Client Certificate Generation Generates client certificates with machine-specific bindings for enhanced security. Server Certificate Management Creates server certificates for HTTPS and DICOM TLS connections. ...

November 23, 2025

Redis 缓存 |Redis Key Management for DICOM Server - High Performance Caching

Redis Key Management in DICOM Server The redis_key.rs module is a critical component of the DICOM server infrastructure, providing high-performance caching capabilities for medical imaging metadata and authentication data. This module implements intelligent key generation and caching strategies to significantly reduce database load and improve response times. Core Features Authentication Caching The module efficiently caches JWKS (JSON Web Key Set) URLs, which are essential for OAuth2/OpenID Connect authentication in medical imaging systems. This reduces authentication overhead and improves security token validation performance. ...

November 23, 2025

消息队列|Kafka Message Processing for Medical Imaging - Scalable Healthcare Data Streaming

Kafka Message Processing System for Medical Imaging The [message_sender_kafka.rs] module provides robust Kafka integration for medical imaging systems, enabling scalable, reliable message processing for DICOM metadata and imaging workflows. Message Processing Architecture High-Throughput Design Implements batch processing and compression to handle large volumes of medical imaging metadata with minimal resource consumption. Reliable Delivery Features built-in retry mechanisms and error handling to ensure message delivery even in challenging network conditions. Topic Segregation Uses separate Kafka topics for different message types: ...

November 23, 2025

Building Scalable Cloud DICOM-WEB Services: Multi-Database Support with Rust Implementation

Overview In this article, we’ll explore how to implement a scalable cloud DICOM-WEB service using Rust that supports multiple database systems, including MySQL, PostgreSQL, and MongoDB. We’ll focus on creating a flexible database interface that allows for seamless switching between different database backends. Prerequisites: Interface Definition (Traits) First, let’s define our database provider interface using Rust traits: dicom_dbprovider.rs use crate::dicom_meta::{DicomJsonMeta, DicomStateMeta}; use async_trait::async_trait; use thiserror::Error; #[derive(Error, Debug)] pub enum DbError { #[error("Database operation failed: {0}")] DatabaseError(String), #[error("Data record does not exist: {0}")] RecordNotExists(String), #[error("Record already exists")] AlreadyExists, #[error("Entity extraction failed: {0}")] ExtractionFailed(String), #[error("Transaction failed: {0}")] TransactionFailed(String), } pub fn current_time() -> chrono::NaiveDateTime { chrono::Local::now().naive_local() } #[async_trait] pub trait DbProvider: Send + Sync { async fn save_state_info(&self, state_meta: &DicomStateMeta) -> Result<(), DbError>; async fn save_state_list(&self, state_meta: &[DicomStateMeta]) -> Result<(), DbError>; async fn save_json_list(&self, state_meta: &[DicomJsonMeta]) -> Result<(), DbError>; async fn get_state_metaes( &self, tenant_id: &str, study_uid: &str, ) -> Result<Vec<DicomStateMeta>, DbError>; /* * Get metadata series information that needs to generate JSON format. * end_time: cutoff time. */ async fn get_json_metaes(&self, end_time: chrono::NaiveDateTime) -> Result<Vec<DicomStateMeta>, DbError>; async fn get_json_meta(&self, tenant_id:&str, study_uid: &str, series_uid: &str)->Result<DicomJsonMeta, DbError>; } Implementation 3.1 MySQL Database Implementation dicom_mysql.rs use crate::dicom_dbprovider::{DbError, DbProvider}; use crate::dicom_meta::{DicomJsonMeta, DicomStateMeta}; use async_trait::async_trait; use mysql::prelude::*; use mysql::*; pub struct MySqlDbProvider { db_connection_string: String, } impl MySqlDbProvider { pub fn new(db_connection_string: String) -> Self { MySqlDbProvider { db_connection_string, } } } #[async_trait] impl DbProvider for MySqlDbProvider { async fn save_state_info(&self, state_meta: &DicomStateMeta) -> Result<(), DbError> { let mut conn = mysql::Conn::new(self.db_connection_string.as_str()) .map_err(|e| DbError::DatabaseError(format!("Failed to connect to MySQL: {}", e)))?; let query = r#" INSERT INTO dicom_state_meta ( tenant_id, patient_id, study_uid, series_uid, study_uid_hash, series_uid_hash, study_date_origin, patient_name, patient_sex, patient_birth_date, patient_birth_time, patient_age, patient_size, patient_weight, study_date, study_time, accession_number, study_id, study_description, modality, series_number, series_date, series_time, series_description, body_part_examined, protocol_name, series_related_instances, created_time, updated_time ) VALUES ( :tenant_id, :patient_id, :study_uid, :series_uid, :study_uid_hash, :series_uid_hash, :study_date_origin, :patient_name, :patient_sex, :patient_birth_date, :patient_birth_time, :patient_age, :patient_size, :patient_weight, :study_date, :study_time, :accession_number, :study_id, :study_description, :modality, :series_number, :series_date, :series_time, :series_description, :body_part_examined, :protocol_name, :series_related_instances, :created_time, :updated_time ) ON DUPLICATE KEY UPDATE patient_id = VALUES(patient_id), study_uid_hash = VALUES(study_uid_hash), series_uid_hash = VALUES(series_uid_hash), study_date_origin = VALUES(study_date_origin), patient_name = VALUES(patient_name), patient_sex = VALUES(patient_sex), patient_birth_date = VALUES(patient_birth_date), patient_birth_time = VALUES(patient_birth_time), patient_age = VALUES(patient_age), patient_size = VALUES(patient_size), patient_weight = VALUES(patient_weight), study_date = VALUES(study_date), study_time = VALUES(study_time), accession_number = VALUES(accession_number), study_id = VALUES(study_id), study_description = VALUES(study_description), modality = VALUES(modality), series_number = VALUES(series_number), series_date = VALUES(series_date), series_time = VALUES(series_time), series_description = VALUES(series_description), body_part_examined = VALUES(body_part_examined), protocol_name = VALUES(protocol_name), series_related_instances = VALUES(series_related_instances), updated_time = VALUES(updated_time) "#; conn.exec_drop( query, params! { "tenant_id" => &state_meta.tenant_id, "patient_id" => &state_meta.patient_id, "study_uid" => &state_meta.study_uid, "series_uid" => &state_meta.series_uid, "study_uid_hash" => &state_meta.study_uid_hash, "series_uid_hash" => &state_meta.series_uid_hash, "study_date_origin" => &state_meta.study_date_origin, "patient_name" => &state_meta.patient_name, "patient_sex" => &state_meta.patient_sex, "patient_birth_date" => &state_meta.patient_birth_date, "patient_birth_time" => &state_meta.patient_birth_time, "patient_age" => &state_meta.patient_age, "patient_size" => &state_meta.patient_size, "patient_weight" => &state_meta.patient_weight, "study_date" => &state_meta.study_date, "study_time" => &state_meta.study_time, "accession_number" => &state_meta.accession_number, "study_id" => &state_meta.study_id, "study_description" => &state_meta.study_description, "modality" => &state_meta.modality, "series_number" => &state_meta.series_number, "series_date" => &state_meta.series_date, "series_time" => &state_meta.series_time, "series_description" => &state_meta.series_description, "body_part_examined" => &state_meta.body_part_examined, "protocol_name" => &state_meta.protocol_name, "series_related_instances" => &state_meta.series_related_instances, "created_time" => &state_meta.created_time, "updated_time" => &state_meta.updated_time, }, ) .map_err(|e| DbError::DatabaseError(format!("Failed to execute query: {}", e)))?; Ok(()) } async fn save_state_list(&self, state_meta_list: &[DicomStateMeta]) -> Result<(), DbError> { if state_meta_list.is_empty() { return Ok(()); } let mut conn = mysql::Conn::new(self.db_connection_string.as_str()) .map_err(|e| DbError::DatabaseError(format!("Failed to connect to MySQL: {}", e)))?; conn.query_drop("START TRANSACTION") .map_err(|e| DbError::DatabaseError(format!("Failed to start transaction: {}", e)))?; let query = r#" INSERT INTO dicom_state_meta ( tenant_id, patient_id, study_uid, series_uid, study_uid_hash, series_uid_hash, study_date_origin, patient_name, patient_sex, patient_birth_date, patient_birth_time, patient_age, patient_size, patient_weight, study_date, study_time, accession_number, study_id, study_description, modality, series_number, series_date, series_time, series_description, body_part_examined, protocol_name, series_related_instances, created_time, updated_time ) VALUES ( :tenant_id, :patient_id, :study_uid, :series_uid, :study_uid_hash, :series_uid_hash, :study_date_origin, :patient_name, :patient_sex, :patient_birth_date, :patient_birth_time, :patient_age, :patient_size, :patient_weight, :study_date, :study_time, :accession_number, :study_id, :study_description, :modality, :series_number, :series_date, :series_time, :series_description, :body_part_examined, :protocol_name, :series_related_instances, :created_time, :updated_time ) ON DUPLICATE KEY UPDATE patient_id = VALUES(patient_id), study_uid_hash = VALUES(study_uid_hash), series_uid_hash = VALUES(series_uid_hash), study_date_origin = VALUES(study_date_origin), patient_name = VALUES(patient_name), patient_sex = VALUES(patient_sex), patient_birth_date = VALUES(patient_birth_date), patient_birth_time = VALUES(patient_birth_time), patient_age = VALUES(patient_age), patient_size = VALUES(patient_size), patient_weight = VALUES(patient_weight), study_date = VALUES(study_date), study_time = VALUES(study_time), accession_number = VALUES(accession_number), study_id = VALUES(study_id), study_description = VALUES(study_description), modality = VALUES(modality), series_number = VALUES(series_number), series_date = VALUES(series_date), series_time = VALUES(series_time), series_description = VALUES(series_description), body_part_examined = VALUES(body_part_examined), protocol_name = VALUES(protocol_name), series_related_instances = VALUES(series_related_instances), updated_time = VALUES(updated_time) "#; for state_meta in state_meta_list { let result = conn.exec_drop( query, params! { "tenant_id" => &state_meta.tenant_id, "patient_id" => &state_meta.patient_id, "study_uid" => &state_meta.study_uid, "series_uid" => &state_meta.series_uid, "study_uid_hash" => &state_meta.study_uid_hash, "series_uid_hash" => &state_meta.series_uid_hash, "study_date_origin" => &state_meta.study_date_origin, "patient_name" => &state_meta.patient_name, "patient_sex" => &state_meta.patient_sex, "patient_birth_date" => &state_meta.patient_birth_date, "patient_birth_time" => &state_meta.patient_birth_time, "patient_age" => &state_meta.patient_age, "patient_size" => &state_meta.patient_size, "patient_weight" => &state_meta.patient_weight, "study_date" => &state_meta.study_date, "study_time" => &state_meta.study_time, "accession_number" => &state_meta.accession_number, "study_id" => &state_meta.study_id, "study_description" => &state_meta.study_description, "modality" => &state_meta.modality, "series_number" => &state_meta.series_number, "series_date" => &state_meta.series_date, "series_time" => &state_meta.series_time, "series_description" => &state_meta.series_description, "body_part_examined" => &state_meta.body_part_examined, "protocol_name" => &state_meta.protocol_name, "series_related_instances" => &state_meta.series_related_instances, "created_time" => &state_meta.created_time, "updated_time" => &state_meta.updated_time, }, ); if let Err(e) = result { conn.query_drop("ROLLBACK").map_err(|rollback_err| { DbError::DatabaseError(format!( "Failed to rollback transaction after error {}: {}", e, rollback_err )) })?; return Err(DbError::DatabaseError(format!( "Failed to execute query for state meta: {}", e ))); } } conn.query_drop("COMMIT") .map_err(|e| DbError::DatabaseError(format!("Failed to commit transaction: {}", e)))?; Ok(()) } async fn save_json_list( &self, json_meta_list: &[DicomJsonMeta], ) -> std::result::Result<(), DbError> { if json_meta_list.is_empty() { return Ok(()); } let mut conn = mysql::Conn::new(self.db_connection_string.as_str()) .map_err(|e| DbError::DatabaseError(format!("Failed to connect to MySQL: {}", e)))?; conn.query_drop("START TRANSACTION") .map_err(|e| DbError::DatabaseError(format!("Failed to start transaction: {}", e)))?; let query = r#" INSERT INTO dicom_json_meta ( tenant_id, study_uid, series_uid, study_uid_hash, series_uid_hash, study_date_origin, created_time, flag_time, json_status, retry_times ) VALUES ( :tenant_id, :study_uid, :series_uid, :study_uid_hash, :series_uid_hash, :study_date_origin, :created_time, :flag_time, :json_status, :retry_times ) ON DUPLICATE KEY UPDATE study_uid_hash = VALUES(study_uid_hash), series_uid_hash = VALUES(series_uid_hash), study_date_origin = VALUES(study_date_origin), created_time = VALUES(created_time), flag_time = VALUES(flag_time), json_status = VALUES(json_status), retry_times = VALUES(retry_times) "#; for json_meta in json_meta_list { let result = conn.exec_drop( query, params! { "tenant_id" => &json_meta.tenant_id, "study_uid" => &json_meta.study_uid, "series_uid" => &json_meta.series_uid, "study_uid_hash" => &json_meta.study_uid_hash, "series_uid_hash" => &json_meta.series_uid_hash, "study_date_origin" => &json_meta.study_date_origin, "created_time" => &json_meta.created_time, "flag_time" => &json_meta.flag_time, "json_status" => &json_meta.json_status, "retry_times" => &json_meta.retry_times, }, ); if let Err(e) = result { conn.query_drop("ROLLBACK").map_err(|rollback_err| { DbError::DatabaseError(format!( "Failed to rollback transaction after error {}: {}", e, rollback_err )) })?; return Err(DbError::DatabaseError(format!( "Failed to execute query for json meta: {}", e ))); } } conn.query_drop("COMMIT") .map_err(|e| DbError::DatabaseError(format!("Failed to commit transaction: {}", e)))?; Ok(()) } async fn get_state_metaes( &self, tenant_id: &str, study_uid: &str, ) -> Result<Vec<DicomStateMeta>, DbError> { let mut conn = mysql::Conn::new(self.db_connection_string.as_str()) .map_err(|e| DbError::DatabaseError(format!("Failed to connect to MySQL: {}", e)))?; let query = r#" SELECT tenant_id, patient_id, study_uid, series_uid, study_uid_hash, series_uid_hash, study_date_origin, patient_name, patient_sex, patient_birth_date, patient_birth_time, patient_age, patient_size, patient_weight, study_date, study_time, accession_number, study_id, study_description, modality, series_number, series_date, series_time, series_description, body_part_examined, protocol_name, series_related_instances, created_time, updated_time FROM dicom_state_meta WHERE tenant_id = :tenant_id AND study_uid = :study_uid "#; let result: Vec<DicomStateMeta> = conn .exec_map( query, params! { "tenant_id" => tenant_id, "study_uid" => study_uid, }, |row: mysql::Row| { DicomStateMeta { tenant_id: row.get("tenant_id").unwrap_or_default(), patient_id: row.get("patient_id").unwrap_or_default(), study_uid: row.get("study_uid").unwrap_or_default(), series_uid: row.get("series_uid").unwrap_or_default(), study_uid_hash: row.get("study_uid_hash").unwrap_or_default(), series_uid_hash: row.get("series_uid_hash").unwrap_or_default(), study_date_origin: row.get("study_date_origin").unwrap_or_default(), patient_name: row.get("patient_name").unwrap_or_default(), patient_sex: row.get("patient_sex").unwrap_or_default(), patient_birth_date: row.get("patient_birth_date").unwrap_or_default(), patient_birth_time: row.get("patient_birth_time").unwrap_or_default(), patient_age: row.get("patient_age").unwrap_or_default(), patient_size: row.get("patient_size").unwrap_or_default(), patient_weight: row.get("patient_weight").unwrap_or_default(), study_date: row.get("study_date").unwrap_or_default(), study_time: row.get("study_time").unwrap_or_default(), accession_number: row.get("accession_number").unwrap_or_default(), study_id: row.get("study_id").unwrap_or_default(), study_description: row.get("study_description").unwrap_or_default(), modality: row.get("modality").unwrap_or_default(), series_number: row.get("series_number").unwrap_or_default(), series_date: row.get("series_date").unwrap_or_default(), series_time: row.get("series_time").unwrap_or_default(), series_description: row.get("series_description").unwrap_or_default(), body_part_examined: row.get("body_part_examined").unwrap_or_default(), protocol_name: row.get("protocol_name").unwrap_or_default(), series_related_instances: row .get("series_related_instances") .unwrap_or_default(), created_time: row.get("created_time").unwrap_or_default(), updated_time: row.get("updated_time").unwrap_or_default(), } }, ) .map_err(|e| DbError::DatabaseError(format!("Failed to execute query: {}", e)))?; Ok(result) } async fn get_json_metaes( &self, end_time: chrono::NaiveDateTime, ) -> std::result::Result<Vec<DicomStateMeta>, DbError> { let mut conn = mysql::Conn::new(self.db_connection_string.as_str()) .map_err(|e| DbError::DatabaseError(format!("Failed to connect to MySQL: {}", e)))?; let query = r#" Select tenant_id, patient_id, study_uid, series_uid, study_uid_hash, series_uid_hash, study_date_origin, patient_name, patient_sex, patient_birth_date, patient_birth_time, patient_age, patient_size, patient_weight, study_date, study_time, accession_number, study_id, study_description, modality, series_number, series_date, series_time, series_description, body_part_examined, protocol_name, series_related_instances, created_time, updated_time From (SELECT dsm.* FROM dicom_state_meta dsm LEFT JOIN dicom_json_meta djm ON dsm.tenant_id = djm.tenant_id AND dsm.study_uid = djm.study_uid AND dsm.series_uid = djm.series_uid WHERE djm.tenant_id IS NULL AND dsm.updated_time < ? UNION ALL SELECT dsm.* FROM dicom_state_meta dsm INNER JOIN dicom_json_meta djm ON dsm.tenant_id = djm.tenant_id AND dsm.study_uid = djm.study_uid AND dsm.series_uid = djm.series_uid WHERE dsm.updated_time != djm.flag_time AND dsm.updated_time < ? ) AS t order by t.updated_time asc limit 10; "#; let result: Vec<DicomStateMeta> = conn .exec_map(query, params! { end_time,end_time }, |row: mysql::Row| { DicomStateMeta { tenant_id: row.get("tenant_id").unwrap_or_default(), patient_id: row.get("patient_id").unwrap_or_default(), study_uid: row.get("study_uid").unwrap_or_default(), series_uid: row.get("series_uid").unwrap_or_default(), study_uid_hash: row.get("study_uid_hash").unwrap_or_default(), series_uid_hash: row.get("series_uid_hash").unwrap_or_default(), study_date_origin: row.get("study_date_origin").unwrap_or_default(), patient_name: row.get("patient_name").unwrap_or_default(), patient_sex: row.get("patient_sex").unwrap_or_default(), patient_birth_date: row.get("patient_birth_date").unwrap_or_default(), patient_birth_time: row.get("patient_birth_time").unwrap_or_default(), patient_age: row.get("patient_age").unwrap_or_default(), patient_size: row.get("patient_size").unwrap_or_default(), patient_weight: row.get("patient_weight").unwrap_or_default(), study_date: row.get("study_date").unwrap_or_default(), study_time: row.get("study_time").unwrap_or_default(), accession_number: row.get("accession_number").unwrap_or_default(), study_id: row.get("study_id").unwrap_or_default(), study_description: row.get("study_description").unwrap_or_default(), modality: row.get("modality").unwrap_or_default(), series_number: row.get("series_number").unwrap_or_default(), series_date: row.get("series_date").unwrap_or_default(), series_time: row.get("series_time").unwrap_or_default(), series_description: row.get("series_description").unwrap_or_default(), body_part_examined: row.get("body_part_examined").unwrap_or_default(), protocol_name: row.get("protocol_name").unwrap_or_default(), series_related_instances: row .get("series_related_instances") .unwrap_or_default(), created_time: row.get("created_time").unwrap_or_default(), updated_time: row.get("updated_time").unwrap_or_default(), } }) .map_err(|e| DbError::DatabaseError(format!("Failed to execute query: {}", e)))?; Ok(result) } async fn get_json_meta( &self, tenant_id: &str, study_uid: &str, series_uid: &str, ) -> std::result::Result<DicomJsonMeta, DbError> { let mut conn = mysql::Conn::new(self.db_connection_string.as_str()) .map_err(|e| DbError::DatabaseError(format!("Failed to connect to MySQL: {}", e)))?; let query = r#" SELECT tenant_id, study_uid, series_uid, study_uid_hash, series_uid_hash, study_date_origin, created_time, flag_time, json_status, retry_times FROM dicom_json_meta WHERE series_uid = :series_uid and tenant_id = :tenant_id and study_uid = :study_uid "#; let result: Option<DicomJsonMeta> = conn .exec_first( query, params! { "series_uid" => series_uid, "tenant_id" => tenant_id, "study_uid" => study_uid, }, ) .map_err(|e| DbError::DatabaseError(format!("Failed to execute query: {}", e)))? .map(|row: mysql::Row| DicomJsonMeta { tenant_id: row.get("tenant_id").unwrap_or_default(), study_uid: row.get("study_uid").unwrap_or_default(), series_uid: row.get("series_uid").unwrap_or_default(), study_uid_hash: row.get("study_uid_hash").unwrap_or_default(), series_uid_hash: row.get("series_uid_hash").unwrap_or_default(), study_date_origin: row.get("study_date_origin").unwrap_or_default(), created_time: row.get("created_time").unwrap_or_default(), flag_time: row.get("flag_time").unwrap_or_default(), json_status: row.get("json_status").unwrap_or_default(), retry_times: row.get("retry_times").unwrap_or_default(), }); match result { Some(json_meta) => Ok(json_meta), None => Err(DbError::DatabaseError(format!( "DicomJsonMeta with series_uid {} not found", series_uid ))), } } } 3.2 PostgreSQL Implementation Here’s the PostgreSQL implementation: ...

November 20, 2025

Building DICOM Gateway with FoDICOM: Batch Sending DICOM Files Guide

DICOM Gateway: A Middleware System for Medical Imaging Data Conversion and Routing A DICOM Gateway is a specialized middleware system designed to convert, route, adapt, or process DICOM data streams between different medical information systems. It is typically deployed at network boundaries or system integration points, serving as a “protocol converter,” “data router,” and “data preprocessor.” What is a DICOM Gateway? Conceptually, a DICOM Gateway is the specific application of traditional network “gateways” in the medical imaging domain: ...

November 20, 2025

Rust Type-Safe Wrappers for DICOM Medical Imaging Systems: Building Scalable Cloud DICOM-WEB Services

Introduction In DICOM medical imaging systems, data accuracy and consistency are critical. This article explores how to use Rust’s type system to create custom type-safe wrappers that prevent runtime errors and improve code maintainability. BoundedString: Length-Limited Strings BoundedString<N> is a generic struct that ensures strings do not exceed N characters in length. FixedLengthString: Fixed-Length Strings FixedLengthString<N> ensures strings are exactly N characters long, which is useful when handling certain DICOM fields. ...

November 20, 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

How to Use NLog in .NET Core

Benefits of Using NLog for Logging in .NET Projects NLog is a powerful, flexible, and high-performance logging framework widely used in .NET applications (including .NET Framework, .NET Core, and .NET 5/6/7/8+). Integrating NLog into .NET projects can significantly enhance system observability, maintainability, and debugging efficiency. Here are the main advantages of using NLog. I. Core Advantages 1. High Performance with Low Overhead NLog is highly optimized, utilizing asynchronous writing, buffering, and batch processing mechanisms that have minimal impact on application performance. Supports asynchronous logging (async="true") to prevent I/O from blocking the main thread. 2. Flexible Configuration Options Supports externalized configuration through XML configuration files (like nlog.config), allowing adjustments to logging behavior without recompiling code. Also supports code-based configuration for dynamic scenarios or cloud-native environments. 3. Rich Output Targets NLog supports writing logs to multiple targets simultaneously, including: ...

November 16, 2025