Skip to content

Instantly share code, notes, and snippets.

@PantelisGeorgiadis
Last active August 30, 2023 09:28
Show Gist options
  • Save PantelisGeorgiadis/58e2c646b3dc0cce7b1eea90ffb23e1b to your computer and use it in GitHub Desktop.
Save PantelisGeorgiadis/58e2c646b3dc0cce7b1eea90ffb23e1b to your computer and use it in GitHub Desktop.
DICOM Encapsulated PDF dataset generator for Node.js
// dcmjs-pdf-gen
// This Node.js utility can generate DICOM Encapsulated PDF datasets,
// for testing purposes.
// The following npm packages are required:
// dcmjs
// faker
// moment
// e.g. npm install dcmjs faker@5.5.3 moment
// Usage:
// [OPTIONS] node dcmjs-pdf-gen.js
// [OPTIONS]:
// PDF_FILE: The file path of the PDF document to encapsulate.
// OUTPUT_FILE: Output file path (default: pdf.dcm).
// e.g. PDF_FILE=test.pdf node dcmjs-pdf-gen.js
const fs = require('fs');
const faker = require('faker');
const moment = require('moment');
const dcmjs = require('dcmjs');
const { DicomMetaDictionary, DicomDict } = dcmjs.data;
//////////////////////////////////////////////////////////////////////////
// Constant UIDs
/**
* Encapsulated PDF Storage SOP class UID.
* @constant {string}
*/
const EncapsulatedPdfSopClassUid = '1.2.840.10008.5.1.4.1.1.104.1';
/**
* Explicit VR Little Endian syntax UID.
* @constant {string}
*/
const ExplicitVrLittleEndianTransferSyntaxUid = '1.2.840.10008.1.2.1';
/**
* Implementation UID (using fo-dicom's!).
* @constant {string}
*/
const ImplementationUid = '1.3.6.1.4.1.30071.8';
//////////////////////////////////////////////////////////////////////////
// Functions
/**
* Generates a DICOM PDF dataset.
* @method
* @param {Buffer} pdfFileBuffer - The buffer of the PDF document to encapsulate.
* @returns {Buffer} The DICOM PDF dataset buffer.
*/
function generatePdfDataset(pdfFileBuffer) {
// Create dataset dates and times
const birthDate = `${moment(faker.date.between('1950-01-01', '2000-01-01')).format('YYYYMMDD')}`;
const date = `${moment(faker.date.between('1950-01-01', '2010-01-01')).format('YYYYMMDD')}`;
const time = '000000';
// Create dataset UIDs
const studyInstanceUid = DicomMetaDictionary.uid();
const seriesInstanceUid = DicomMetaDictionary.uid();
const sopInstanceUid = DicomMetaDictionary.uid();
// Pad PDF buffer
let pdfBuffer = pdfFileBuffer;
if (pdfBuffer.length & 1) {
pdfBuffer = Buffer.concat([pdfBuffer, Buffer.from([0x00])]);
}
const dataset = {
_vrMap: {
EncapsulatedDocument: 'OB',
},
_meta: {
_vrMap: {},
FileMetaInformationVersion: new Uint8Array([0, 1]).buffer,
MediaStorageSOPClassUID: EncapsulatedPdfSopClassUid,
MediaStorageSOPInstanceUID: sopInstanceUid,
TransferSyntaxUID: ExplicitVrLittleEndianTransferSyntaxUid,
ImplementationClassUID: ImplementationUid,
},
// Patient Module Attributes
PatientID: `${faker.random.alphaNumeric(6).toUpperCase()}`,
PatientName: `${faker.name.firstName().toUpperCase()}^${faker.name.lastName().toUpperCase()}`,
PatientBirthDate: birthDate,
PatientSex: 'O',
// General Study Module Attributes
StudyInstanceUID: studyInstanceUid,
StudyDate: date,
StudyTime: time,
StudyID: `${faker.random.alphaNumeric(3).toUpperCase()}`,
AccessionNumber: `${faker.random.alphaNumeric(6).toUpperCase()}`,
ReferringPhysicianName: `${faker.name.firstName().toUpperCase()}^${faker.name
.lastName()
.toUpperCase()}`,
// Encapsulated Document Series Module Attributes
Modality: 'DOC',
SeriesInstanceUID: seriesInstanceUid,
SeriesNumber: `${faker.datatype.number(100)}`,
// General/SC Equipment Module Attributes
Manufacturer: `${faker.company.companyName().toUpperCase()}`,
ManufacturerModelName: `${faker.commerce.productName().toUpperCase()}`,
DeviceSerialNumber: `${faker.random.alphaNumeric(6).toUpperCase()}`,
SoftwareVersions: `${faker.random.alphaNumeric(1).toUpperCase()}.${faker.random
.alphaNumeric(2)
.toUpperCase()}`,
ConversionType: 'WSD',
// Encapsulated Document Module Attributes
InstanceNumber: '1',
AcquisitionDateTime: `${date}${time}`,
ContentDate: date,
ContentTime: time,
DocumentTitle: '',
BurnedInAnnotation: 'YES',
MIMETypeOfEncapsulatedDocument: 'application/pdf',
EncapsulatedDocument: [pdfBuffer.buffer],
ConceptNameCodeSequence: [],
// SOP Common Module Attributes
SOPClassUID: EncapsulatedPdfSopClassUid,
SOPInstanceUID: sopInstanceUid,
SpecificCharacterSet: 'ISO_IR 100',
};
// Convert the JSON dataset into a DICOM part10 file
const denaturalizedMetaHeader = DicomMetaDictionary.denaturalizeDataset(dataset._meta);
const dicomDict = new DicomDict(denaturalizedMetaHeader);
dicomDict.dict = DicomMetaDictionary.denaturalizeDataset(dataset);
return Buffer.from(dicomDict.write({ fragmentMultiframe: false }));
}
//////////////////////////////////////////////////////////////////////////
// Gather params
const pdfFile = process.env.PDF_FILE;
if (!pdfFile) {
throw new Error(
'Provide the path of the PDF document to encapsulate through the PDF_FILE variable'
);
}
const outFile = process.env.OUTPUT_FILE || 'pdf.dcm';
//////////////////////////////////////////////////////////////////////////
// Validate PDF
const pdfFileBuffer = fs.readFileSync(pdfFile);
const pdfFileBufferHeader = pdfFileBuffer.slice(0, 8).toString();
var pdfHeaderRegex = new RegExp('%PDF-1.[0-7]');
if (!pdfFileBufferHeader.match(pdfHeaderRegex)) {
throw new Error(`The provided PDF file header is not valid [${pdfFileBufferHeader}]`);
}
//////////////////////////////////////////////////////////////////////////
// Generate PDF dataset and persist it to file
const pdfDatasetBuffer = generatePdfDataset(pdfFileBuffer);
fs.writeFileSync(outFile, pdfDatasetBuffer);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment