npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@nuxthealth/node-dicom

v0.2.1

Published

High-performance Node.js bindings for DICOM (Digital Imaging and Communications in Medicine) operations, powered by Rust and [dicom-rs](https://github.com/Enet4/dicom-rs).

Readme

node-dicom-rs

High-performance Node.js bindings for DICOM (Digital Imaging and Communications in Medicine) operations, powered by Rust and dicom-rs.

Features

  • StoreScp: Receive DICOM files over the network with C-STORE SCP server
  • StoreScu: Send DICOM files to remote PACS systems
  • DicomFile: Read, parse, and manipulate DICOM files with full metadata extraction
  • Storage Backends: Filesystem and S3-compatible object storage support
  • TypeScript Support: Full TypeScript definitions with autocomplete for 300+ DICOM tags
  • Event-driven API: Consistent callback-based events with typed data structures

Installation

npm install @nuxthealth/node-dicom

Quick Start

Receiving DICOM Files (StoreScp)

import { StoreScp } from '@nuxthealth/node-dicom';

const receiver = new StoreScp({
    port: 4446,
    callingAeTitle: 'MY-SCP',
    outDir: './dicom-storage',
    verbose: true,
    extractTags: ['PatientName', 'StudyDate', 'Modality']
});

receiver.onFileStored((err, event) => {
    if (err) return console.error('Error:', err);
    const data = event.data;
    if (!data) return;
    
    console.log('File received:', data.file);
    if (data.tags) {
        console.log('Patient:', data.tags.PatientName);
        console.log('Study Date:', data.tags.StudyDate);
        console.log('Modality:', data.tags.Modality);
    }
});

receiver.onStudyCompleted((err, event) => {
    if (err) return console.error('Error:', err);
    const study = event.data?.study;
    if (!study) return;
    
    console.log(`Study ${study.studyInstanceUid} complete`);
    console.log(`${study.series.length} series, total instances: ${study.series.reduce((sum, s) => sum + s.instances.length, 0)}`);
});

receiver.start();

Sending DICOM Files (StoreScu)

import { StoreScu } from '@nuxthealth/node-dicom';

const sender = new StoreScu({
    addr: '192.168.1.100:104',
    callingAeTitle: 'MY-SCU',
    calledAeTitle: 'REMOTE-SCP',
    verbose: true
});

// Add files
sender.addFile('./path/to/file.dcm');
sender.addDirectory('./dicom-folder');

// Send with progress tracking
const result = await sender.send({
    onFileSent: (err, event) => {
        console.log('✓ File sent:', event.data?.sopInstanceUid);
    },
    onFileError: (err, event) => {
        console.error('✗ Error:', event.message, event.data?.error);
    },
    onTransferCompleted: (err, event) => {
        const data = event.data;
        if (data) {
            console.log(`Transfer complete! ${data.successful}/${data.totalFiles} files in ${data.durationSeconds.toFixed(2)}s`);
        }
    }
});

console.log('Result:', result);

Working with DICOM Files

import { DicomFile } from '@nuxthealth/node-dicom';

const file = new DicomFile();
await file.open('./scan.dcm');

// Extract specific tags (always returns flat structure)
const data = file.extract(['PatientName', 'StudyDate', 'Modality']);
console.log('Patient:', data.PatientName);
console.log('Study Date:', data.StudyDate);
console.log('Modality:', data.Modality);

// Get DICOM as JSON (without saving to file)
const json = file.toJson(true);
const obj = JSON.parse(json);

// Get pixel data info
const pixelInfo = file.getPixelDataInfo();
console.log(`Image: ${pixelInfo.width}x${pixelInfo.height}, ${pixelInfo.frames} frames`);

// Get pixel data as Buffer (without saving to file)
const pixelBuffer = file.getPixelData();
console.log(`Pixel data: ${pixelBuffer.length} bytes`);

// For compressed data, decode it
if (pixelInfo.isCompressed) {
    const decodedBuffer = file.getDecodedPixelData();
    processImage(decodedBuffer, pixelInfo);
}

// NEW! Get processed pixel data with windowing, frame extraction, 8-bit conversion
const displayReady = file.getProcessedPixelData({
    applyVoiLut: true,      // Use WindowCenter/Width from file
    convertTo8bit: true      // Convert to 8-bit for display (0-255)
});

// Custom window settings for different tissue types
const boneWindow = file.getProcessedPixelData({
    windowCenter: 300,       // Bone window
    windowWidth: 1500,
    convertTo8bit: true
});

// Or save pixel data to file (synchronous)
file.saveRawPixelData('./output.raw');

file.close();

Update DICOM Tags 🆕

Modify tag values for anonymization or corrections:

import { DicomFile } from '@nuxthealth/node-dicom';
import crypto from 'crypto';

const file = new DicomFile();
await file.open('scan.dcm');

// Update tags (changes in memory only)
file.updateTags({
    PatientName: 'ANONYMOUS',
    PatientID: crypto.randomUUID(),
    PatientBirthDate: '',
    InstitutionName: 'ANONYMIZED'
});

// Save changes to new file
await file.saveAsDicom('anonymized.dcm');
file.close();

DICOMweb Services

node-dicom-rs provides DICOMweb servers for querying and retrieving DICOM objects over HTTP.

QIDO-RS Server (Query)

QIDO-RS allows clients to search for DICOM studies, series, and instances:

import { QidoServer } from '@nuxthealth/node-dicom';

const qidoServer = new QidoServer(8080);
qidoServer.start();

// Server is now listening on http://localhost:8080
// Endpoints:
//   GET /studies - Search for studies
//   GET /series - Search for series
//   GET /instances - Search for instances

// Stop when done
qidoServer.stop();

For more details, see the QIDO-RS Guide.

WADO-RS Server (Retrieval)

WADO-RS provides standardized retrieval of DICOM files:

import { WadoServer } from '@nuxthealth/node-dicom';

const wadoConfig = {
  storageType: 'filesystem',
  basePath: '/path/to/dicom/files'
};

const wadoServer = new WadoServer(8081, wadoConfig);
wadoServer.start();

// Server is now listening on http://localhost:8081
// Endpoints:
//   GET /studies/{studyUID}
//   GET /studies/{studyUID}/series/{seriesUID}
//   GET /studies/{studyUID}/series/{seriesUID}/instances/{instanceUID}
//   GET /studies/{studyUID}/metadata

// Stop when done
wadoServer.stop();

For filesystem storage, organize files as: {basePath}/{studyUID}/{seriesUID}/{instanceUID}.dcm

For more details, see the QIDO-RS Guide.

Documentation

For detailed documentation, see:

  • StoreScp Guide - Receiving DICOM files, tag extraction, storage backends, async tag modification
  • StoreScu Guide - Sending DICOM files, transfer syntaxes, batch operations
  • DicomFile Guide - Reading files, extracting metadata, pixel data operations
  • QIDO-RS Guide - Query service for searching DICOM studies, series, and instances
  • WADO-RS Guide - Retrieval service for accessing DICOM objects over HTTP

Key Features

Tag Extraction

Extract DICOM metadata with ease:

// DicomFile: Always returns flat structure
const data = file.extract(['PatientName', 'StudyDate', 'Modality']);
console.log('Patient:', data.PatientName);

// StoreScp: Flat tags for OnFileStored
receiver.onFileStored((err, event) => {
    const tags = event.data?.tags;
    console.log('Patient:', tags?.PatientName);
});

// StoreScp: Hierarchical with flat tags at each level for OnStudyCompleted
receiver.onStudyCompleted((err, event) => {
    const study = event.data?.study;
    console.log('Study tags:', study?.tags); // Patient + Study level
    study?.series.forEach(series => {
        console.log('Series tags:', series.tags); // Series level
        series.instances.forEach(instance => {
            console.log('Instance tags:', instance.tags); // Instance + Equipment level
        });
    });
});

Tag Modification Before Storage 🆕

Modify DICOM tags asynchronously before files are saved using the onBeforeStore callback:

const receiver = new StoreScp({
    port: 4446,
    outDir: './dicom-storage',
    extractTags: ['PatientName', 'PatientID', 'PatientBirthDate', 'StudyDescription']
});

// Anonymize incoming files before storage (with async database lookup)
receiver.onBeforeStore(async (error, tagsJson) => {
    if (error) throw error;
    
    const tags = JSON.parse(tagsJson);
    
    // Async database lookup for persistent anonymization
    const anonId = await db.getOrCreateAnonId(tags.PatientID);
    
    const modified = {
        ...tags,
        PatientName: 'ANONYMOUS',
        PatientID: anonId,
        PatientBirthDate: '', // Remove PHI
        StudyDescription: tags.StudyDescription ? 
            `ANONYMIZED - ${tags.StudyDescription}` : 
            'ANONYMIZED STUDY'
    };
    
    return JSON.stringify(modified);
});

receiver.start();

Key Features:

  • Asynchronous: Supports async/await for database operations and API calls
  • Error-First Pattern: Callback receives (error, tagsJson) parameters
  • Pre-Storage: Modifications applied BEFORE writing to disk
  • Tag-Safe: Only modifies extracted tags (specified in extractTags)
  • JSON Format: Tags passed as JSON string, must parse and stringify
  • Flexible: Use for anonymization, validation, enrichment, or standardization

Use Cases:

  • Real-time anonymization with persistent database mappings
  • Adding institution-specific metadata from external APIs
  • Tag validation against external services
  • Format standardization
  • PHI removal for GDPR/HIPAA compliance

See demos for complete examples.

TypeScript Autocomplete

Full autocomplete support for 300+ DICOM tags:

const data = file.extract([
    'PatientName',      // Autocomplete suggests all standard tags
    'StudyDate',
    'Modality',
    'SeriesDescription'
]);

Storage Backends

Store received DICOM files to filesystem or S3:

// S3 Storage
const receiver = new StoreScp({
    port: 4446,
    storageBackend: 'S3',
    s3Config: {
        bucket: 'dicom-archive',
        accessKey: 'YOUR_KEY',
        secretKey: 'YOUR_SECRET',
        endpoint: 'https://s3.amazonaws.com'
    }
});

Configurable SCP Acceptance

Control which DICOM types your SCP accepts:

import { getCommonSopClasses, getCommonTransferSyntaxes } from '@nuxthealth/node-dicom';

const sopClasses = getCommonSopClasses();
const transferSyntaxes = getCommonTransferSyntaxes();

const receiver = new StoreScp({
    port: 4446,
    abstractSyntaxMode: 'Custom',
    abstractSyntaxes: [...sopClasses.ct, ...sopClasses.mr], // Only CT and MR
    transferSyntaxMode: 'UncompressedOnly' // Only uncompressed
});

Examples

Check the playground/ directory for more examples:

  • Basic SCP receiver
  • SCU sender with batch processing
  • File metadata extraction
  • S3 storage integration
  • Custom tag extraction

Performance

Built with Rust for maximum performance:

  • Fast DICOM parsing and validation
  • Efficient memory usage for large files
  • Native async/await support
  • Zero-copy operations where possible

Credits

License

See LICENSE file for details.