dcmjs-org / dcmjs

Javascript implementation of DICOM manipulation
https://dcmjs.netlify.com/
MIT License
291 stars 110 forks source link

[mutiple Qs]: convert .jpg to .dcm, no dicom header, could't not find declaration module ''dcmjs",... #333

Closed chenkenkone closed 1 year ago

chenkenkone commented 1 year ago

Having problem in converting .jpg to .dcm. I've read about Convert jpeg file ( .jpeg ) to dicom file ( .dcm ) #85 and try it out. However there are few things I can't solve.

Q1: For the first one, I can't figure it out how this generate.js convert .jpg file to .dcm file? Isn't it just create a object and make it as the configuration dataset? And the store in buffer? There is no source or output file directory infomation in metioned article or code, that's why I got confused.

Q2: So, I tried this(most simply compy from the generate.js, I need to successfully create dicom file first and then to match my business logics later), the concept is concat the Dataset()Buffer and Image(.jpg)Buffer together to create a file, not sure is it a right way.

    // Get picture's pixle data
    var jpeg = require('jpeg-js');
    var jpegData = fs.readFileSync('./upload/image/4/fa50f460-732f-11ed-ae7d-cfd6aeaf2db8.jpg');
    var rawImageData = jpeg.decode(jpegData, {useTArray: true}); // return as Uint8Array
    console.log("rawImageData:", rawImageData);
    /*
    rawImageData: {
    width: 640,
    height: 480,
    exifBuffer: undefined,
    data: Uint8Array(1228800) [
        106, 109, 104, 255, 104, 107, 102, 255, 103, 106, 101, 255,
        104, 107, 102, 255, 105, 108, 101, 255, 103, 106,  99, 255,
        102, 105,  98, 255, 102, 105,  98, 255, 105, 108, 101, 255,
        106, 109, 102, 255, 107, 110, 103, 255, 106, 109, 102, 255,
        104, 108,  98, 255, 103, 107,  97, 255, 106, 110, 100, 255,
        108, 112, 102, 255, 107, 112, 106, 255, 106, 111, 105, 255,
        105, 110, 104, 255, 105, 110, 104, 255, 105, 110, 104, 255,
        106, 111, 105, 255, 107, 112, 106, 255, 107, 112, 106, 255,
        107, 112, 106, 255,
        ... 1228700 more items
    ] // typed array
    } 
    */
    const jsonDataset = `{
        "AccessionNumber": "",
        "AcquisitionMatrix": [ 256, 0, 0, 192 ],
        "AcquisitionNumber": "8",
        "BitsAllocated": 16,
        "BitsStored": 16,
        "Columns": ${rawImageData.width},
        "ContentDate": "20020628",
        "ContentTime": "164936.0",
        "ContrastBolusAgent": "",
        "DeviceSerialNumber": "4168496325",
        "EchoTime": "6",
        "EchoTrainLength": "1",
        "FlipAngle": "3",
        "FrameOfReferenceUID": "2.16.840.1.113662.4.4168496325.1025305873.7118351817185979330",
        "HighBit": 15,
        "ImageOrientationPatient": [ "0.00000e+00", "1.00000e+00", "-0.00000e+00", "-0.00000e+00", "0.00000e+00", "-1.00000e+00" ],
        "ImagePositionPatient": [ "6.454490e+01", "-1.339286e+02", "1.167857e+02" ],
        "ImageType": [ "ORIGINAL", "PRIMARY", "OTHER" ],
        "ImagesInAcquisition": "130",
        "ImagingFrequency": "6.37165e+01",
        "InstanceNumber": "18",
        "InstitutionName": "UCI Medical Center",
        "MRAcquisitionType": "",
        "MagneticFieldStrength": "1.5",
        "Manufacturer": "Marconi Medical Systems, Inc.",
        "ManufacturerModelName": "Eclipse 1.5T",
        "Modality": "MR",
        "NumberOfAverages": "1",
        "NumberOfTemporalPositions": "1",
        "OperatorsName": "SSRT/^^^^",
        "PatientAge": "039Y",
        "PatientBirthDate": "19000000",
        "PatientID": "0000000",
        "PatientName": "Zzzzzz^Yyyyy^^^",
        "PatientPosition": "HFS",
        "PatientSex": "M",
        "PatientWeight": "99.773300",
        "PercentPhaseFieldOfView": "91.4062",
        "PhotometricInterpretation": "MONOCHROME2",
        "PixelRepresentation": 1,
        "PixelSpacing": [ "1.000000e+00", "1.000000e+00" ],
        "PositionReferenceIndicator": "BRAIN",
        "ProtocolName": "SAG/RF-FAST/VOL/FLIP 3",
        "ReceiveCoilName": "HEAD",
        "ReferencedImageSequence": {
            "ReferencedSOPClassUID": "1.2.840.10008.5.1.4.1.1.4",
            "ReferencedSOPInstanceUID": "2.16.840.1.113662.4.4168496325.1025306066.995213515550827262",
            "_vrMap": {}
        },
        "ReferringPhysicianName": "",
        "RepetitionTime": "20",
        "Rows": ${rawImageData.height},
        "SOPClassUID": "1.2.840.10008.5.1.4.1.1.4",
        "SOPInstanceUID": "2.16.840.1.113662.4.4168496325.1025307679.1207625440068523197",
        "SamplesPerPixel": 1,
        "ScanOptions": "",
        "ScanningSequence": "GR",
        "SequenceVariant": [
            "SS",
            "SP"
        ],
        "SeriesDate": "20020628",
        "SeriesDescription": "SAG/RF-FAST/VOL/FLIP 3",
        "SeriesInstanceUID": "2.16.840.1.113662.4.4168496325.1025307678.3494705239865384161",
        "SeriesNumber": "5",
        "SeriesTime": "164118.0",
        "SliceLocation": "64.544899",
        "SliceThickness": "1.300000e+00",
        "SoftwareVersions": "VIA2.0E.003",
        "SpecificCharacterSet": "ISO_IR 192",
        "StationName": "ba187_ws",
        "StudyDate": "20020628",
        "StudyDescription": "BRAIN",
        "StudyID": "14024",
        "StudyInstanceUID": "2.16.840.1.113662.4.4168496325.1025305873.7118351817185979330",
        "StudyTime": "160956.0",
        "TemporalPositionIdentifier": "1",
        "TemporalResolution": "458660",
        "WindowCenter": "28",
        "WindowWidth": "20",
        "_meta": {
            "FileMetaInformationVersion": {
                "Value": [
                    {
                        "0": 0,
                        "1": 1
                    }
                ],
                "vr": "OB"
            },
            "ImplementationClassUID": {
                "Value": [
                    "1.2.840.113819.7.1.1997.1.0"
                ],
                "vr": "UI"
            },
            "ImplementationVersionName": {
                "Value": [
                    "SENSORSYSTEMS1.0"
                ],
                "vr": "SH"
            },
            "MediaStorageSOPClassUID": {
                "Value": [
                    "1.2.840.10008.5.1.4.1.1.4"
                ],
                "vr": "UI"
            },
            "MediaStorageSOPInstanceUID": {
                "Value": [
                    "2.16.840.1.113662.4.4168496325.1025307679.1207625440068523197"
                ],
                "vr": "UI"
            },
            "TransferSyntaxUID": {
                "Value": [
                    "1.2.840.10008.1.2"
                ],
                "vr": "UI"
            }
        },
        "_vrMap": {
            "PixelData": "OW"
        }
    }`;
    const dataset = JSON.parse(jsonDataset);

    pixelData = new Int16Array(dataset.Rows * dataset.Columns);
    pixelData.fill(100);
    dataset.PixelData = pixelData.buffer;

    const dicomDict = dcmjs.data.datasetToDict(dataset);
    const buffer = Buffer.from(dicomDict.write());

    const dcmBuffer = Buffer.concat([rawImageData.data, buffer]);

    appendFile("./upload/image/4/fa50f460-732f-11ed-ae7d-cfd6aeaf2db8.dcm", dcmBuffer, (err) => {
        if (err) throw err;
        console.log('The "data to append" was appended to file!');
    });

