WorklistService.cs 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. // Copyright (c) 2012-2020 fo-dicom contributors.
  2. // Licensed under the Microsoft Public License (MS-PL).
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Linq;
  6. using System.Text;
  7. using System.Threading.Tasks;
  8. using Dicom;
  9. using Dicom.Log;
  10. using Dicom.Network;
  11. using Worklist_SCP.Model;
  12. namespace Worklist_SCP
  13. {
  14. public class WorklistService : DicomService, IDicomServiceProvider, IDicomCEchoProvider, IDicomCFindProvider, IDicomNServiceProvider
  15. {
  16. private static readonly DicomTransferSyntax[] AcceptedTransferSyntaxes = new DicomTransferSyntax[]
  17. {
  18. DicomTransferSyntax.ExplicitVRLittleEndian,
  19. DicomTransferSyntax.ExplicitVRBigEndian,
  20. DicomTransferSyntax.ImplicitVRLittleEndian
  21. };
  22. private IMppsSource _mppsSource;
  23. private IMppsSource MppsSource
  24. {
  25. get
  26. {
  27. if (_mppsSource == null)
  28. {
  29. _mppsSource = new MppsHandler(Logger);
  30. }
  31. return _mppsSource;
  32. }
  33. }
  34. public WorklistService(INetworkStream stream, Encoding fallbackEncoding, Logger log) : base(stream, fallbackEncoding, log)
  35. {
  36. }
  37. public DicomCEchoResponse OnCEchoRequest(DicomCEchoRequest request)
  38. {
  39. Logger.Info($"Received verification request from AE {Association.CallingAE} with IP: {Association.RemoteHost}");
  40. return new DicomCEchoResponse(request, DicomStatus.Success);
  41. }
  42. public IEnumerable<DicomCFindResponse> OnCFindRequest(DicomCFindRequest request)
  43. {
  44. // you should validate the level of the request. I leave it here since there is a bug in version 3.0.2
  45. // from version 4 on this should be done
  46. //if (request.Level != DicomQueryRetrieveLevel.Worklist)
  47. //{
  48. // yield return new DicomCFindResponse(request, DicomStatus.QueryRetrieveUnableToProcess);
  49. //}
  50. //else
  51. //{
  52. foreach (DicomDataset result in WorklistHandler.FilterWorklistItems(request.Dataset, WorklistServer.CurrentWorklistItems))
  53. {
  54. yield return new DicomCFindResponse(request, DicomStatus.Pending) { Dataset = result };
  55. }
  56. yield return new DicomCFindResponse(request, DicomStatus.Success);
  57. //}
  58. }
  59. public void OnConnectionClosed(Exception exception)
  60. {
  61. Clean();
  62. }
  63. public void OnReceiveAbort(DicomAbortSource source, DicomAbortReason reason)
  64. {
  65. //log the abort reason
  66. Logger.Error($"Received abort from {source}, reason is {reason}");
  67. }
  68. public Task OnReceiveAssociationReleaseRequestAsync()
  69. {
  70. Clean();
  71. return SendAssociationReleaseResponseAsync();
  72. }
  73. public Task OnReceiveAssociationRequestAsync(DicomAssociation association)
  74. {
  75. Logger.Info($"Received association request from AE: {association.CallingAE} with IP: {association.RemoteHost} ");
  76. //if (WorklistServer.AETitle != association.CalledAE)
  77. //{
  78. // Logger.Error($"Association with {association.CallingAE} rejected since called aet {association.CalledAE} is unknown");
  79. // return SendAssociationRejectAsync(DicomRejectResult.Permanent, DicomRejectSource.ServiceUser, DicomRejectReason.CalledAENotRecognized);
  80. //}
  81. foreach (var pc in association.PresentationContexts)
  82. {
  83. if (pc.AbstractSyntax == DicomUID.Verification
  84. || pc.AbstractSyntax == DicomUID.ModalityWorklistInformationModelFIND
  85. || pc.AbstractSyntax == DicomUID.ModalityPerformedProcedureStepSOPClass
  86. || pc.AbstractSyntax == DicomUID.ModalityPerformedProcedureStepNotificationSOPClass
  87. || pc.AbstractSyntax == DicomUID.ModalityPerformedProcedureStepNotificationSOPClass)
  88. {
  89. pc.AcceptTransferSyntaxes(AcceptedTransferSyntaxes);
  90. }
  91. else
  92. {
  93. Logger.Warn($"Requested abstract syntax {pc.AbstractSyntax} from {association.CallingAE} not supported");
  94. pc.SetResult(DicomPresentationContextResult.RejectAbstractSyntaxNotSupported);
  95. }
  96. }
  97. Logger.Info($"Accepted association request from {association.CallingAE}");
  98. return SendAssociationAcceptAsync(association);
  99. }
  100. public void Clean()
  101. {
  102. // cleanup, like cancel outstanding move- or get-jobs
  103. }
  104. public DicomNCreateResponse OnNCreateRequest(DicomNCreateRequest request)
  105. {
  106. if (request.SOPClassUID != DicomUID.ModalityPerformedProcedureStepSOPClass)
  107. {
  108. return new DicomNCreateResponse(request, DicomStatus.SOPClassNotSupported);
  109. }
  110. // on N-Create the UID is stored in AffectedSopInstanceUID, in N-Set the UID is stored in RequestedSopInstanceUID
  111. var affectedSopInstanceUID = request.Command.GetSingleValue<string>(DicomTag.AffectedSOPInstanceUID);
  112. Logger.Log(LogLevel.Info, $"reeiving N-Create with SOPUID {affectedSopInstanceUID}");
  113. // get the procedureStepIds from the request
  114. var procedureStepId = request.Dataset
  115. .GetSequence(DicomTag.ScheduledStepAttributesSequence)
  116. .First()
  117. .GetSingleValue<string>(DicomTag.ScheduledProcedureStepID);
  118. var ok = MppsSource.SetInProgress(affectedSopInstanceUID, procedureStepId);
  119. return new DicomNCreateResponse(request, ok ? DicomStatus.Success : DicomStatus.ProcessingFailure);
  120. }
  121. public DicomNSetResponse OnNSetRequest(DicomNSetRequest request)
  122. {
  123. if (request.SOPClassUID != DicomUID.ModalityPerformedProcedureStepSOPClass)
  124. {
  125. return new DicomNSetResponse(request, DicomStatus.SOPClassNotSupported);
  126. }
  127. // on N-Create the UID is stored in AffectedSopInstanceUID, in N-Set the UID is stored in RequestedSopInstanceUID
  128. var requestedSopInstanceUID = request.Command.GetSingleValue<string>(DicomTag.RequestedSOPInstanceUID);
  129. Logger.Log(LogLevel.Info, $"receiving N-Set with SOPUID {requestedSopInstanceUID}");
  130. var status = request.Dataset.GetSingleValue<string>(DicomTag.PerformedProcedureStepStatus);
  131. if (status == "COMPLETED")
  132. {
  133. // most vendors send some informations with the mpps-completed message.
  134. // this information should be stored into the datbase
  135. var doseDescription = request.Dataset.GetSingleValueOrDefault(DicomTag.CommentsOnRadiationDose, string.Empty);
  136. var listOfInstanceUIDs = new List<string>();
  137. foreach (var seriesDataset in request.Dataset.GetSequence(DicomTag.PerformedSeriesSequence))
  138. {
  139. // you can read here some information about the series that the modalidy created
  140. //seriesDataset.Get(DicomTag.SeriesDescription, string.Empty);
  141. //seriesDataset.Get(DicomTag.PerformingPhysicianName, string.Empty);
  142. //seriesDataset.Get(DicomTag.ProtocolName, string.Empty);
  143. foreach (var instanceDataset in seriesDataset.GetSequence(DicomTag.ReferencedImageSequence))
  144. {
  145. // here you can read the SOPClassUID and SOPInstanceUID
  146. var instanceUID = instanceDataset.GetSingleValueOrDefault(DicomTag.ReferencedSOPInstanceUID, string.Empty);
  147. if (!string.IsNullOrEmpty(instanceUID)) listOfInstanceUIDs.Add(instanceUID);
  148. }
  149. }
  150. var ok = MppsSource.SetCompleted(requestedSopInstanceUID, doseDescription, listOfInstanceUIDs);
  151. return new DicomNSetResponse(request, ok ? DicomStatus.Success : DicomStatus.ProcessingFailure);
  152. }
  153. else if (status == "DISCONTINUED")
  154. {
  155. // some vendors send a reason code or description with the mpps-discontinued message
  156. // var reason = request.Dataset.Get(DicomTag.PerformedProcedureStepDiscontinuationReasonCodeSequence);
  157. var ok = MppsSource.SetDiscontinued(requestedSopInstanceUID, string.Empty);
  158. return new DicomNSetResponse(request, ok ? DicomStatus.Success : DicomStatus.ProcessingFailure);
  159. }
  160. else
  161. {
  162. return new DicomNSetResponse(request, DicomStatus.InvalidAttributeValue);
  163. }
  164. }
  165. #region not supported methods but that are required because of the interface
  166. public DicomNDeleteResponse OnNDeleteRequest(DicomNDeleteRequest request)
  167. {
  168. Logger.Log(LogLevel.Info, "receiving N-Delete, not supported");
  169. return new DicomNDeleteResponse(request, DicomStatus.UnrecognizedOperation);
  170. }
  171. public DicomNEventReportResponse OnNEventReportRequest(DicomNEventReportRequest request)
  172. {
  173. Logger.Log(LogLevel.Info, "receiving N-Event, not supported");
  174. return new DicomNEventReportResponse(request, DicomStatus.UnrecognizedOperation);
  175. }
  176. public DicomNGetResponse OnNGetRequest(DicomNGetRequest request)
  177. {
  178. Logger.Log(LogLevel.Info, "receiving N-Get, not supported");
  179. return new DicomNGetResponse(request, DicomStatus.UnrecognizedOperation);
  180. }
  181. public DicomNActionResponse OnNActionRequest(DicomNActionRequest request)
  182. {
  183. Logger.Log(LogLevel.Info, "receiving N-Action, not supported");
  184. return new DicomNActionResponse(request, DicomStatus.UnrecognizedOperation);
  185. }
  186. #endregion
  187. }
  188. }