DICOM DIMSE implementation for Node.js using Steve Pieper's dcmjs library. This library was inspired by fo-dicom and mdcm. Part of the networking code was taken from dicom-dimse.
This effort is a work-in-progress and should not be used for production or clinical purposes.
npm install dcmjs-dimse
npm install
npm run build
- Implements C-ECHO, C-FIND, C-STORE, C-MOVE, C-GET, C-CANCEL, N-CREATE, N-ACTION, N-DELETE, N-EVENT-REPORT, N-GET and N-SET services as SCU and SCP.
- Supports secure DICOM TLS connections and user identity negotiation.
- Allows custom DICOM implementations (Implementation Class UID and Implementation Version).
- Provides asynchronous event handlers for incoming SCP requests.
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');
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');
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');
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');
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');
const dcmjsDimse = require('dcmjs-dimse');
const { Dataset, Server, Scp } = dcmjsDimse;
const { CEchoResponse, CFindResponse, CStoreResponse } = dcmjsDimse.responses;
const {
Status,
PresentationContextResult,
UserIdentityType,
RejectResult,
RejectSource,
RejectReason,
TransferSyntax,
SopClass,
StorageClass,
} = 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();
Please check the respecting Wiki section for more examples.
- dcmjs-imaging - DICOM image and overlay rendering for Node.js and browser using dcmjs.
- dcmjs-ecg - DICOM electrocardiography (ECG) rendering for Node.js and browser using dcmjs.
dcmjs-dimse is released under the MIT License.