using FellowOakDicom.Network; using FellowOakDicom; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using IRaCIS.Core.SCP.Service; using IRaCIS.Core.Domain.Models; using IRaCIS.Core.Infra.EFCore; using Medallion.Threading; using IRaCIS.Core.Domain.Share; using Serilog; using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal; using Microsoft.Extensions.Options; using System.Data; using FellowOakDicom.Imaging; using SharpCompress.Common; using SixLabors.ImageSharp.Formats.Jpeg; using IRaCIS.Core.Infrastructure; namespace IRaCIS.Core.SCP.Service { public class DicomSCPServiceOption { public List CalledAEList { get; set; } public string ServerPort { get; set; } } public class CStoreSCPService : DicomService, IDicomServiceProvider, IDicomCStoreProvider, IDicomCEchoProvider { private IServiceProvider _serviceProvider { get; set; } private List _SCPStudyIdList { get; set; } = new List(); private SCPImageUpload _upload { get; set; } private Guid _trialId { get; set; } private Guid _trialSiteId { get; set; } private static readonly DicomTransferSyntax[] _acceptedTransferSyntaxes = new DicomTransferSyntax[] { DicomTransferSyntax.ExplicitVRLittleEndian, DicomTransferSyntax.ExplicitVRBigEndian, DicomTransferSyntax.ImplicitVRLittleEndian }; private static readonly DicomTransferSyntax[] _acceptedImageTransferSyntaxes = new DicomTransferSyntax[] { // Lossless DicomTransferSyntax.JPEGLSLossless, //1.2.840.10008.1.2.4.80 DicomTransferSyntax.JPEG2000Lossless, //1.2.840.10008.1.2.4.90 DicomTransferSyntax.JPEGProcess14SV1, //1.2.840.10008.1.2.4.70 DicomTransferSyntax.JPEGProcess14, //1.2.840.10008.1.2.4.57 JPEG Lossless, Non-Hierarchical (Process 14) DicomTransferSyntax.RLELossless, //1.2.840.10008.1.2.5 // Lossy DicomTransferSyntax.JPEGLSNearLossless,//1.2.840.10008.1.2.4.81" DicomTransferSyntax.JPEG2000Lossy, //1.2.840.10008.1.2.4.91 DicomTransferSyntax.JPEGProcess1, //1.2.840.10008.1.2.4.50 DicomTransferSyntax.JPEGProcess2_4, //1.2.840.10008.1.2.4.51 // Uncompressed DicomTransferSyntax.ExplicitVRLittleEndian, //1.2.840.10008.1.2.1 DicomTransferSyntax.ExplicitVRBigEndian, //1.2.840.10008.1.2.2 DicomTransferSyntax.ImplicitVRLittleEndian //1.2.840.10008.1.2 }; public CStoreSCPService(INetworkStream stream, Encoding fallbackEncoding, Microsoft.Extensions.Logging.ILogger log, DicomServiceDependencies dependencies, IServiceProvider injectServiceProvider) : base(stream, fallbackEncoding, log, dependencies) { _serviceProvider = injectServiceProvider.CreateScope().ServiceProvider; } public Task OnReceiveAssociationRequestAsync(DicomAssociation association) { _upload = new SCPImageUpload() { StartTime = DateTime.Now, CallingAE = association.CallingAE, CalledAE = association.CalledAE, CallingAEIP = association.RemoteHost }; Log.Logger.Warning($"接收到来自{association.CallingAE}的连接"); //_serviceProvider = (IServiceProvider)this.UserState; var _trialDicomAERepository = _serviceProvider.GetService>(); var trialDicomAEList = _trialDicomAERepository.Select(t => new { t.CalledAE, t.TrialId }).ToList(); var trialCalledAEList = trialDicomAEList.Select(t => t.CalledAE).ToList(); Log.Logger.Information("当前系统配置:", string.Join('|', trialDicomAEList)); var findCalledAE = trialDicomAEList.Where(t => t.CalledAE == association.CalledAE).FirstOrDefault(); var isCanReceiveIamge = false; if (findCalledAE != null) { _trialId = findCalledAE.TrialId; var _trialSiteDicomAERepository = _serviceProvider.GetService>(); var findTrialSiteAE = _trialSiteDicomAERepository.Where(t => t.CallingAE == association.CallingAE && t.TrialId==_trialId).FirstOrDefault(); if (findTrialSiteAE != null) { _trialSiteId = findTrialSiteAE.TrialSiteId; isCanReceiveIamge = true; } } if (association.CallingAE == "test-callingAE") { isCanReceiveIamge = true; } if (!trialCalledAEList.Contains(association.CalledAE) || isCanReceiveIamge == false) { Log.Logger.Warning($"拒绝CallingAE:{association.CallingAE} CalledAE:{association.CalledAE}的连接"); return SendAssociationRejectAsync( DicomRejectResult.Permanent, DicomRejectSource.ServiceUser, DicomRejectReason.CalledAENotRecognized); } foreach (var pc in association.PresentationContexts) { if (pc.AbstractSyntax == DicomUID.Verification) { pc.AcceptTransferSyntaxes(_acceptedTransferSyntaxes); } else if (pc.AbstractSyntax.StorageCategory != DicomStorageCategory.None) { pc.AcceptTransferSyntaxes(_acceptedImageTransferSyntaxes); } } return SendAssociationAcceptAsync(association); } public async Task OnReceiveAssociationReleaseRequestAsync() { await DataMaintenanceAsaync(); //记录监控 var _SCPImageUploadRepository = _serviceProvider.GetService>(); _upload.EndTime = DateTime.Now; _upload.StudyCount = _SCPStudyIdList.Count; _upload.TrialId = _trialId; _upload.TrialSiteId = _trialSiteId; await _SCPImageUploadRepository.AddAsync(_upload, true); var _studyRepository = _serviceProvider.GetService>(); //将检查设置为传输结束 await _studyRepository.BatchUpdateNoTrackingAsync(t => _SCPStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true }); await _studyRepository.SaveChangesAndClearAllTrackingAsync(); await SendAssociationReleaseResponseAsync(); } private async Task DataMaintenanceAsaync() { Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE}传输结束:开始维护数据,处理检查Modality"); //处理检查Modality var _dictionaryRepository = _serviceProvider.GetService>(); var _seriesRepository = _serviceProvider.GetService>(); var _studyRepository = _serviceProvider.GetService>(); var dicModalityList = _dictionaryRepository.Where(t => t.Code == "Modality").SelectMany(t => t.ChildList.Select(c => c.Value)).ToList(); var seriesModalityList = _seriesRepository.Where(t => _SCPStudyIdList.Contains(t.StudyId)).Select(t => new { SCPStudyId = t.StudyId, t.Modality }).ToList(); foreach (var g in seriesModalityList.GroupBy(t => t.SCPStudyId)) { var modality = string.Join('、', g.Select(t => t.Modality).Distinct().ToList()); //特殊逻辑 var modalityForEdit = dicModalityList.Contains(modality) ? modality : String.Empty; if (modality == "MR") { modalityForEdit = "MRI"; } if (modality == "PT") { modalityForEdit = "PET"; } if (modality == "PT、CT" || modality == "CT、PT") { modalityForEdit = "PET-CT"; } await _studyRepository.BatchUpdateNoTrackingAsync(t => t.Id == g.Key, u => new SCPStudy() { Modalities = modality, ModalityForEdit = modalityForEdit }); } Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE}维护数据结束"); } public void OnReceiveAbort(DicomAbortSource source, DicomAbortReason reason) { Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE}接收中断,中断原因:{source.ToString() + reason.ToString()}"); /* nothing to do here */ } public async void OnConnectionClosed(Exception exception) { /* nothing to do here */ //奇怪的bug 上传的时候,用王捷修改的影像,会关闭,重新连接,导致检查id 丢失,然后状态不一致 if (exception == null) { //var _studyRepository = _serviceProvider.GetService>(); ////将检查设置为传输结束 //await _studyRepository.BatchUpdateNoTrackingAsync(t => _SCPStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true }); //await _studyRepository.SaveChangesAndClearAllTrackingAsync(); } Log.Logger.Warning($"连接关闭 {exception?.Message} {exception?.InnerException?.Message}"); } public async Task OnCStoreRequestAsync(DicomCStoreRequest request) { string studyInstanceUid = request.Dataset.GetString(DicomTag.StudyInstanceUID); string seriesInstanceUid = request.Dataset.GetString(DicomTag.SeriesInstanceUID); string sopInstanceUid = request.Dataset.GetString(DicomTag.SOPInstanceUID); //Guid studyId = IdentifierHelper.CreateGuid(studyInstanceUid, trialId.ToString()); Guid seriesId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, _trialId.ToString()); Guid instanceId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, sopInstanceUid, _trialId.ToString()); var ossService = _serviceProvider.GetService(); var dicomArchiveService = _serviceProvider.GetService(); var _seriesRepository = _serviceProvider.GetService>(); var _distributedLockProvider = _serviceProvider.GetService(); var storeRelativePath = string.Empty; var ossFolderPath = $"{_trialId}/Image/PACS/{_trialSiteId}/{studyInstanceUid}"; long fileSize = 0; try { using (MemoryStream ms = new MemoryStream()) { await request.File.SaveAsync(ms); //irc 从路径最后一截取Guid storeRelativePath = await ossService.UploadToOSSAsync(ms, ossFolderPath, instanceId.ToString(), false); fileSize = ms.Length; } Log.Logger.Information($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} {request.SOPInstanceUID} 上传完成 "); } catch (Exception ec) { Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} 上传异常 {ec.Message}"); } var @lock = _distributedLockProvider.CreateLock($"{studyInstanceUid}"); using (await @lock.AcquireAsync()) { try { var scpStudyId = await dicomArchiveService.ArchiveDicomFileAsync(request.Dataset, _trialId, _trialSiteId, storeRelativePath, Association.CallingAE, Association.CalledAE,fileSize); if (!_SCPStudyIdList.Contains(scpStudyId)) { _SCPStudyIdList.Add(scpStudyId); } var series = await _seriesRepository.FirstOrDefaultAsync(t => t.Id == seriesId); //没有缩略图 if (series != null && string.IsNullOrEmpty(series.ImageResizePath)) { // 生成缩略图 using (var memoryStream = new MemoryStream()) { DicomImage image = new DicomImage(request.Dataset); var sharpimage = image.RenderImage().AsSharpImage(); sharpimage.Save(memoryStream, new JpegEncoder()); // 上传缩略图到 OSS var seriesPath = await ossService.UploadToOSSAsync(memoryStream, ossFolderPath, seriesId.ToString() + ".preview.jpg", false); Console.WriteLine(seriesPath + " Id: " + seriesId); series.ImageResizePath = seriesPath; } } await _seriesRepository.SaveChangesAsync(); } catch (Exception ex) { Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} 传输处理异常:{ex.ToString()}"); } } //监控信息设置 _upload.FileCount++; _upload.FileSize = _upload.FileSize + fileSize; return new DicomCStoreResponse(request, DicomStatus.Success); } public Task OnCStoreRequestExceptionAsync(string tempFileName, Exception e) { // let library handle logging and error response return Task.CompletedTask; } public Task OnCEchoRequestAsync(DicomCEchoRequest request) { return Task.FromResult(new DicomCEchoResponse(request, DicomStatus.Success)); } } }