// Copyright (c) 2012-2020 fo-dicom contributors. // Licensed under the Microsoft Public License (MS-PL). using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Dicom; using Dicom.Log; using Dicom.Network; using Worklist_SCP.Model; using System.Configuration; namespace Worklist_SCP { public class WorklistService : DicomService, IDicomServiceProvider, IDicomCEchoProvider, IDicomCFindProvider, IDicomNServiceProvider { private static readonly DicomTransferSyntax[] AcceptedTransferSyntaxes = new DicomTransferSyntax[] { DicomTransferSyntax.ExplicitVRLittleEndian, DicomTransferSyntax.ExplicitVRBigEndian, DicomTransferSyntax.ImplicitVRLittleEndian }; private IMppsSource _mppsSource; private IMppsSource MppsSource { get { if (_mppsSource == null) { _mppsSource = new MppsHandler(Logger); } return _mppsSource; } } public WorklistService(INetworkStream stream, Encoding fallbackEncoding, Logger log) : base(stream, fallbackEncoding, log) { } public DicomCEchoResponse OnCEchoRequest(DicomCEchoRequest request) { Logger.Info($"Received verification request from AE {Association.CallingAE} with IP: {Association.RemoteHost}"); return new DicomCEchoResponse(request, DicomStatus.Success); } public IEnumerable OnCFindRequest(DicomCFindRequest request) { // you should validate the level of the request. I leave it here since there is a bug in version 3.0.2 // from version 4 on this should be done //if (request.Level != DicomQueryRetrieveLevel.Worklist) //{ // yield return new DicomCFindResponse(request, DicomStatus.QueryRetrieveUnableToProcess); //} //else //{ var configType = ConfigurationManager.AppSettings["Type"]; IEnumerable exams = null; Console.Write($"configType: {configType}"); switch (configType) { case "API": exams = WorklistApiHandler.FilterWorklistItems(request.Dataset); break; case "JSON": exams = WorklistJsonHandler.FilterWorklistItems(request.Dataset); break; case "SQLServer": exams = WorklistSQLServerHandler.FilterWorklistItems(request.Dataset); break; case "DB": break; default: exams = WorklistHandler.FilterWorklistItems(request.Dataset, new WorklistItemsProvider().GetAllCurrentWorklistItems()); break; } //var exams = WorklistApiHandler.FilterWorklistItems(request.Dataset, WorklistServer.CurrentWorklistItems); foreach (DicomDataset result in exams) //foreach (DicomDataset result in WorklistHandler.FilterWorklistItems(request.Dataset, WorklistServer.CurrentWorklistItems)) { yield return new DicomCFindResponse(request, DicomStatus.Pending) { Dataset = result }; } yield return new DicomCFindResponse(request, DicomStatus.Success); //} } public void OnConnectionClosed(Exception exception) { Clean(); } public void OnReceiveAbort(DicomAbortSource source, DicomAbortReason reason) { //log the abort reason Logger.Error($"Received abort from {source}, reason is {reason}"); } public Task OnReceiveAssociationReleaseRequestAsync() { Clean(); return SendAssociationReleaseResponseAsync(); } public Task OnReceiveAssociationRequestAsync(DicomAssociation association) { Logger.Info($"Received association request from AE: {association.CallingAE} with IP: {association.RemoteHost} "); //if (WorklistServer.AETitle != association.CalledAE) //{ // Logger.Error($"Association with {association.CallingAE} rejected since called aet {association.CalledAE} is unknown"); // return SendAssociationRejectAsync(DicomRejectResult.Permanent, DicomRejectSource.ServiceUser, DicomRejectReason.CalledAENotRecognized); //} foreach (var pc in association.PresentationContexts) { if (pc.AbstractSyntax == DicomUID.Verification || pc.AbstractSyntax == DicomUID.ModalityWorklistInformationModelFIND || pc.AbstractSyntax == DicomUID.ModalityPerformedProcedureStepSOPClass || pc.AbstractSyntax == DicomUID.ModalityPerformedProcedureStepNotificationSOPClass || pc.AbstractSyntax == DicomUID.ModalityPerformedProcedureStepNotificationSOPClass) { pc.AcceptTransferSyntaxes(AcceptedTransferSyntaxes); } else { Logger.Warn($"Requested abstract syntax {pc.AbstractSyntax} from {association.CallingAE} not supported"); pc.SetResult(DicomPresentationContextResult.RejectAbstractSyntaxNotSupported); } } Logger.Info($"Accepted association request from {association.CallingAE}"); return SendAssociationAcceptAsync(association); } public void Clean() { // cleanup, like cancel outstanding move- or get-jobs } public DicomNCreateResponse OnNCreateRequest(DicomNCreateRequest request) { if (request.SOPClassUID != DicomUID.ModalityPerformedProcedureStepSOPClass) { return new DicomNCreateResponse(request, DicomStatus.SOPClassNotSupported); } // on N-Create the UID is stored in AffectedSopInstanceUID, in N-Set the UID is stored in RequestedSopInstanceUID var affectedSopInstanceUID = request.Command.GetSingleValue(DicomTag.AffectedSOPInstanceUID); Logger.Log(LogLevel.Info, $"reeiving N-Create with SOPUID {affectedSopInstanceUID}"); // get the procedureStepIds from the request var procedureStepId = request.Dataset .GetSequence(DicomTag.ScheduledStepAttributesSequence) .First() .GetSingleValue(DicomTag.ScheduledProcedureStepID); var ok = MppsSource.SetInProgress(affectedSopInstanceUID, procedureStepId); return new DicomNCreateResponse(request, ok ? DicomStatus.Success : DicomStatus.ProcessingFailure); } public DicomNSetResponse OnNSetRequest(DicomNSetRequest request) { if (request.SOPClassUID != DicomUID.ModalityPerformedProcedureStepSOPClass) { return new DicomNSetResponse(request, DicomStatus.SOPClassNotSupported); } // on N-Create the UID is stored in AffectedSopInstanceUID, in N-Set the UID is stored in RequestedSopInstanceUID var requestedSopInstanceUID = request.Command.GetSingleValue(DicomTag.RequestedSOPInstanceUID); Logger.Log(LogLevel.Info, $"receiving N-Set with SOPUID {requestedSopInstanceUID}"); var status = request.Dataset.GetSingleValue(DicomTag.PerformedProcedureStepStatus); if (status == "COMPLETED") { // most vendors send some informations with the mpps-completed message. // this information should be stored into the datbase var doseDescription = request.Dataset.GetSingleValueOrDefault(DicomTag.CommentsOnRadiationDose, string.Empty); var listOfInstanceUIDs = new List(); foreach (var seriesDataset in request.Dataset.GetSequence(DicomTag.PerformedSeriesSequence)) { // you can read here some information about the series that the modalidy created //seriesDataset.Get(DicomTag.SeriesDescription, string.Empty); //seriesDataset.Get(DicomTag.PerformingPhysicianName, string.Empty); //seriesDataset.Get(DicomTag.ProtocolName, string.Empty); foreach (var instanceDataset in seriesDataset.GetSequence(DicomTag.ReferencedImageSequence)) { // here you can read the SOPClassUID and SOPInstanceUID var instanceUID = instanceDataset.GetSingleValueOrDefault(DicomTag.ReferencedSOPInstanceUID, string.Empty); if (!string.IsNullOrEmpty(instanceUID)) listOfInstanceUIDs.Add(instanceUID); } } var ok = MppsSource.SetCompleted(requestedSopInstanceUID, doseDescription, listOfInstanceUIDs); return new DicomNSetResponse(request, ok ? DicomStatus.Success : DicomStatus.ProcessingFailure); } else if (status == "DISCONTINUED") { // some vendors send a reason code or description with the mpps-discontinued message // var reason = request.Dataset.Get(DicomTag.PerformedProcedureStepDiscontinuationReasonCodeSequence); var ok = MppsSource.SetDiscontinued(requestedSopInstanceUID, string.Empty); return new DicomNSetResponse(request, ok ? DicomStatus.Success : DicomStatus.ProcessingFailure); } else { return new DicomNSetResponse(request, DicomStatus.InvalidAttributeValue); } } #region not supported methods but that are required because of the interface public DicomNDeleteResponse OnNDeleteRequest(DicomNDeleteRequest request) { Logger.Log(LogLevel.Info, "receiving N-Delete, not supported"); return new DicomNDeleteResponse(request, DicomStatus.UnrecognizedOperation); } public DicomNEventReportResponse OnNEventReportRequest(DicomNEventReportRequest request) { Logger.Log(LogLevel.Info, "receiving N-Event, not supported"); return new DicomNEventReportResponse(request, DicomStatus.UnrecognizedOperation); } public DicomNGetResponse OnNGetRequest(DicomNGetRequest request) { Logger.Log(LogLevel.Info, "receiving N-Get, not supported"); return new DicomNGetResponse(request, DicomStatus.UnrecognizedOperation); } public DicomNActionResponse OnNActionRequest(DicomNActionRequest request) { Logger.Log(LogLevel.Info, "receiving N-Action, not supported"); return new DicomNActionResponse(request, DicomStatus.UnrecognizedOperation); } #endregion } }