using Dicom; using Dicom.Imaging.Codec; using EasyCaching.Core; using IRaCIS.Core.Application.Contracts.Dicom; using IRaCIS.Core.Domain.Share; using System.Text; using IRaCIS.Core.Application.Contracts; using IRaCIS.Core.Application.Helper; using Microsoft.AspNetCore.Hosting; using IRaCIS.Core.Infrastructure; namespace IRaCIS.Core.Application.Services { public class DicomArchiveService : IDicomArchiveService { private readonly IRepository _studyRepository; private readonly IRepository _seriesRepository; private readonly IRepository _instanceRepository; private readonly IRepository _dictionaryRepository; private readonly IEasyCachingProvider _provider; private readonly IWebHostEnvironment _hostEnvironment; private static object lockCodeGenerate = new object(); private List _instanceIdList = new List(); public DicomArchiveService(IRepository studyRepository, IRepository seriesRepository, IRepository instanceRepository, IWebHostEnvironment hostEnvironment, IRepository dictionaryRepository, IEasyCachingProvider provider) { _hostEnvironment = hostEnvironment; _studyRepository = studyRepository; _seriesRepository = seriesRepository; _instanceRepository = instanceRepository; _dictionaryRepository = dictionaryRepository; _provider = provider; } public async Task DicomDBDataSaveChange() { var success = await _studyRepository.SaveChangesAsync(); return success; } public async Task<(Guid StudyId, string StudyCode)> ArchiveDicomStreamAsync(Stream dicomStream, DicomTrialSiteSubjectInfo addtionalInfo, List seriesInstanceUidList, List instanceUidList) { DicomFile dicomFile = await DicomFile.OpenAsync(dicomStream, Encoding.Default); DicomDataset dataset = dicomFile.Dataset; //如果数据库存在该instance 记录 那么就不处理 string sopInstanceUid = dataset.GetString(DicomTag.SOPInstanceUID); string studyInstanceUid = dataset.GetString(DicomTag.StudyInstanceUID); if (instanceUidList.Any(t => t == sopInstanceUid)) { return (IdentifierHelper.CreateGuid(studyInstanceUid, addtionalInfo.TrialId.ToString()), string.Empty); } var anonymize_AddFixedFiledList = _provider.Get>(StaticData.Anonymize.Anonymize_AddFixedFiled).Value ?? new List(); var anonymize_AddIRCInfoFiledList = _provider.Get>(StaticData.Anonymize.Anonymize_AddIRCInfoFiled).Value ?? new List(); var anonymize_FixedFieldList = _provider.Get>(StaticData.Anonymize.Anonymize_FixedField).Value ?? new List(); var anonymize_IRCInfoFieldList = _provider.Get>(StaticData.Anonymize.Anonymize_IRCInfoField).Value ?? new List(); if (anonymize_AddFixedFiledList.Union(anonymize_AddIRCInfoFiledList).Union(anonymize_FixedFieldList).Union(anonymize_IRCInfoFieldList).Count() == 0) { throw new BusinessValidationFailedException("未取到缓存匿名化配置数据,上传停止,请联系开发人员核实"); } foreach (var item in anonymize_AddFixedFiledList.Union(anonymize_FixedFieldList)) { var dicomTag = new DicomTag(Convert.ToUInt16(item.Group, 16), Convert.ToUInt16(item.Element, 16)); dataset.AddOrUpdate(dicomTag, item.ReplaceValue); } foreach (var item in anonymize_AddIRCInfoFiledList.Union(anonymize_IRCInfoFieldList)) { var dicomTag = new DicomTag(Convert.ToUInt16(item.Group, 16), Convert.ToUInt16(item.Element, 16)); if (dicomTag == DicomTag.ClinicalTrialProtocolID) { dataset.AddOrUpdate(dicomTag, addtionalInfo.TrialCode); } if (dicomTag == DicomTag.ClinicalTrialSiteID) { dataset.AddOrUpdate(dicomTag, addtionalInfo.TrialSiteCode); } if (dicomTag == DicomTag.ClinicalTrialSubjectID) { dataset.AddOrUpdate(dicomTag, addtionalInfo.SubjectCode); } if (dicomTag == DicomTag.ClinicalTrialTimePointID) { dataset.AddOrUpdate(dicomTag, addtionalInfo.VisitNum.ToString()); } if (dicomTag == DicomTag.PatientID) { dataset.AddOrUpdate(dicomTag, addtionalInfo.TrialCode + "_" + addtionalInfo.SubjectCode); } } DicomStudy dicomStudy = CreateDicomStudy(dataset, addtionalInfo, out bool isStudyNeedAdd); DicomSeries dicomSeries = CreateDicomSeries(dataset, dicomStudy, out bool isSeriesNeedAdd); DicomInstance dicomInstance = CreateDicomInstance(dataset, dicomStudy, dicomSeries,out bool isInstanceNeedAdd); dicomSeries.DicomStudy = dicomStudy; var createtime = DateTime.Now; if (isStudyNeedAdd) { // 添加检查 await _studyRepository.AddAsync(dicomStudy); } if (isSeriesNeedAdd) { dicomSeries.DicomStudy = dicomStudy; // 添加序列 await _seriesRepository.AddAsync(dicomSeries); } var (physicalPath, relativePath) = FileStoreHelper.GetDicomInstanceFilePath(_hostEnvironment, dicomStudy.TrialId, dicomStudy.SiteId, dicomStudy.SubjectId, dicomStudy.SubjectVisitId, dicomStudy.Id, dicomInstance.Id); dicomInstance.Path = relativePath; if (isInstanceNeedAdd) { await _instanceRepository.AddAsync(dicomInstance); } var samplesPerPixel = dataset.GetSingleValueOrDefault(DicomTag.SamplesPerPixel, string.Empty); var photometricInterpretation = dataset.GetSingleValueOrDefault(DicomTag.PhotometricInterpretation, string.Empty); if (samplesPerPixel == "1" && (photometricInterpretation.ToUpper() == "MONOCHROME2" || photometricInterpretation.ToUpper() == "MONOCHROME1"))//MONOCHROME2 { if (dataset.InternalTransferSyntax.IsEncapsulated) { //正常保存 不做处理 await dicomFile.SaveAsync(physicalPath); } else { //JPEGLSLossless 保存 await dicomFile.Clone(DicomTransferSyntax.JPEGLSLossless).SaveAsync(physicalPath); } } else { if (dataset.InternalTransferSyntax.IsEncapsulated) { //正常保存 不做处理 await dicomFile.SaveAsync(physicalPath); } else { //RLELossless 保存 await dicomFile.Clone(DicomTransferSyntax.RLELossless).SaveAsync(physicalPath); //RLELossless } } return (dicomInstance.StudyId, dicomStudy.StudyCode); } private DicomStudy CreateDicomStudy(DicomDataset dataset, DicomTrialSiteSubjectInfo addtionalInfo, out bool isStudyNeedAdd) { string studyInstanceUid = dataset.GetString(DicomTag.StudyInstanceUID); Guid studyId = IdentifierHelper.CreateGuid(studyInstanceUid, addtionalInfo.TrialId.ToString()); // 每个线程都查询数据库最大的,和缓存中最大的,取最大值为基数生成Code //var id = Thread.CurrentThread.ManagedThreadId.ToString("00"); //虽然每个文件都会进来,但是只要查询过,就会跟踪,不会再次查询数据库 这里线程并发会有问题,得加锁,不然生成Code 出错 DicomStudy dicomStudy = _studyRepository.ImageFind(studyId, typeof(DicomStudy)); if (dicomStudy != null) { isStudyNeedAdd = false; return dicomStudy; } //_logger.LogWarning($"Thread {id} ,studyUid{studyInstanceUid}, 生成StudyId:{studyId}"); isStudyNeedAdd = true; //dataset.GetSingleValue(DicomTag.StudyDate) + dataset.GetSingleValue(DicomTag.StudyTime) var modality = dataset.GetSingleValueOrDefault(DicomTag.Modality, string.Empty); var dicModalityList = _dictionaryRepository.Where(t => t.Code == "Modality").SelectMany(t => t.ChildList.Select(c => c.Value)).ToList(); var modalityForEdit = dicModalityList.Contains(modality) ? modality : String.Empty; if (modality == "MR") { modalityForEdit = "MRI"; } if (modality == "PT") { modalityForEdit = "PET"; } if(modality== "PT、CT") { modalityForEdit = "PET-CT"; } dicomStudy = new DicomStudy { Id = studyId, StudyInstanceUid = studyInstanceUid, /* StudyTime = dataset.GetSingleValueOrDefault(DicomTag.StudyDate, DateTime.Now).Add(dataset.GetSingleValueOrDefault(DicomTag.StudyTime, DateTime.Now).TimeOfDay),*///dataset.GetDateTime(DicomTag.StudyDate, DicomTag.StudyTime), StudyTime = dataset.GetSingleValueOrDefault(DicomTag.StudyDate, string.Empty) == string.Empty ? null : dataset.GetSingleValue(DicomTag.StudyDate).Add(dataset.GetSingleValueOrDefault(DicomTag.StudyTime, string.Empty) == string.Empty ? TimeSpan.Zero : dataset.GetSingleValue(DicomTag.StudyTime).TimeOfDay), Modalities = modality, ModalityForEdit = modalityForEdit, Description = dataset.GetSingleValueOrDefault(DicomTag.StudyDescription, string.Empty), InstitutionName = dataset.GetSingleValueOrDefault(DicomTag.InstitutionName, string.Empty), PatientId = dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty), PatientName = dataset.GetSingleValueOrDefault(DicomTag.PatientName, string.Empty), PatientAge = dataset.GetSingleValueOrDefault(DicomTag.PatientAge, string.Empty), PatientSex = dataset.GetSingleValueOrDefault(DicomTag.PatientSex, string.Empty), BodyPartExamined = dataset.GetSingleValueOrDefault(DicomTag.BodyPartExamined, string.Empty), StudyId = dataset.GetSingleValueOrDefault(DicomTag.StudyID, string.Empty), AccessionNumber = dataset.GetSingleValueOrDefault(DicomTag.AccessionNumber, string.Empty), //需要特殊处理 PatientBirthDate = dataset.GetSingleValueOrDefault(DicomTag.PatientBirthDate, string.Empty), AcquisitionTime = dataset.GetSingleValueOrDefault(DicomTag.AcquisitionTime, string.Empty), AcquisitionNumber = dataset.GetSingleValueOrDefault(DicomTag.AcquisitionNumber, string.Empty), TriggerTime = dataset.GetSingleValueOrDefault(DicomTag.TriggerTime, string.Empty), SiteId = addtionalInfo.SiteId, TrialId = addtionalInfo.TrialId, SubjectId = addtionalInfo.SubjectId, SubjectVisitId = addtionalInfo.SubjectVisitId, //IsDoubleReview = addtionalInfo.IsDoubleReview, SeriesCount = 0, InstanceCount = 0 }; if (dicomStudy.PatientBirthDate.Length == 8) { dicomStudy.PatientBirthDate = $"{dicomStudy.PatientBirthDate[0]}{dicomStudy.PatientBirthDate[1]}{dicomStudy.PatientBirthDate[2]}{dicomStudy.PatientBirthDate[3]}-{dicomStudy.PatientBirthDate[4]}{dicomStudy.PatientBirthDate[5]}-{dicomStudy.PatientBirthDate[6]}{dicomStudy.PatientBirthDate[7]}"; } lock (lockCodeGenerate) { //查询数据库获取最大的Code 没有记录则为0 var dbStudyCodeIntMax = _studyRepository.Where(s => s.TrialId == addtionalInfo.TrialId).Select(t => t.Code).DefaultIfEmpty().Max(); //获取缓存中的值 并发的时候,需要记录,已被占用的值 这样其他线程在此占用的最大的值上递增 var cacheMaxCodeInt = _provider.Get($"{addtionalInfo.TrialId}_{StaticData.CacheKey.StudyMaxCode}").Value; int currentNextCodeInt = cacheMaxCodeInt > dbStudyCodeIntMax ? cacheMaxCodeInt + 1 : dbStudyCodeIntMax + 1; dicomStudy.Code = currentNextCodeInt; dicomStudy.StudyCode = AppSettings.GetCodeStr(currentNextCodeInt, nameof(DicomStudy)); _provider.Set($"{addtionalInfo.TrialId}_{StaticData.CacheKey.StudyMaxCode}", dicomStudy.Code, TimeSpan.FromMinutes(30)); } #region Setting Code old //var studyCode = _studyRepository.Where(s => s.TrialId == addtionalInfo.TrialId).Select(t => t.StudyCode).OrderByDescending(c => c).FirstOrDefault(); //var cacheMaxCode = _provider.Get($"{addtionalInfo.TrialId }_{ StaticData.StudyMaxCode}").Value; //if (studyCode == null && string.IsNullOrEmpty(cacheMaxCode)) //{ // dicomStudy.StudyCode = "ST" + 1.ToString().PadLeft(5, '0'); // _logger.LogWarning($"Thread {id} DB:{studyCode} 生成{ dicomStudy.StudyCode}"); //} //else //{ // int dbNum = 0; // int cacheNum = 0; // if (studyCode != null) // { // int.TryParse(studyCode.Substring(studyCode.Length - 5, 5), out dbNum); // } // if (!string.IsNullOrEmpty(cacheMaxCode)) // { // int.TryParse(cacheMaxCode.Substring(cacheMaxCode.Length - 5, 5), out cacheNum); // } // dbNum = cacheNum > dbNum ? cacheNum : dbNum; // dicomStudy.StudyCode = "ST" + (++dbNum).ToString().PadLeft(5, '0'); // _logger.LogWarning($" Thread {id} DB:{studyCode} cache:{cacheNum} 生成{ dicomStudy.StudyCode}"); //} //_provider.Set($"{addtionalInfo.TrialId }_{ StaticData.StudyMaxCode}", dicomStudy.StudyCode, TimeSpan.FromMinutes(30)); #endregion return dicomStudy; } private DicomSeries CreateDicomSeries(DicomDataset dataset, DicomStudy dicomStudy, out bool isSeriesNeedAdd) { string seriesInstanceUid = dataset.GetString(DicomTag.SeriesInstanceUID); Guid seriesId = IdentifierHelper.CreateGuid(dicomStudy.StudyInstanceUid, seriesInstanceUid, dicomStudy.TrialId.ToString()); //有几个序列会查询几次 DicomSeries dicomSeries = _seriesRepository.ImageFind(seriesId, typeof(DicomSeries)); if (dicomSeries != null) { isSeriesNeedAdd = false; return dicomSeries; } else { //var id = Thread.CurrentThread.ManagedThreadId.ToString("00"); isSeriesNeedAdd = true; dicomSeries = new DicomSeries { Id = seriesId, StudyId = dicomStudy.Id, StudyInstanceUid = dicomStudy.StudyInstanceUid, SeriesInstanceUid = seriesInstanceUid, SeriesNumber = dataset.GetSingleValueOrDefault(DicomTag.SeriesNumber, string.Empty), //SeriesTime = dataset.GetSingleValueOrDefault(DicomTag.SeriesDate, DateTime.Now).Add(dataset.GetSingleValueOrDefault(DicomTag.SeriesTime, DateTime.Now).TimeOfDay), //SeriesTime = DateTime.TryParse(dataset.GetSingleValue(DicomTag.SeriesDate) + dataset.GetSingleValue(DicomTag.SeriesTime), out DateTime dt) ? dt : null, SeriesTime = dataset.GetSingleValueOrDefault(DicomTag.SeriesDate, string.Empty) == string.Empty ? null : dataset.GetSingleValue(DicomTag.SeriesDate).Add(dataset.GetSingleValueOrDefault(DicomTag.SeriesTime, string.Empty) == string.Empty ? TimeSpan.Zero : dataset.GetSingleValue(DicomTag.SeriesTime).TimeOfDay), Modality = dataset.GetSingleValueOrDefault(DicomTag.Modality, string.Empty), Description = dataset.GetSingleValueOrDefault(DicomTag.SeriesDescription, string.Empty), SliceThickness = dataset.GetSingleValueOrDefault(DicomTag.SliceThickness, string.Empty), ImagePositionPatient = dataset.GetSingleValueOrDefault(DicomTag.ImagePositionPatient, string.Empty), ImageOrientationPatient = dataset.GetSingleValueOrDefault(DicomTag.ImageOrientationPatient, string.Empty), BodyPartExamined = dataset.GetSingleValueOrDefault(DicomTag.BodyPartExamined, string.Empty), SequenceName = dataset.GetSingleValueOrDefault(DicomTag.SequenceName, string.Empty), ProtocolName = dataset.GetSingleValueOrDefault(DicomTag.ProtocolName, string.Empty), ImagerPixelSpacing = dataset.GetSingleValueOrDefault(DicomTag.ImagerPixelSpacing, string.Empty), AcquisitionTime = dataset.GetSingleValueOrDefault(DicomTag.AcquisitionTime, string.Empty), AcquisitionNumber = dataset.GetSingleValueOrDefault(DicomTag.AcquisitionNumber, string.Empty), TriggerTime = dataset.GetSingleValueOrDefault(DicomTag.TriggerTime, string.Empty), SiteId = dicomStudy.SiteId, TrialId = dicomStudy.TrialId, SubjectId = dicomStudy.SubjectId, SubjectVisitId = dicomStudy.SubjectVisitId, InstanceCount = 0 }; ++dicomStudy.SeriesCount; //_logger.LogWarning($"线程:{id},sericeId:{seriesId},count:{SeriesDic.Keys.Count}"); return dicomSeries; } } private DicomInstance CreateDicomInstance(DicomDataset dataset, DicomStudy dicomStudy, DicomSeries dicomSeries, out bool isInstanceNeedAdd) { string sopInstanceUid = dataset.GetString(DicomTag.SOPInstanceUID); Guid instanceId = IdentifierHelper.CreateGuid(dicomStudy.StudyInstanceUid, dicomSeries.SeriesInstanceUid, sopInstanceUid, dicomStudy.TrialId.ToString()); DicomInstance dicomInstance = new DicomInstance { Id = instanceId, StudyId = dicomStudy.Id, SeriesId = dicomSeries.Id, SiteId = dicomStudy.SiteId, TrialId = dicomStudy.TrialId, SubjectId = dicomStudy.SubjectId, SubjectVisitId = dicomStudy.SubjectVisitId, StudyInstanceUid = dicomStudy.StudyInstanceUid, SeriesInstanceUid = dicomSeries.SeriesInstanceUid, SopInstanceUid = sopInstanceUid, InstanceNumber = dataset.GetSingleValueOrDefault(DicomTag.InstanceNumber, 1), InstanceTime = dataset.GetSingleValueOrDefault(DicomTag.ContentDate, string.Empty) == string.Empty ? null : dataset.GetSingleValue(DicomTag.ContentDate).Add(dataset.GetSingleValueOrDefault(DicomTag.ContentTime, string.Empty) == string.Empty ? TimeSpan.Zero : dataset.GetSingleValue(DicomTag.ContentTime).TimeOfDay), //InstanceTime = DateTime.TryParse(dataset.GetSingleValue(DicomTag.ContentDate) + dataset.GetSingleValue(DicomTag.ContentTime), out DateTime dt) ? dt : null, //InstanceTime = dataset.GetSingleValueOrDefault(DicomTag.ContentDate,(DateTime?)null)?.Add(dataset.GetSingleValueOrDefault(DicomTag.ContentTime, TimeSpan.Zero)), //dataset.GetSingleValueOrDefault(DicomTag.ContentDate,DateTime.Now);//, DicomTag.ContentTime) CPIStatus = false, ImageRows = dataset.GetSingleValueOrDefault(DicomTag.Rows, 0), ImageColumns = dataset.GetSingleValueOrDefault(DicomTag.Columns, 0), SliceLocation = dataset.GetSingleValueOrDefault(DicomTag.SliceLocation, 0), SliceThickness = dataset.GetSingleValueOrDefault(DicomTag.SliceThickness, string.Empty), NumberOfFrames = dataset.GetSingleValueOrDefault(DicomTag.NumberOfFrames, 0), PixelSpacing = dataset.GetSingleValueOrDefault(DicomTag.PixelSpacing, string.Empty), ImagerPixelSpacing = dataset.GetSingleValueOrDefault(DicomTag.ImagerPixelSpacing, string.Empty), FrameOfReferenceUID = dataset.GetSingleValueOrDefault(DicomTag.FrameOfReferenceUID, string.Empty), WindowCenter = dataset.GetSingleValueOrDefault(DicomTag.WindowCenter, string.Empty), WindowWidth = dataset.GetSingleValueOrDefault(DicomTag.WindowWidth, string.Empty), }; isInstanceNeedAdd = false; if (!_instanceIdList.Contains(instanceId)) { ++dicomStudy.InstanceCount; ++dicomSeries.InstanceCount; isInstanceNeedAdd = true; _instanceIdList.Add(instanceId); } return dicomInstance; } } }