Skip to content

Examples

Pantelis Georgiadis edited this page Oct 25, 2024 · 8 revisions

C-Echo SCU

const dcmjsDimse = require('dcmjs-dimse');
const { Client } = dcmjsDimse;
const { CEchoRequest } = dcmjsDimse.requests;
const { Status } = dcmjsDimse.constants;

const client = new Client();
const request = new CEchoRequest();
request.on('response', (response) => {
  if (response.getStatus() === Status.Success) {
    console.log('Happy!');
  }
});
client.addRequest(request);
client.on('networkError', (e) => {
  console.log('Network error: ', e);
});
client.send('127.0.0.1', 12345, 'SCU', 'ANY-SCP');

C-Echo SCU (Custom Implementation, Secure, User Identity Negotiation)

const dcmjsDimse = require('dcmjs-dimse');
const { Client, Implementation } = dcmjsDimse;
const { CEchoRequest } = dcmjsDimse.requests;
const { Status, UserIdentityType } = dcmjsDimse.constants;

Implementation.setImplementationClassUid('1.2.826.0.1.3680043.10.854');
Implementation.setImplementationVersion('DCMJS-DIMSE-V0.1');

const client = new Client();
const request = new CEchoRequest();
request.on('response', (response) => {
  if (response.getStatus() === Status.Success) {
    console.log('Happy!');
  }
});
client.addRequest(request);
client.on('networkError', (e) => {
  console.log('Network error: ', e);
});
client.send('127.0.0.1', 12345, 'SCU', 'ANY-SCP', {
  securityOptions: {
	key: Buffer.from(keyBuffer),
	cert: Buffer.from(certBuffer),
	ca: Buffer.from(caBuffer),
	rejectUnauthorized: false,
	requestCert: false,
  },
  userIdentity: {
	type: UserIdentityType.UsernameAndPasscode,
	positiveResponseRequested: true,
	primaryField: username,
	secondaryField: password,
  },
});

C-Find SCU (Studies)

const dcmjsDimse = require('dcmjs-dimse');
const { Client } = dcmjsDimse;
const { CFindRequest } = dcmjsDimse.requests;
const { Status } = dcmjsDimse.constants;

const client = new Client();
const request = CFindRequest.createStudyFindRequest({ PatientID: '12345', PatientName: '*' });
request.on('response', (response) => {
  if (response.getStatus() === Status.Pending && response.hasDataset()) {
    console.log(response.getDataset());
  }
});
client.addRequest(request);
client.on('networkError', (e) => {
  console.log('Network error: ', e);
});
client.send('127.0.0.1', 12345, 'SCU', 'ANY-SCP');

C-Find SCU (Worklist)

const dcmjsDimse = require('dcmjs-dimse');
const { Client } = dcmjsDimse;
const { CFindRequest } = dcmjsDimse.requests;
const { Status } = dcmjsDimse.constants;

const client = new Client();
const request = CFindRequest.createWorklistFindRequest({ PatientName: '*' });
request.on('response', (response) => {
  if (response.getStatus() === Status.Pending && response.hasDataset()) {
    console.log(response.getDataset());
  }
});
client.addRequest(request);
client.on('networkError', (e) => {
  console.log('Network error: ', e);
});
client.send('127.0.0.1', 12345, 'SCU', 'ANY-SCP');

C-Store SCU

const dcmjsDimse = require('dcmjs-dimse');
const { Client } = dcmjsDimse;
const { CStoreRequest } = dcmjsDimse.requests;

const client = new Client();
const request = new CStoreRequest('test.dcm');
client.addRequest(request);
client.on('networkError', (e) => {
  console.log('Network error: ', e);
});
client.send('127.0.0.1', 12345, 'SCU', 'ANY-SCP');

C-Move SCU

const dcmjsDimse = require('dcmjs-dimse');
const { Client } = dcmjsDimse;
const { CMoveRequest } = dcmjsDimse.requests;
const { Status } = dcmjsDimse.constants;

const client = new Client();
const request = CMoveRequest.createStudyMoveRequest('DEST-AE', studyInstanceUid);
request.on('response', (response) => {
  if (response.getStatus() === Status.Pending) {
    console.log('Remaining: ' + response.getRemaining());
    console.log('Completed: ' + response.getCompleted());
    console.log('Warning: ' + response.getWarnings());
    console.log('Failed: ' + response.getFailures());
  }
});
client.addRequest(request);
client.on('networkError', (e) => {
  console.log('Network error: ', e);
});
client.send('127.0.0.1', 12345, 'SCU', 'ANY-SCP');

C-Get SCU

const dcmjsDimse = require('dcmjs-dimse');
const { Client } = dcmjsDimse;
const { CGetRequest } = dcmjsDimse.requests;
const { CStoreResponse } = dcmjsDimse.responses;
const { Status } = dcmjsDimse.constants;

