This commit is contained in:
Aysel Afsar 2016-03-16 21:27:16 -04:00
commit d1d4e09815
11 changed files with 452 additions and 33 deletions

View File

@ -60,7 +60,8 @@ CSocket = function(socket, options) {
this.on("released", function() {
this.released();
});
this.on('aborted', function() {
this.on('aborted', function(pdu) {
console.log('Assocation aborted with reason ' + pdu.reason);
this.released();
})
this.on('message', function(pdvs) {
@ -153,11 +154,20 @@ CSocket.prototype.setPresentationContexts = function(uids) {
var contexts = [],
id = 0;
uids.forEach(function(uid) {
contexts.push({
id: ++id,
abstractSyntax: uid,
transferSyntaxes: [C.IMPLICIT_LITTLE_ENDIAN, C.EXPLICIT_LITTLE_ENDIAN, C.EXPLICIT_BIG_ENDIAN]
});
id++;
if (typeof uid == 'string') {
contexts.push({
id: id,
abstractSyntax: uid,
transferSyntaxes: [C.IMPLICIT_LITTLE_ENDIAN, C.EXPLICIT_LITTLE_ENDIAN, C.EXPLICIT_BIG_ENDIAN]
});
} else {
contexts.push({
id: id,
abstractSyntax: uid.context,
transferSyntaxes: uid.syntaxes
});
}
});
this.presentationContexts = contexts;
};
@ -174,9 +184,7 @@ CSocket.prototype.send = function(pdu, afterCbk) {
//console.log('SEND PDU-TYPE: ', pdu.type);
var toSend = pdu.buffer();
//console.log('send buffer', toSend.toString('hex'));
this.socket.write(toSend, afterCbk ? afterCbk : function() {
//console.log('Data written');
});
return this.socket.write(toSend, afterCbk ? afterCbk : null);
};
CSocket.prototype.release = function() {
@ -318,7 +326,7 @@ CSocket.prototype.interpret = function(stream) {
this.emit('released');
} else if (pdu.is(C.ITEM_TYPE_PDU_AABORT)) {
//console.log('Aborted');
this.emit('aborted');
this.emit('aborted', pdu);
} else if (pdu.is(C.ITEM_TYPE_PDU_PDATA)) {
pdatas.push(pdu);
}
@ -363,16 +371,19 @@ CSocket.prototype.receivedMessage = function(pdv) {
this.lastCommand = msg;
if (msg.isResponse()) {
var replyId = msg.respondedTo(),
listener = this.messages[replyId].listener;
if (msg.is(C.COMMAND_C_GET_RSP) || msg.is(C.COMMAND_C_MOVE_RSP)) {
//console.log('remaining', msg.getNumOfRemainingSubOperations(), msg.getNumOfCompletedSubOperations());
}
if (msg.failure()) {
//console.log("message failed with status ", msg.getStatus().toString(16));
console.log("message failed with status ", msg.getStatus().toString(16));
}
listener.emit('response', msg);
if (msg.isFinal()) {
var replyId = msg.respondedTo();
if (this.messages[replyId].listener) {
this.messages[replyId].listener.emit('end', msg);
if (listener) {
listener.emit('end', msg);
if (!msg.haveData())
delete this.messages[replyId];
@ -474,9 +485,9 @@ CSocket.prototype.sendMessage = function(context, command, dataset) {
msgData.command = command;
this.messages[messageId] = msgData;
console.log('Sending command ' + command.typeString());
this.send(pdata);
if (dataset) {
if (dataset && typeof dataset == 'object') {
dataset.setSyntax(syntax);
var dsData = new PDataTF(),
dPdv = new PresentationDataValueItem(cid);
@ -488,6 +499,14 @@ CSocket.prototype.sendMessage = function(context, command, dataset) {
return msgData.listener;
};
CSocket.prototype.sendPData = function(pdv, after) {
var pdata = new PDataTF();
pdata.setPresentationDataValueItems([pdv]);
//console.log('Sending pdata');
//console.log(pdata.totalLength());
this.send(pdata, after);
};
CSocket.prototype.verify = function() {
this.setPresentationContexts([C.SOP_VERIFICATION]);
this.startAssociationRequest(function() {
@ -515,6 +534,14 @@ CSocket.prototype.move = function(destination, params, options) {
return this.sendMessage(options.context, moveMessage, this.wrapMessage(params));
};
CSocket.prototype.storeInstance = function(sopClassUID, sopInstanceUID, options) {
var storeMessage = new CStoreRQ();
storeMessage.setAffectedSOPInstanceUID(sopInstanceUID);
storeMessage.setAffectedSOPClassUID(sopClassUID);
return this.sendMessage(sopClassUID, storeMessage, true);
}
CSocket.prototype.moveInstances = function(destination, params, options) {
var sendParams = Object.assign({
0x00080052: C.QUERY_RETRIEVE_LEVEL_IMAGE,

View File

@ -1,4 +1,5 @@
var EventEmitter = Npm.require('events').EventEmitter, net = Npm.require('net'), Socket = net.Socket;
var net = Npm.require('net'),
Socket = net.Socket;
var DEFAULT_MAX_PACKAGE_SIZE = 32768;
@ -21,6 +22,11 @@ Connection = function(options) {
util.inherits(Connection, EventEmitter);
var StoreHandle = function() {
EventEmitter.call(this);
}
util.inherits(StoreHandle, EventEmitter);
Connection.prototype.addPeer = function(options) {
if (!options.aeTitle || !options.host || !options.port) {
return false;
@ -65,6 +71,92 @@ Connection.prototype.selectPeer = function(aeTitle) {
return this.peers[aeTitle];
}
Connection.prototype._sendFile = function(socket, sHandle, file, maxSend, metaLength, list) {
var fileNameText = typeof file.file == 'string' ? file.file : 'buffer';
console.log("Sending file " + fileNameText);
var useContext = socket.getContextByUID(file.context), self = this;
PDU.generatePDatas(useContext.id, file.file, maxSend, null, metaLength, function(err, handle) {
if (err) {
console.log('Error while sending file');
return;
}
var processNext = function() {
var next = list.shift();
if (next)
self._sendFile(socket, sHandle, next, maxSend, metaLength, list);
else
socket.release();
};
var store = socket.storeInstance(useContext.abstractSyntax, file.uid);
handle.on('pdv', function(pdv) {
socket.sendPData(pdv);
});
handle.on('error', function(err) {
sHandle.emit('file', err, fileNameText);
processNext();
});
store.on('response', function(msg) {
var statusText = msg.getStatus().toString(16);
console.log('STORE reponse with status', statusText);
var error = null;
if (msg.failure()) {
error = new Error(statusText);
}
sHandle.emit('file', error, fileNameText);
processNext();
});
});
};
Connection.prototype.storeInstances = function(fileList) {
var contexts = {}, read = 0, length = fileList.length, toSend = [], self = this, handle = new StoreHandle();
fileList.forEach(function(bufferOrFile){
var fileNameText = typeof bufferOrFile == 'string' ? bufferOrFile : 'buffer';
DicomMessage.readMetaHeader(bufferOrFile, function(err, metaMessage, metaLength) {
read++;
if (err) {
handle.emit('file', err, fileNameText);
return;
}
console.log('Dicom file ' + (typeof bufferOrFile == 'string' ? bufferOrFile : 'buffer') + ' found');
var syntax = metaMessage.getValue(0x00020010),
sopClassUID = metaMessage.getValue(0x00020002),
instanceUID = metaMessage.getValue(0x00020003);
if (!contexts[sopClassUID]) {
contexts[sopClassUID] = [];
}
if (syntax && contexts[sopClassUID].indexOf(syntax) == -1) {
contexts[sopClassUID].push(syntax);
}
toSend.push({file : bufferOrFile, context : sopClassUID, uid : instanceUID});
if (read == length) {
var useContexts = [];
for (var context in contexts) {
var useSyntaxes = contexts[context];
if (useSyntaxes.length > 0) {
useContexts.push({context : context, syntaxes : contexts[context]});
} else {
throw "No syntax specified for context " + context;
}
}
self.associate({
contexts : useContexts
}, function(ac) {
var maxSend = ac.getMaxSize(), next = toSend.shift();
self._sendFile(this, handle, next, maxSend, metaLength, toSend);
});
}
});
});
return handle;
};
Connection.prototype.storeResponse = function(messageId, msg) {
var rq = this.messages[messageId];

View File

@ -179,6 +179,13 @@ DIMSE.retrieveInstances = function(studyInstanceUID, seriesInstanceUID, params,
return future.wait();
};
DIMSE.storeInstances = function(fileList) {
var handle = conn.storeInstances(fileList);
handle.on('file', function(err, file) {
console.log(err, file);
})
};
DIMSE.moveInstances = function(studyInstanceUID, seriesInstanceUID, sopInstanceUID, sopClassUID, params) {
DIMSE.associate([C.SOP_STUDY_ROOT_MOVE, sopClassUID], function() {
var defaultParams = {

View File

@ -73,7 +73,7 @@ ValueRepresentation.prototype.read = function(stream, length, syntax) {
if (!length)
return this.defaultValue;
if (this.maxLength != length)
throw "Invalid length for fixed length tag, vr " + this.type + ", length " + this.maxLength + " != " + length;
console.log("Invalid length for fixed length tag, vr " + this.type + ", length " + this.maxLength + " != " + length);
}
return this.readBytes(stream, length, syntax);
}
@ -212,11 +212,12 @@ DateValue = function() {
util.inherits(DateValue, ValueRepresentation);
DateValue.prototype.readBytes = function(stream, length) {
var datestr = stream.read(C.TYPE_ASCII, 8);
var datestr = stream.read(C.TYPE_ASCII, length);
var year = parseInt(datestr.substring(0,4)),
month = parseInt(datestr.substring(4,6)),
day = parseInt(datestr.substring(6,8));
return datestr;//new Date(year, month, day);
}
@ -701,6 +702,20 @@ OtherWordString.prototype.getFields = function(value) {
return OtherWordString.super_.prototype.getFields.call(this, [new StringField(value)]);
}
OtherByteString = function() {
ValueRepresentation.call(this, "OB");
this.maxLength = null;
this.padByte = "00";
};
util.inherits(OtherByteString, ValueRepresentation);
OtherByteString.prototype.readBytes = function(stream, length) {
return stream.read(C.TYPE_HEX, length);
}
OtherByteString.prototype.getFields = function(value) {
return OtherByteString.super_.prototype.getFields.call(this, [new HexField(value)]);
}
elementByType = function(type, value, syntax) {
var elem = null, nk = DicomElements.dicomNDict[type];
@ -789,6 +804,18 @@ readElements = function(stream, syntax) {
stream.setEndian(oldEndian);
}
readAElement = function(stream, syntax) {
var elem = newElementWithSyntax(syntax);
elem.readBytes(stream);
return elem;
}
newElementWithSyntax = function(syntax) {
var elem = new DataElement();
elem.setSyntax(syntax);
return elem;
}
var explicitVRList = ["OB", "OW", "OF", "SQ", "UC", "UR", "UT", "UN"],
binaryVRs = ["FL", "FD", "SL", "SS", "UL", "US"];

View File

@ -15,6 +15,21 @@ Field.prototype.isNumeric = function() {
return false;
}
BufferField = function(buffer, start, length) {
Field.call(this, C.TYPE_BUFFER, buffer);
this.bufferLength = length;
this.bufferStart = start;
}
util.inherits(BufferField, Field);
BufferField.prototype.length = function() {
return this.bufferLength;
}
BufferField.prototype.write = function(stream) {
stream.writeRawBuffer(this.value, this.bufferStart, this.bufferLength);
}
StringField = function(str) {
Field.call(this, C.TYPE_ASCII, typeof str == 'string' ? str : "");
}

View File

@ -173,7 +173,7 @@ DicomMessage.prototype.printElements = function(pairs, indent) {
return typeName;
}
DicomMessage.prototype.toString = function() {
DicomMessage.prototype.typeString = function() {
var typeName = "";
if (!this.isCommand()) {
typeName = "DateSet Message";
@ -188,7 +188,12 @@ DicomMessage.prototype.toString = function() {
case C.COMMAND_C_FIND_RQ : typeName = "C-FIND-RQ"; break;
case C.COMMAND_C_STORE_RSP : typeName = "C-STORE-RSP"; break;
}
}
}
return typeName;
}
DicomMessage.prototype.toString = function() {
var typeName = this.typeString();
typeName += " [\n";
typeName += this.printElements(this.elementPairs, 0);
typeName += "]";
@ -217,6 +222,83 @@ DicomMessage.prototype.toObject = function() {
return this.walkObject(this.elementPairs);
}
DicomMessage.readToPairs = function(stream, syntax, options) {
var pairs = {};
while (!stream.end()) {
var elem = new DataElement();
if (options) {
elem.setOptions(options);
}
elem.setSyntax(syntax);
elem.readBytes(stream);
pairs[elem.tag.value] = elem;
}
return pairs;
}
var fileValid = function(stream) {
return stream.readString(4, C.TYPE_ASCII) == 'DICM';
}
var readMetaStream = function(stream, useSyntax, length, callback) {
var message = new FileMetaMessage();
message.setElementPairs(DicomMessage.readToPairs(stream, useSyntax));
if (callback) {
callback(null, message, length);
}
return message;
}
DicomMessage.readMetaHeader = function(bufferOrFile, callback) {
var useSyntax = C.EXPLICIT_LITTLE_ENDIAN;
if (bufferOrFile instanceof Buffer) {
var stream = new ReadStream(bufferOrFile);
stream.reset();
stream.increment(128);
if (!fileValid(stream)) {
return quitWithError('Invalid a dicom file ', callback);
}
var el = readAElement(stream, useSyntax),
metaLength = el.value,
metaStream = stream.more(metaLength);
return readMetaStream(metaStream, useSyntax, metaLength, callback);
} else if (typeof bufferOrFile == 'string') {
fs.open(bufferOrFile, 'r', function(err, fd) {
if (err) {
//fs.closeSync(fd);
return quitWithError('Cannot open file', callback);
}
var buffer = new Buffer(16);
fs.read(fd, buffer, 0, 16, 128, function(err, bytesRead) {
if (err || bytesRead != 16) {
fs.closeSync(fd);
return quitWithError('Cannot read file', callback);
}
var stream = new ReadStream(buffer);
if (!fileValid(stream)) {
fs.closeSync(fd);
return quitWithError('Not a dicom file ' + bufferOrFile, callback);
}
var el = readAElement(stream, useSyntax),
metaLength = el.value,
metaBuffer = new Buffer(metaLength);
fs.read(fd, metaBuffer, 0, metaLength, 144, function(err, bytesRead) {
fs.closeSync(fd);
if (err || bytesRead != metaLength) {
return quitWithError('Invalid a dicom file ' + bufferOrFile, callback);
}
var metaStream = new ReadStream(metaBuffer);
return readMetaStream(metaStream, useSyntax, metaLength, callback);
});
});
});
}
return null;
}
DicomMessage.read = function(stream, type, syntax, options) {
var elements = [], pairs = {}, useSyntax = type == C.DATA_TYPE_COMMAND ? C.IMPLICIT_LITTLE_ENDIAN : syntax;
stream.reset();
@ -240,6 +322,7 @@ DicomMessage.read = function(stream, type, syntax, options) {
case 0x8010 : message = new CGetRSP(useSyntax); break;
case 0x0001 : message = new CStoreRQ(useSyntax); break;
case 0x0020 : message = new CFindRQ(useSyntax); break;
case 0x8001 : message = new CStoreRSP(useSyntax); break;
default : throw "Unrecognized command type " + cmdType.toString(16); break;
}
@ -270,6 +353,12 @@ DataSetMessage.prototype.is = function(type) {
return false;
}
FileMetaMessage = function(syntax){
DicomMessage.call(this, syntax);
this.type = null
};
util.inherits(FileMetaMessage, DicomMessage);
CommandMessage = function(syntax) {
DicomMessage.call(this, syntax);
this.type = C.DATA_TYPE_COMMAND;
@ -438,10 +527,18 @@ CStoreRQ.prototype.getMoveMessageId = function() {
return this.getValue(0x00001031);
}
CStoreRQ.prototype.getSOPInstanceUID = function() {
CStoreRQ.prototype.getAffectedSOPInstanceUID = function() {
return this.getValue(0x00001000);
}
CStoreRQ.prototype.setAffectedSOPInstanceUID = function(uid) {
this.setElement(0x00001000, uid);
}
CStoreRQ.prototype.setAffectedSOPClassUID = function(uid) {
this.setElement(0x00000002, uid);
}
CStoreRSP = function(syntax) {
CommandResponse.call(this, syntax);
this.commandType = 0x8001;

View File

@ -1,3 +1,8 @@
PDVHandle = function() {
}
util.inherits(PDVHandle, EventEmitter);
PDU = function() {
this.fields = [];
this.lengthBytes = 4;
@ -103,6 +108,100 @@ mergePDVs = function(pdvs) {
return merges;
}
PDU.splitPData = function(pdata, maxSize) {
var totalLength = pdata.totalLength();
if (totalLength > maxSize) {
//split into chunks of pdatas
var chunks = Math.floor(totalLength / maxSize), left = totalLength % maxSize;
for (var i = 0;i < chunks;i++) {
if (i == chunks - 1) {
if (left < 6) {
//need to move some of the last chunk
}
}
}
} else {
return [pdata];
}
}
var readChunk = function(fd, bufferSize, slice, callback) {
var buffer = new Buffer(bufferSize), length = slice.length, start = slice.start;
fs.read(fd, buffer, 0, length, start, function(err, bytesRead) {
callback(err, bytesRead, buffer, slice);
});
}
PDU.generatePDatas = function(context, bufferOrFile, maxSize, length, metaLength, callback) {
var total, isFile = false;
if (typeof bufferOrFile == 'string') {
var stats = fs.statSync(bufferOrFile);
total = stats['size'];
isFile = true;
} else if (bufferOrFile instanceof Buffer) {
total = length ? length : bufferOrFile.length;
}
var handler = new PDVHandle();
var slices = [], start = metaLength + 144, index = 0;
maxSize -= 6;
while (start < total) {
var sliceLength = maxSize, isLast = false;
if (total - start < maxSize) {
sliceLength = total - start;
isLast = true;
}
slices.push({start : start, length : sliceLength, isLast : isLast, index : index});
start += sliceLength;
index++;
}
if (isFile) {
fs.open(bufferOrFile, 'r', function(err, fd) {
if (err) {
//fs.closeSync(fd);
return quitWithError(err, callback);
} else callback(null, handler);
var after = function(err, bytesRead, buffer, slice) {
if (err) {
fs.closeSync(fd);
handler.emit('error', err);
return;
}
var pdv = new RawDataPDV(context, buffer, 0, slice.length, slice.isLast);
handler.emit('pdv', pdv);
if (slices.length < 1) {
handler.emit('end');
fs.closeSync(fd);
} else {
var next = slices.shift();
readChunk(fd, maxSize, next, after);
}
};
var sl = slices.shift();
readChunk(fd, maxSize, sl, after);
});
} else {
for (var i = 0;i < slices.length;i++) {
var toSlice = slices[i];
var buffer = bufferOrFile.slice(toSlice.start, toSlice.length);
var pdv = new RawDataPDV(context, buffer, 0, toSlice.length, toSlice.isLast);
handler.emit('pdv', pdv);
if (i == slices.length - 1) {
handler.emit('end');
}
}
}
return;
}
PDU.typeToString = function(type) {
var pdu = null, typeNum = parseInt(type, 16);
//console.log("RECEIVED PDU-TYPE ", typeNum);
@ -269,6 +368,18 @@ AssociateAC.prototype.readBytes = function(stream, length) {
this.setUserInformationItem(userItem);
}
AssociateAC.prototype.getMaxSize = function() {
var items = this.userInformationItem.userDataItems, length = items.length, size = null;
for (var i = 0;i < length;i++) {
if (items[i].is(C.ITEM_TYPE_MAXIMUM_LENGTH)) {
size = items[i].maximumLengthReceived;
break;
}
}
return size;
}
AssociateAbort = function() {
this.type = C.ITEM_TYPE_PDU_AABORT;
this.source = 1;
@ -346,6 +457,12 @@ PDataTF.prototype.getFields = function() {
return PDataTF.super_.prototype.getFields.call(this, fields);
}
PDataTF.prototype.totalLength = function() {
var fields = this.presentationDataValueItems;
return this.length(fields);
}
PDataTF.prototype.readBytes = function(stream, length) {
var pdvs = this.loadPDV(stream, length);
//let merges = mergePDVs(pdvs);
@ -422,19 +539,32 @@ PresentationDataValueItem.prototype.getFields = function() {
fields.push(new UInt8Field(messageHeader));
fields.push(this.dataFragment);
/*var stream = new WriteStream();this.dataFragment.write(stream);
//var f = this.dataFragment.getFields();
//var wr = new WriteStream();f[0].setSyntax(this.dataFragment.syntax);f[0].write(wr);
var rst = stream.toReadBuffer();
rst.setEndian(C.LITTLE_ENDIAN);
var group = rst.read(C.TYPE_UINT16),
element = rst.read(C.TYPE_UINT16),
tag = tagFromNumbers(group, element);
console.log(tag.toString(), rst.read(C.TYPE_UINT32));*/
return PresentationDataValueItem.super_.prototype.getFields.call(this, fields);
}
RawDataPDV = function(context, buffer, start, length, isLast) {
this.type = null;
this.isLast = isLast;
this.dataFragmentBuffer = buffer;
this.bufferStart = start;
this.bufferLength = length;
this.contextId = context;
Item.call(this);
this.lengthBytes = 4;
};
util.inherits(RawDataPDV, Item);
RawDataPDV.prototype.getFields = function() {
var fields = [new UInt8Field(this.contextId)];
var messageHeader = (this.isLast ? 1 : 0) << 1;
fields.push(new UInt8Field(messageHeader));
fields.push(new BufferField(this.dataFragmentBuffer, this.bufferStart, this.bufferLength));
return RawDataPDV.super_.prototype.getFields.call(this, fields);
}
ApplicationContextItem = function() {
this.type = C.ITEM_TYPE_APPLICATION_CONTEXT;
this.applicationContextName = C.APPLICATION_CONTEXT_NAME;

View File

@ -85,6 +85,13 @@ WriteStream.prototype.writeToBuffer = function(type, value, length) {
this.increment(length);
}
WriteStream.prototype.writeRawBuffer = function(source, start, length) {
if (!source) return;
this.checkSize(length);
source.copy(this.rawBuffer, this.offset, start, length);
this.increment(length);
}
WriteStream.prototype.write = function(type, value) {
if (isString(type)) {
this.writeString(value, type);

View File

@ -42,7 +42,8 @@ C = {
TYPE_DOUBLE : 8,
TYPE_INT8 : 9,
TYPE_INT16 : 10,
TYPE_INT32 : 11,
TYPE_INT32 : 11,
TYPE_BUFFER : 12,
RESULT_REASON_ACCEPTANCE : 0,
RESULT_REASON_USER_REJECTION : 1,
RESULT_REASON_NO_REASON :2,

View File

@ -1,4 +1,14 @@
var dicomNDict = {
0x00020000 : { vr : 'UL', vm : C.VM_SINGLE, keyword : "FileMetaInformationGroupLength" },
0x00020001 : { vr : 'OB', vm : C.VM_SINGLE, keyword : "FileMetaInformationVersion" },
0x00020002 : { vr : 'UI', vm : C.VM_SINGLE, keyword : "MediaStorageSOPClassUID" },
0x00020003 : { vr : 'UI', vm : C.VM_SINGLE, keyword : "MediaStorageSOPInstanceUID" },
0x00020010 : { vr : 'UI', vm : C.VM_SINGLE, keyword : "TransferSyntaxUID" },
0x00020012 : { vr : 'UI', vm : C.VM_SINGLE, keyword : "ImplementationClassUID" },
0x00020013 : { vr : 'SH', vm : C.VM_SINGLE, keyword : "ImplementationVersionName" },
0x00020016 : { vr : 'AE', vm : C.VM_SINGLE, keyword : "SourceApplicationEntityTitle" },
0x00020100 : { vr : 'UI', vm : C.VM_SINGLE, keyword : "PrivateInformationCreatorUID" },
0x00020102 : { vr : 'OB', vm : C.VM_SINGLE, keyword : "PrivateInformation" },
0x00080001 : { vr : "UL", vm : C.VM_SINGLE, keyword : "LengthToEnd"},
0x00080005 : { vr : "CS", vm : C.VM_1N, keyword : "SpecificCharacterSet"},
0x00080006 : { vr : "SQ", vm : C.VM_SINGLE, keyword : "LanguageCodeSequence"},

View File

@ -1 +1,7 @@
util = Npm.require("util");
util = Npm.require("util");
fs = Npm.require('fs');
EventEmitter = Npm.require('events').EventEmitter;
quitWithError = function(message, callback) {
return callback(new Error(message), null);
};