Skip to content

Commit

Permalink
feat: Storage Commitment SCP
Browse files Browse the repository at this point in the history
  • Loading branch information
Chinlinlee committed Aug 22, 2023
1 parent 1278960 commit 5415ece
Show file tree
Hide file tree
Showing 13 changed files with 643 additions and 2 deletions.
3 changes: 3 additions & 0 deletions dimse/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const { default: CLIUtils } = require("@dcm4che/tool/common/CLIUtils");
const { JsCMoveScp } = require("./c-move");
const fileExist = require("@root/utils/file/fileExist");
const { JsCGetScp } = require("./c-get");
const { JsStgCmtScp } = require("./stgcmt");

const aeTitle = "FKQRSCP";
const host = "0.0.0.0";
Expand All @@ -46,6 +47,8 @@ class DcmQrScp {

await dicomServiceRegistry.addDicomService(new BasicCEchoSCP());

await dicomServiceRegistry.addDicomService(new JsStgCmtScp(this).get());

// #region C-STORE
let jsCStoreScp = new JsCStoreScp();
await dicomServiceRegistry.addDicomService(jsCStoreScp.get());
Expand Down
149 changes: 149 additions & 0 deletions dimse/stgcmt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
const path = require("path");
const { Commands } = require("@dcm4che/net/Commands");
const { Status } = require("@dcm4che/net/Status");
const { DicomServiceError } = require("@error/dicom-service");
const { createStgCmtSCPInjectProxy } = require("../models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm777/net/StgCmtSCPInject");
const { Attributes } = require("@dcm4che/data/Attributes");
const { Tag } = require("@dcm4che/data/Tag");
const { VR } = require("@dcm4che/data/VR");
const { raccoonConfig } = require("@root/config-class");
const { findOneInstanceFromKeysAttr } = require("./utils");
const fileExist = require("@root/utils/file/fileExist");
const { SimpleStgCmtSCP } = require("@chinlinlee/dcm777/net/SimpleStgCmtSCP");
const { SendStgCmtResult } = require("@chinlinlee/dcm777/dcmqrscp/SendStgCmtResult");

class JsStgCmtScp {
constructor(dcmQrScp) {
/** @type { import("./index").DcmQrScp } */
this.dcmQrScp = dcmQrScp;
}

get() {
return new SimpleStgCmtSCP(
this.getStgCmtInjectProxy()
);
}

getStgCmtInjectProxy() {
/** @type { import("../models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm777/net/StgCmtSCPInject").StgCmtSCPInjectInterface } */
const stgCmtInjectProxyMethods = {
onDimseRQ: async (as, pc, dimse, rq, actionInfo) => {
let rsp = await Commands.mkNActionRSP(rq, Status.Success);
let callingAet = await as.getCallingAET();
let calledAet = await as.getCalledAET();

let remoteConnection = this.dcmQrScp.getRemoteConnection(callingAet);
if (!remoteConnection)
throw new DicomServiceError(Status.ProcessingFailure, `Unknown Calling AET: ${callingAet}`);

let eventInfo;
try {
eventInfo = await this.calculateStorageCommitmentResult(calledAet, actionInfo);
} catch(e) {
console.error(e);
throw e;
}

try {
await as.writeDimseRSP(pc, rsp, null);

await this.dcmQrScp.device.execute(
await SendStgCmtResult.newInstanceAsync(
as,
eventInfo,
false,
remoteConnection
)
);
} catch(e) {
console.error(`${await as.toString()} << N-ACTION-RSP failed: ${e}`);
}
}
};

return createStgCmtSCPInjectProxy(stgCmtInjectProxyMethods, {
keepAsDaemon: true
});
}

/**
*
* @param {string} calledAet
* @param {Attributes} actionInfo
* @returns { Promise<Attributes> }
*/
async calculateStorageCommitmentResult(calledAet, actionInfo) {
let requestReq = await actionInfo.getSequence(Tag.ReferencedSOPSequence);
let size = await requestReq.size();

let eventInfo = await Attributes.newInstanceAsync(6);

await eventInfo.setString(Tag.RetrieveAETitle, VR.AE, calledAet);
await eventInfo.setString(Tag.StorageMediaFileSetID, VR.SH, raccoonConfig.mediaStorageID);
await eventInfo.setString(Tag.StorageMediaFileSetUID, VR.SH, raccoonConfig.mediaStorageUID);
await eventInfo.setString(Tag.TransactionUID, VR.UI, await actionInfo.getString(Tag.TransactionUID));
let successSeq = await eventInfo.newSequence(Tag.ReferencedSOPSequence, size);
let failedSeq = await eventInfo.newSequence(Tag.FailedSOPSequence, size);

let uidMap = {};
for (let i = 0; i < size; i++) {
/** @type { Attributes } */
let item = await requestReq.get(i);
uidMap[await item.getString(Tag.ReferencedSOPInstanceUID)] = await item.getString(Tag.ReferencedSOPClassUID);
}

for (let key in uidMap) {
let classUid = uidMap[key];
let attr = await Attributes.newInstanceAsync();
await attr.setString(Tag.SOPInstanceUID, VR.UI, key);
await attr.setString(Tag.SOPClassUID, VR.UI, classUid);

let instance = await findOneInstanceFromKeysAttr(attr);
if (instance) {
let isExist = await fileExist(
path.join(
raccoonConfig.dicomWebConfig.storeRootPath,
instance.instancePath
)
);
if (isExist) {
await successSeq.add(
await JsStgCmtScp.refSOP(key, classUid, Status.Success)
);
} else {
await failedSeq.add(
await JsStgCmtScp.refSOP(key, classUid, Status.NoSuchObjectInstance)
);
}
} else {
await failedSeq.add(
await JsStgCmtScp.refSOP(key, classUid, Status.NoSuchObjectInstance)
);
}
}

if (await failedSeq.isEmpty())
await eventInfo.remove(Tag.FailedSOPSequence);

return eventInfo;
}

/**
* @private
* @param {string} instanceUid
* @param {string} classUid
* @param {number} failureReason
* @returns { Promise<Attributes> }
*/
static async refSOP(instanceUid, classUid, failureReason) {
let attr = await Attributes.newInstanceAsync(3);
await attr.setString(Tag.ReferencedSOPClassUID, VR.UI, classUid);
await attr.setString(Tag.ReferencedSOPInstanceUID, VR.UI, instanceUid);
if (failureReason !== Status.Success) {
await attr.setInt(Tag.FailureReason, VR.US, failureReason);
}
return attr;
}
}

module.exports.JsStgCmtScp = JsStgCmtScp;
34 changes: 32 additions & 2 deletions dimse/utils.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const _ = require("lodash");
const path = require("path");
const { Attributes } = require("@dcm4che/data/Attributes");
const { mongoose } = require("mongoose");
const mongoose = require("mongoose");
const { importClass } = require("java-bridge");
const { raccoonConfig } = require("@root/config-class");
const { InstanceLocator } = require("@dcm4che/net/service/InstanceLocator");
Expand Down Expand Up @@ -66,5 +66,35 @@ async function getInstancesFromKeysAttr(keys) {
return list;
}

/**
*
* @param {Attributes} keys
* @returns
*/
async function findOneInstanceFromKeysAttr(keys) {
const { DimseQueryBuilder } = require("./queryBuilder");
let queryBuilder = new DimseQueryBuilder(keys, "instance");
let normalQuery = await queryBuilder.toNormalQuery();
let mongoQuery = await queryBuilder.getMongoQuery(normalQuery);

let returnKeys = {
"instancePath": 1,
"00020010": 1,
"00080016": 1,
"00080018": 1,
"0020000D": 1,
"0020000E": 1
};

let instance = await mongoose.model("dicom").findOne({
...mongoQuery.$match
}, returnKeys).setOptions({
strictQuery: false
}).exec();

return instance;
}

module.exports.intTagToString = intTagToString;
module.exports.getInstancesFromKeysAttr = getInstancesFromKeysAttr;
module.exports.getInstancesFromKeysAttr = getInstancesFromKeysAttr;
module.exports.findOneInstanceFromKeysAttr = findOneInstanceFromKeysAttr;
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { JavaClass, BasicOrJavaType } from "java-bridge";
import { Long as java_lang_Long } from "./../../../../../java/lang/Long";
import { Integer as java_lang_Integer } from "./../../../../../java/lang/Integer";
import { Class as java_lang_Class } from "./../../../../../java/lang/Class";
import { Association as org_dcm4che3_net_Association } from "./../../../../dcm4che3/net/Association";
import { Attributes as org_dcm4che3_data_Attributes } from "./../../../../dcm4che3/data/Attributes";
import { Boolean as java_lang_Boolean } from "./../../../../../java/lang/Boolean";
import { Connection as org_dcm4che3_net_Connection } from "./../../../../dcm4che3/net/Connection";
/**
* This class just defines types, you should import {@link SendStgCmtResult} instead of this.
* This was generated by java-bridge.
* You should probably not edit this.
*/
export declare class SendStgCmtResultClass extends JavaClass {
/**
* @return original return type: 'void'
*/
run(): Promise<void>;
/**
* @return original return type: 'void'
*/
runSync(): void;
/**
* @param var0 original type: 'long'
* @param var1 original type: 'int'
* @return original return type: 'void'
*/
wait(var0: java_lang_Long | bigint | number, var1: java_lang_Integer | number): Promise<void>;
/**
* @param var0 original type: 'long'
* @param var1 original type: 'int'
* @return original return type: 'void'
*/
waitSync(var0: java_lang_Long | bigint | number, var1: java_lang_Integer | number): void;
/**
* @return original return type: 'void'
*/
wait(): Promise<void>;
/**
* @return original return type: 'void'
*/
waitSync(): void;
/**
* @param var0 original type: 'long'
* @return original return type: 'void'
*/
wait(var0: java_lang_Long | bigint | number): Promise<void>;
/**
* @param var0 original type: 'long'
* @return original return type: 'void'
*/
waitSync(var0: java_lang_Long | bigint | number): void;
/**
* @param var0 original type: 'java.lang.Object'
* @return original return type: 'boolean'
*/
equals(var0: BasicOrJavaType | null): Promise<boolean>;
/**
* @param var0 original type: 'java.lang.Object'
* @return original return type: 'boolean'
*/
equalsSync(var0: BasicOrJavaType | null): boolean;
/**
* @return original return type: 'java.lang.String'
*/
toString(): Promise<string>;
/**
* @return original return type: 'java.lang.String'
*/
toStringSync(): string;
/**
* @return original return type: 'int'
*/
hashCode(): Promise<number>;
/**
* @return original return type: 'int'
*/
hashCodeSync(): number;
/**
* @return original return type: 'java.lang.Class'
*/
getClass(): Promise<java_lang_Class>;
/**
* @return original return type: 'java.lang.Class'
*/
getClassSync(): java_lang_Class;
/**
* @return original return type: 'void'
*/
notify(): Promise<void>;
/**
* @return original return type: 'void'
*/
notifySync(): void;
/**
* @return original return type: 'void'
*/
notifyAll(): Promise<void>;
/**
* @return original return type: 'void'
*/
notifyAllSync(): void;
/**
* @param var0 original type: 'org.dcm4che3.net.Association'
* @param var1 original type: 'org.dcm4che3.data.Attributes'
* @param var2 original type: 'boolean'
* @param var3 original type: 'org.dcm4che3.net.Connection'
* @return original return type: 'org.github.chinlinlee.dcm777.dcmqrscp.SendStgCmtResult'
*/
static newInstanceAsync(var0: org_dcm4che3_net_Association | null, var1: org_dcm4che3_data_Attributes | null, var2: java_lang_Boolean | boolean, var3: org_dcm4che3_net_Connection | null): Promise<SendStgCmtResult>;
/**
* @param var0 original type: 'org.dcm4che3.net.Association'
* @param var1 original type: 'org.dcm4che3.data.Attributes'
* @param var2 original type: 'boolean'
* @param var3 original type: 'org.dcm4che3.net.Connection'
*/
constructor(var0: org_dcm4che3_net_Association | null, var1: org_dcm4che3_data_Attributes | null, var2: java_lang_Boolean | boolean, var3: org_dcm4che3_net_Connection | null);
}
declare const SendStgCmtResult_base: typeof SendStgCmtResultClass;
/**
* Class org.github.chinlinlee.dcm777.dcmqrscp.SendStgCmtResult.
*
* This actually imports the java class for further use.
* The class {@link SendStgCmtResultClass} only defines types, this is the class you should actually import.
* Please note that this statement imports the underlying java class at runtime, which may take a while.
* This was generated by java-bridge.
* You should probably not edit this.
*/
export declare class SendStgCmtResult extends SendStgCmtResult_base {
}
export default SendStgCmtResult;
//# sourceMappingURL=SendStgCmtResult.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SendStgCmtResult = void 0;
const java_bridge_1 = require("java-bridge");
/**
* Class org.github.chinlinlee.dcm777.dcmqrscp.SendStgCmtResult.
*
* This actually imports the java class for further use.
* The class {@link SendStgCmtResultClass} only defines types, this is the class you should actually import.
* Please note that this statement imports the underlying java class at runtime, which may take a while.
* This was generated by java-bridge.
* You should probably not edit this.
*/
class SendStgCmtResult extends (0, java_bridge_1.importClass)('org.github.chinlinlee.dcm777.dcmqrscp.SendStgCmtResult') {
}
exports.SendStgCmtResult = SendStgCmtResult;
exports.default = SendStgCmtResult;
Loading

0 comments on commit 5415ece

Please sign in to comment.