const client = new Client();
const request = CGetRequest.createStudyGetRequest(studyInstanceUid);
request.on('response', (response) => {
  if (response.getStatus() === Status.Pending) {
    console.log('Remaining: ' + response.getRemaining());
    console.log('Completed: ' + response.getCompleted());
    console.log('Warning: ' + response.getWarnings());
    console.log('Failed: ' + response.getFailures());
  }
});
client.on('cStoreRequest', (request, callback) => {
  console.log(request.getDataset());

  const response = CStoreResponse.fromRequest(request);
  response.setStatus(Status.Success);
  callback(response);
});
client.addRequest(request);
client.on('networkError', (e) => {
  console.log('Network error: ', e);
});
client.send('127.0.0.1', 12345, 'SCU', 'ANY-SCP');

N-Action SCU (e.g. Storage Commitment)

const dcmjsDimse = require('dcmjs-dimse');
const { Client, Dataset } = dcmjsDimse;
const { NActionRequest } = dcmjsDimse.requests;
const { NEventReportResponse } = dcmjsDimse.responses;
const { SopClass, Status, StorageClass } = dcmjsDimse.constants;

const client = new Client();
const request = new NActionRequest(
  SopClass.StorageCommitmentPushModel,
  Dataset.generateDerivedUid(),
  0x0001
);
request.setDataset(
  new Dataset({
    TransactionUID: Dataset.generateDerivedUid(),
    ReferencedSOPSequence: [
      {
        ReferencedSOPClassUID: StorageClass.MrImageStorage,
        ReferencedSOPInstanceUID: Dataset.generateDerivedUid(),
      },
    ],
  })
);
client.addRequest(request);
client.on('nEventReportRequest', (request, callback) => {
  console.log(request.getDataset());

  const response = NEventReportResponse.fromRequest(request);
  response.setStatus(Status.Success);
  callback(response);
});
client.on('networkError', (e) => {
  console.log('Network error: ', e);
});
// Linger the association to get a synchronous 
// storage commitment response
client.send('127.0.0.1', 12345, 'SCU', 'ANY-SCP', {
  associationLingerTimeout: 5000,
});

N-Create SCU (e.g. Instance Availability Notification)

const dcmjsDimse = require('dcmjs-dimse');
const { Client, Dataset } = dcmjsDimse;
const { NCreateRequest } = dcmjsDimse.requests;
const { SopClass, Status, StorageClass } = dcmjsDimse.constants;

const client = new Client();
const request = new NCreateRequest(SopClass.InstanceAvailabilityNotification, '');
request.setDataset(
  new Dataset({
    StudyInstanceUID: Dataset.generateDerivedUid(),
    ReferencedPerformedProcedureStepSequence: [],
    ReferencedSeriesSequence: [
      {
        SeriesInstanceUID: Dataset.generateDerivedUid(),
        ReferencedSOPSequence: [
          {
            ReferencedSOPClassUID: StorageClass.MrImageStorage,
            ReferencedSOPInstanceUID: Dataset.generateDerivedUid(),
            RetrieveAETitle: 'ANY-SCP',
            InstanceAvailability: 'ONLINE',
          },
        ],
      },
    ],
  })
);
request.on('response', (response) => {
  if (response.getStatus() === Status.Success) {
    console.log('Notified!');
  }
});
client.addRequest(request);
client.on('networkError', (e) => {
  console.log('Network error: ', e);
});
client.send('127.0.0.1', 12345, 'SCU', 'ANY-SCP');

N-Get SCU (e.g. Printer)

const dcmjsDimse = require('dcmjs-dimse');
const { Client } = dcmjsDimse;
const { NGetRequest } = dcmjsDimse.requests;
const { SopClass, Status } = dcmjsDimse.constants;

const client = new Client();
const request = new NGetRequest(SopClass.Printer, '1.2.840.10008.5.1.1.17', [
  'PrinterStatus',
  'PrinterStatusInfo',
  'PrinterName',
  'Manufacturer',
  'ManufacturersModelName',
  'DeviceSerialNumber',
  'SoftwareVersions',
  'DeviceSerialNumber',
  'DateOfLastCalibration',
  'TimeOfLastCalibration',
]);
request.on('response', (response) => {
  if (response.getStatus() === Status.Success) {
    console.log(response.getDataset());
  }
});
client.addRequest(request);
client.on('networkError', (e) => {
  console.log('Network error: ', e);
});
client.send('127.0.0.1', 12345, 'SCU', 'ANY-SCP');

N-Create, N-Action, N-Set, N-Delete SCU (e.g. Print)

const dcmjsDimse = require('dcmjs-dimse');
const { Client, Dataset } = dcmjsDimse;
const { PresentationContext } = dcmjsDimse.association;
const { NActionRequest, NCreateRequest, NDeleteRequest, NSetRequest } = dcmjsDimse.requests;
const { SopClass, Status, TransferSyntax } = dcmjsDimse.constants;