I can create a .dcm file successfully. But using ezDICOM open the .dcm I got LoadData: Error reading image., using DICOM_Parser_Rubo I got No DICOM Header detected. So I tried to find something for this issue, I found this #315 https://github.com/dcmjs-org/dcmjs/blob/914b3dccd1a9e9fd19f2ac94be6e02a82a6316ec/src/DicomDict.js#L20-L24 So dcmjs will add the header. But I don't understand why I got NO DICOM HEADER detected. And did the "Preamble" exist in the header already too?

Q3: I got this notice. However, when I altered it to const dcmjs = require("dcmjs/build/dcmjs"); the notice was gone. I don't know which would be correct.

截圖 2022-12-22 下午6 10 45

Q4: Why use Int16Array? and Why .fill(100)

    pixelData = new Int16Array(dataset.Rows * dataset.Columns);
    pixelData.fill(100);
    dataset.PixelData = pixelData.buffer;

Thank you for reply.

pieper commented 1 year ago

It looks like you are on the right track. As mentioned in the thread you linked, the generate.js example creates just a dummy file with nonsense data (an image with the value 100 at every pixel using the fill(100) command. Instead you need to put the real jpg image you read using the library into the PixelData element. Also you. need to adjust the other header tags to match, like you did with the Rows and Columns. My suggestion from #85 is still what I would suggest: generate a valid Secondary Capture image using another tool and use it as the template for creating a new dicom dataset in dcmjs with your header values and pixel data. Please post if you come up with a complete solution or better yet add a pull request with working code in the form of a software test.

coderider007 commented 1 year ago

I am able to generate DICOM file from Jpeg image using below code:

` import dcmjs from 'dcmjs'; // eslint-disable-next-line @typescript-eslint/no-var-requires const jpeg = require('jpeg-js');

 async createDicomFromImageAndAddToZip(zipUpdated: any, fileData: any, caseData: CaseData): Promise<void> {
    logger.info('update patient details, Uuids and image pixel data of Dicom generated from X-Ray image');

    // Get dicom template data
    const dataset = JSON.parse(CommonConstants.DICOM_META_TEMPLATE);

    // Get json data as dcmjs dicom dictionary
    const dicomDict = dcmjs.data.datasetToDict(dataset);

    // Get jpeg image pixels
    const rawImageData = jpeg.decode(fileData, { formatAsRGBA: false });

    // 1. Set Pixel data with var type Other Bytes
    dicomDict.dict['7FE00010'] = {
        vr: 'OB',
        Value: [rawImageData.data.buffer]
    }

    // Parse the byte array to get a DataSet object that has the parsed contents
    const parsedDataSet = dcmjs.data.DicomMetaDictionary.naturalizeDataset(dicomDict.dict);

    // 2. Set image data rows and columns
    parsedDataSet.Columns = rawImageData.width;
    parsedDataSet.Rows = rawImageData.height;

    // 3. Alter StudyInstanceUID 
    const oldStudyInstanceUID = parsedDataSet.StudyInstanceUID;
    const oldStudyInstanceUIDArray = oldStudyInstanceUID.split('.');
    oldStudyInstanceUIDArray[oldStudyInstanceUIDArray.length - 1] = caseData.id.toString();
    oldStudyInstanceUIDArray[oldStudyInstanceUIDArray.length - 2] = this.getRndInteger(1000, 10000)
    const newStudyInstanceUID = oldStudyInstanceUIDArray.join('.');
    parsedDataSet.StudyInstanceUID = newStudyInstanceUID;

    // 4. Alter SeriesInstanceUID 
    const oldSeriesInstanceUID = parsedDataSet.SeriesInstanceUID;
    const oldSeriesInstanceUIDArray = oldSeriesInstanceUID.split('.');
    oldSeriesInstanceUIDArray[oldSeriesInstanceUIDArray.length - 1] = caseData.id.toString();
    oldSeriesInstanceUIDArray[oldSeriesInstanceUIDArray.length - 2] = this.getRndInteger(10000, 100000)
    const newSeriesInstanceUID = oldSeriesInstanceUIDArray.join('.');
    parsedDataSet.SeriesInstanceUID = newSeriesInstanceUID;

    // 5. Alter SOPInstanceUID 
    const oldInstanceUID = parsedDataSet.SOPInstanceUID;
    const oldInstanceUIDArray = oldInstanceUID.split('.');
    oldInstanceUIDArray[oldInstanceUIDArray.length - 1] = caseData.id.toString();
    oldInstanceUIDArray[oldInstanceUIDArray.length - 2] = this.getRndInteger(100000, 1000000)
    const newInstanceUID = oldInstanceUIDArray.join('.');
    parsedDataSet.SOPInstanceUID = newInstanceUID;

    // 6. Update Patient Details
    // Patient ID
    parsedDataSet.PatientID = caseData.patient.mrNumber ? `${caseData.patient.mrNumber}` : `${caseData.patient.id}`;
    // Patient Name
    parsedDataSet.PatientName = `${caseData.patient.firstName} ${caseData.patient.lastName}`;
    // Patient Gender
    parsedDataSet.PatientSex = caseData.patient.gender == 'male' ? 'M' : (caseData.patient.gender == 'female' ? 'F' : 'O');
    // Patient DoB
    parsedDataSet.PatientBirthDate = utils.getDateAsDicomMetaDate(caseData.patient.dob as any);
    // Patient Age
    parsedDataSet.PatientAge = this.calculateAge(new Date(caseData.patient.dob));

    // Set updated content back to dicom dict
    dicomDict.dict = dcmjs.data.DicomMetaDictionary.denaturalizeDataset(parsedDataSet);
    const updatedDicomArrayBuffer = dicomDict.write();
    const updatedDicomBuffer = Buffer.from(updatedDicomArrayBuffer);

    // add altered dicom to zip
    zipUpdated.addFile('image-' + this.getRndInteger(100, 10000), updatedDicomBuffer);
}

`

static readonly DICOM_META_TEMPLATE = { "AccessionNumber": "", "AcquisitionMatrix": [ 256, 0, 0, 192 ], "AcquisitionNumber": "8", "BitsAllocated": "8", "BitsStored": "8", "Columns": "256", "ContentDate": "20020628", "ContentTime": "164936.0", "ContrastBolusAgent": "", "DeviceSerialNumber": "4168496325", "EchoTime": "6", "EchoTrainLength": "1", "FlipAngle": "3", "FrameOfReferenceUID": "2.16.840.1.113662.4.4168496325.1025305873.7118351817185979330", "HighBit": "7", "ImageComments": "Picture of X-ray", "ImageOrientationPatient": [ "0.00000e+00", "1.00000e+00", "-0.00000e+00", "-0.00000e+00", "0.00000e+00", "-1.00000e+00" ], "ImagePositionPatient": [ "6.454490e+01", "-1.339286e+02", "1.167857e+02" ], "ImageType": [ "ORIGINAL", "PRIMARY", "OTHER" ], "ImagesInAcquisition": "1", "ImagingFrequency": "6.37165e+01", "InstanceNumber": "1", "InstitutionName": "MyOrganization", "LossyImageCompression": "00", "MRAcquisitionType": "", "MagneticFieldStrength": "1.5", "Manufacturer": "MyOrganization", "ManufacturerModelName": "MyOrganization 1.5T", "Modality": "DX", "NumberOfAverages": "1", "NumberOfFrames": "1", "NumberOfTemporalPositions": "1", "OperatorsName": "NA", "PatientAge": "023Y", "PatientBirthDate": "20000101", "PatientID": "0000000", "PatientName": "NA", "PatientPosition": "HFS", "PatientSex": "O", "PatientWeight": "99.773300", "PercentPhaseFieldOfView": "91.4062", "PhotometricInterpretation": "RGB", "PixelRepresentation": 0, "PixelSpacing": [ "0.1171875", "0.1171875" ], "PlanarConfiguration": "0", "PositionReferenceIndicator": "BRAIN", "ProtocolName": "SAG/RF-FAST/VOL/FLIP 3", "ReceiveCoilName": "HEAD", "ReferencedImageSequence": { "ReferencedSOPClassUID": "1.2.840.10008.5.1.4.1.1.4", "ReferencedSOPInstanceUID": "2.16.840.1.113662.4.4168496325.1025306066.995213515550827262", "_vrMap": {} }, "ReferringPhysicianName": "", "RepetitionTime": "20", "Rows": 256, "SOPClassUID": "1.2.840.10008.5.1.4.1.1.4", "SOPInstanceUID": "2.16.840.1.113662.4.4168496325.1025307679.1207625440068523197", "SamplesPerPixel": 3, "ScanOptions": "", "ScanningSequence": "GR", "SequenceVariant": [ "SS", "SP" ], "SeriesDate": "20020628", "SeriesDescription": "L Spine", "SeriesInstanceUID": "2.16.840.1.113662.4.4168496325.1025307678.3494705239865384161", "SeriesNumber": "1", "SeriesTime": "164118.0", "SliceLocation": "64.544899", "SliceThickness": "1.300000e+00", "SoftwareVersions": "VIA2.0E.003", "SpecificCharacterSet": "ISO_IR 100", "StationName": "ba187_ws", "StudyDate": "20230101", "StudyDescription": "SPINE", "StudyID": "14024", "StudyInstanceUID": "2.16.840.1.113662.4.4168496325.1025305873.7118351817185979330", "StudyTime": "160956.0", "TemporalPositionIdentifier": "1", "TemporalResolution": "458660", "_meta": { "FileMetaInformationVersion": { "Value": [ { "0": 0, "1": 1 } ], "vr": "OB" }, "ImplementationClassUID": { "Value": [ "1.2.840.113819.7.1.1997.1.0" ], "vr": "UI" }, "ImplementationVersionName": { "Value": [ "SENSORSYSTEMS1.0" ], "vr": "SH" }, "MediaStorageSOPClassUID": { "Value": [ "1.2.840.10008.5.1.4.1.1.4" ], "vr": "UI" }, "MediaStorageSOPInstanceUID": { "Value": [ "2.16.840.1.113662.4.4168496325.1025307679.1207625440068523197" ], "vr": "UI" }, "TransferSyntaxUID": { "Value": [ "1.2.840.10008.1.2" ], "vr": "UI" } }, "_vrMap": { "PixelData": "OB" } };

pieper commented 1 year ago

Excellent - thanks for sharing it. If you have a chance, it would be great if you could convert this into a test so that others can easily discover and learn from it, and so we can confirm that this keeps working in future versions.

coderider007 commented 1 year ago

Sure Steve @pieper, I'll do it.

Looks like there is an issue when setting dataset.PixelData using Tag name. The var type is not set correctly.

As a workaround I used

dicomDict.dict['7FE00010'] = { vr: 'OB', Value: [rawImageData.data.buffer] }

to set PixelData

pieper commented 1 year ago

Sure Steve @pieper, I'll do it.

That's great 👍

Looks like there is an issue when setting dataset.PixelData using Tag name. The var type is not set correctly.

If you think that's an error in the way PixelData is handled generally maybe it's fixable. I don't think generating datasets from scratch like this has gotten a lot of testing compared to read or read/modify/write.