Last active
August 30, 2023 09:28
-
-
Save PantelisGeorgiadis/58e2c646b3dc0cce7b1eea90ffb23e1b to your computer and use it in GitHub Desktop.
DICOM Encapsulated PDF dataset generator for Node.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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