const client = new Client();

const pc = new PresentationContext(
  0,
  SopClass.BasicGrayscalePrintManagementMeta,
  TransferSyntax.ImplicitVRLittleEndian
);
client.addAdditionalPresentationContext(pc);

let filmSessionCreateRequest;
let filmBoxCreateRequest;
let imageBoxSetRequest;
let filmBoxPrintRequest;
let filmBoxDeleteRequest;
let filmSessionDeleteRequest;

// Create film session
filmSessionCreateRequest = new NCreateRequest(
  SopClass.BasicFilmSession,
  '', // No need to provide a UID
  SopClass.BasicGrayscalePrintManagementMeta
);
filmSessionCreateRequest.setDataset(
  new Dataset({
    FilmDestination: 'MAGAZINE',
    FilmSessionLabel: '',
    MediumType: '',
    NumberOfCopies: 1,
    PrintPriority: 'MED',
  })
);
filmSessionCreateRequest.on('response', (response) => {
  if (response.getStatus() === Status.Success) {
    // Set the referenced film session sequence of the film box creation request
    // that is returned in the response of the film session creation request
    const filmBoxDataset = filmBoxCreateRequest.getDataset();
    filmBoxDataset.setElement('ReferencedFilmSessionSequence', [
      {
        ReferencedSOPClassUID: SopClass.BasicFilmSession,
        ReferencedSOPInstanceUID: response.getAffectedSopInstanceUid(),
      },
    ]);

    // Set the requested SOP instance UID of the film session delete request
    // that is returned in the response of the film session creation request
    filmSessionDeleteRequest.setRequestedSopInstanceUid(response.getAffectedSopInstanceUid());
  }
});
client.addRequest(filmSessionCreateRequest);

// Create film box
filmBoxCreateRequest = new NCreateRequest(
  SopClass.BasicFilmBox,
  '', // No need to provide a UID
  SopClass.BasicGrayscalePrintManagementMeta
);
filmBoxCreateRequest.setDataset(
  new Dataset({
    ConfigurationInformation: '',
    FilmOrientation: 'PORTRAIT',
    FilmSizeID: '14INX14IN',
    ImageDisplayFormat: 'STANDARD\\1,1',
    MagnificationType: 'CUBIC',
  })
);

filmBoxCreateRequest.on('response', (response) => {
  if (response.getStatus() === Status.Success) {
    // Set the requested SOP instance UID of the film box print request
    // that is returned in the response of the film box creation request
    filmBoxPrintRequest.setRequestedSopInstanceUid(response.getAffectedSopInstanceUid());

    // Set the requested SOP instance UID of the film box delete request
    // that is returned in the response of the film box creation request
    filmBoxDeleteRequest.setRequestedSopInstanceUid(response.getAffectedSopInstanceUid());

    // Set the requested SOP instance UID of the image box set creation request
    // that is returned in the response of the film box creation request
    const dataset = response.getDataset();
    const referencedImageBoxSequenceItem = dataset.getElement('ReferencedImageBoxSequence');
    imageBoxSetRequest.setRequestedSopInstanceUid(
      referencedImageBoxSequenceItem.ReferencedSOPInstanceUID
    );
  }
});
client.addRequest(filmBoxCreateRequest);

// Set image box
imageBoxSetRequest = new NSetRequest(
  SopClass.BasicGrayscaleImageBox,
  '', // UID will be set once received
  SopClass.BasicGrayscalePrintManagementMeta
);

// Create a random image
const width = 256;
const height = 256;
const pixels = new Uint8Array(width * height);
for (let i = 0; i < height; i++) {
  for (let j = 0; j < width; j++) {
    pixels[j * width + i] = Math.floor(Math.random() * Math.pow(2, 8) - 1);
  }
}
imageBoxSetRequest.setDataset(
  new Dataset({
    ImageBoxPosition: 1,
    Polarity: 'NORMAL',
    RequestedDecimateCropBehavior: 'CROP',
    BasicGrayscaleImageSequence: [
      {
        _vrMap: {
          PixelData: 'OB',
        },
        Columns: width,
        Rows: height,
        BitsAllocated: 8,
        BitsStored: 8,
        HighBit: 7,
        PixelRepresentation: 0,
        SamplesPerPixel: 1,
        PhotometricInterpretation: 'MONOCHROME2',
        PixelData: [pixels.buffer],
      },
    ],
  })
);
client.addRequest(imageBoxSetRequest);

// Print film box!
filmBoxPrintRequest = new NActionRequest(
  SopClass.BasicFilmBox,
  '', // UID will be set once received
  0x001,
  SopClass.BasicGrayscalePrintManagementMeta
);
client.addRequest(filmBoxPrintRequest);

// Delete film box
filmBoxDeleteRequest = new NDeleteRequest(
  SopClass.BasicFilmBox,
  '', // UID will be set once received
  SopClass.BasicGrayscalePrintManagementMeta
);
client.addRequest(filmBoxDeleteRequest);

// Delete film session
filmSessionDeleteRequest = new NDeleteRequest(
  SopClass.BasicFilmSession,
  '', // UID will be set once received
  SopClass.BasicGrayscalePrintManagementMeta
);
client.addRequest(filmSessionDeleteRequest);

// Send requests
client.send('127.0.0.1', 104, 'SCU', 'ANY-SCP');

SCP

const dcmjsDimse = require('dcmjs-dimse');
const { Dataset, Server, Scp } = dcmjsDimse;
const { CEchoResponse, CFindResponse, CStoreResponse } = dcmjsDimse.responses;
const {
  Status,
  PresentationContextResult,
  RejectResult,
  RejectSource,
  RejectReason,
  TransferSyntax,
  SopClass,
  StorageClass,
  UserIdentityType,
} = dcmjsDimse.constants;

class DcmjsDimseScp extends Scp {
  constructor(socket, opts) {
    super(socket, opts);
    this.association = undefined;
  }

  // Handle incoming association requests
  associationRequested(association) {
    this.association = association;

    // Evaluate calling/called AET and reject association, if needed
    if (this.association.getCallingAeTitle() !== 'SCU') {
      this.sendAssociationReject(
        RejectResult.Permanent,
        RejectSource.ServiceUser,
        RejectReason.CallingAeNotRecognized
      );
      return;
    }

    // Evaluate user identity and reject association, if needed
    if (
      this.association.getNegotiateUserIdentity() &&
      this.association.getUserIdentityPositiveResponseRequested()
    ) {
      if (
        this.association.getUserIdentityType() === UserIdentityType.UsernameAndPasscode &&
        this.association.getUserIdentityPrimaryField() === 'Username' &&
        this.association.getUserIdentitySecondaryField() === 'Password'
      ) {
        this.association.setUserIdentityServerResponse('');
        this.association.setNegotiateUserIdentityServerResponse(true);
      } else {
        this.sendAssociationReject(
          RejectResult.Permanent,
          RejectSource.ServiceUser,
          RejectReason.NoReasonGiven
        );
        return;
      }
    }

    // Optionally set the preferred max PDU length
    this.association.setMaxPduLength(65536);

    const contexts = association.getPresentationContexts();
    contexts.forEach((c) => {
      const context = association.getPresentationContext(c.id);
      if (
        context.getAbstractSyntaxUid() === SopClass.Verification ||
        context.getAbstractSyntaxUid() === SopClass.StudyRootQueryRetrieveInformationModelFind ||
        context.getAbstractSyntaxUid() === StorageClass.MrImageStorage
        // Accept other presentation contexts, as needed
      ) {
        const transferSyntaxes = context.getTransferSyntaxUids();
        transferSyntaxes.forEach((transferSyntax) => {
          if (
            transferSyntax === TransferSyntax.ImplicitVRLittleEndian ||
            transferSyntax === TransferSyntax.ExplicitVRLittleEndian
          ) {
            context.setResult(PresentationContextResult.Accept, transferSyntax);
          } else {
            context.setResult(PresentationContextResult.RejectTransferSyntaxesNotSupported);
          }
        });
      } else {
        context.setResult(PresentationContextResult.RejectAbstractSyntaxNotSupported);
      }
    });
    this.sendAssociationAccept();
  }

  // Handle incoming C-ECHO requests
  cEchoRequest(request, callback) {
    const response = CEchoResponse.fromRequest(request);
    response.setStatus(Status.Success);
    
    callback(response);
  }

  // Handle incoming C-FIND requests
  cFindRequest(request, callback) {
    console.log(request.getDataset());

    const pendingResponse = CFindResponse.fromRequest(request);
    pendingResponse.setDataset(new Dataset({ PatientID: '12345', PatientName: 'JOHN^DOE' }));
    pendingResponse.setStatus(Status.Pending);

    const finalResponse = CFindResponse.fromRequest(request);
    finalResponse.setStatus(Status.Success);

    callback([pendingResponse, finalResponse]);
  }

  // Handle incoming C-STORE requests
  cStoreRequest(request, callback) {
    console.log(request.getDataset());

    const response = CStoreResponse.fromRequest(request);
    response.setStatus(Status.Success);

    callback(response);
  }

  // Handle incoming association release requests
  associationReleaseRequested() {
    this.sendAssociationReleaseResponse();
  }
}

const server = new Server(DcmjsDimseScp);
server.on('networkError', (e) => {
  console.log('Network error: ', e);
});
server.listen(port);

// When done
server.close();