dicom dir处理返回处理后的路径
continuous-integration/drone/push Build is passing Details

Test_IRC_Net8
hang 2025-08-08 10:58:21 +08:00
parent 44156a2e34
commit eafceeb5d1
7 changed files with 376 additions and 87 deletions

View File

@ -49,8 +49,9 @@ namespace IRaCIS.Core.Application.Helper
public static class DicomDIRHelper
{
public static async Task GenerateStudyDIRAndUploadAsync(List<StudyDIRInfo> list, string ossFolder, IOSSService _oSSService)
public static async Task<Dictionary<string, string>> GenerateStudyDIRAndUploadAsync(List<StudyDIRInfo> list, string ossFolder, IOSSService _oSSService)
{
var dic = new Dictionary<string, string>();
var mappings = new List<string>();
int index = 1;
@ -98,6 +99,8 @@ namespace IRaCIS.Core.Application.Helper
mappings.Add($"{filename} => {item.InstanceId}");
dic.Add(item.InstanceId.ToString(), filename);
dicomDir.AddFile(dicomFile, filename);
}
@ -130,6 +133,45 @@ namespace IRaCIS.Core.Application.Helper
#endregion
return dic;
}
public static StudyDIRInfo ReadDicomDIRInfo(DicomFile dicomFile)
{
var dataset = dicomFile.Dataset;
var info = new StudyDIRInfo
{
PatientId = dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty),
PatientName = dataset.GetSingleValueOrDefault(DicomTag.PatientName, string.Empty),
PatientBirthDate = dataset.GetSingleValueOrDefault(DicomTag.PatientBirthDate, string.Empty),
PatientSex = dataset.GetSingleValueOrDefault(DicomTag.PatientSex, string.Empty),
StudyInstanceUid = dataset.GetSingleValueOrDefault(DicomTag.StudyInstanceUID, string.Empty),
StudyId = dataset.GetSingleValueOrDefault(DicomTag.StudyID, string.Empty),
DicomStudyDate = dataset.GetSingleValueOrDefault(DicomTag.StudyDate, string.Empty),
DicomStudyTime = dataset.GetSingleValueOrDefault(DicomTag.StudyTime, string.Empty),
AccessionNumber = dataset.GetSingleValueOrDefault(DicomTag.AccessionNumber, string.Empty),
StudyDescription = dataset.GetSingleValueOrDefault(DicomTag.StudyDescription, string.Empty),
SeriesInstanceUid = dataset.GetSingleValueOrDefault(DicomTag.SeriesInstanceUID, string.Empty),
Modality = dataset.GetSingleValueOrDefault(DicomTag.Modality, string.Empty),
DicomSeriesDate = dataset.GetSingleValueOrDefault(DicomTag.SeriesDate, string.Empty),
DicomSeriesTime = dataset.GetSingleValueOrDefault(DicomTag.SeriesTime, string.Empty),
SeriesNumber = dataset.GetSingleValueOrDefault(DicomTag.SeriesNumber, 1),
SeriesDescription = dataset.GetSingleValueOrDefault(DicomTag.SeriesDescription, string.Empty),
SopInstanceUid = dataset.GetSingleValueOrDefault(DicomTag.SOPInstanceUID, string.Empty),
SOPClassUID = dataset.GetSingleValueOrDefault(DicomTag.SOPClassUID, string.Empty),
InstanceNumber = dataset.GetSingleValueOrDefault(DicomTag.InstanceNumber, 1),
MediaStorageSOPClassUID = dataset.GetSingleValueOrDefault(DicomTag.MediaStorageSOPClassUID, string.Empty),
MediaStorageSOPInstanceUID = dataset.GetSingleValueOrDefault(DicomTag.MediaStorageSOPInstanceUID, string.Empty),
TransferSytaxUID = dicomFile.FileMetaInfo.GetSingleValueOrDefault(DicomTag.TransferSyntaxUID, string.Empty)
};
return info;
}
}
}

View File

@ -145,6 +145,8 @@ public interface IOSSService
public Task DownLoadFromOSSAsync(string ossRelativePath, string localFilePath);
public Task<Stream> GetStreamFromOSSAsync(string ossRelativePath);
public ObjectStoreServiceOptions ObjectStoreServiceOptions { get; set; }
public Task<string> GetSignedUrl(string ossRelativePath);
@ -474,6 +476,96 @@ public class OSSService : IOSSService
}
public async Task<Stream> GetStreamFromOSSAsync(string ossRelativePath)
{
BackBatchGetToken();
ossRelativePath = ossRelativePath.TrimStart('/');
try
{
if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
{
var aliConfig = ObjectStoreServiceOptions.AliyunOSS;
var _ossClient = new OssClient(
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint,
AliyunOSSTempToken.AccessKeyId,
AliyunOSSTempToken.AccessKeySecret,
AliyunOSSTempToken.SecurityToken
);
var result = _ossClient.GetObject(aliConfig.BucketName, ossRelativePath);
// 将OSS返回的流复制到内存流中并返回
var memoryStream = new MemoryStream();
await result.Content.CopyToAsync(memoryStream);
memoryStream.Position = 0; // 重置位置以便读取
return memoryStream;
}
else if (ObjectStoreServiceOptions.ObjectStoreUse == "MinIO")
{
var minIOConfig = ObjectStoreServiceOptions.MinIO;
var minioClient = new MinioClient()
.WithEndpoint($"{minIOConfig.EndPoint}:{minIOConfig.Port}")
.WithCredentials(minIOConfig.AccessKeyId, minIOConfig.SecretAccessKey)
.WithSSL(minIOConfig.UseSSL)
.Build();
var memoryStream = new MemoryStream();
var getObjectArgs = new GetObjectArgs()
.WithBucket(minIOConfig.BucketName)
.WithObject(ossRelativePath)
.WithCallbackStream(stream => stream.CopyToAsync(memoryStream));
await minioClient.GetObjectAsync(getObjectArgs);
memoryStream.Position = 0;
return memoryStream;
}
else if (ObjectStoreServiceOptions.ObjectStoreUse == "AWS")
{
var awsConfig = ObjectStoreServiceOptions.AWS;
var credentials = new SessionAWSCredentials(
AWSTempToken.AccessKeyId,
AWSTempToken.SecretAccessKey,
AWSTempToken.SessionToken
);
var clientConfig = new AmazonS3Config
{
RegionEndpoint = RegionEndpoint.USEast1,
UseHttp = true,
};
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
var getObjectRequest = new Amazon.S3.Model.GetObjectRequest
{
BucketName = awsConfig.BucketName,
Key = ossRelativePath
};
var response = await amazonS3Client.GetObjectAsync(getObjectRequest);
var memoryStream = new MemoryStream();
await response.ResponseStream.CopyToAsync(memoryStream);
memoryStream.Position = 0;
return memoryStream;
}
else
{
throw new BusinessValidationFailedException("未定义的存储介质类型");
}
}
catch (Exception ex)
{
throw new BusinessValidationFailedException("oss流获取失败! " + ex.Message);
}
}
public async Task<string> GetSignedUrl(string ossRelativePath)
{
GetObjectStoreTempToken();

View File

@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace IRaCIS.Core.Application.Helper
{
public static class SafeBussinessHelper
{
public static async Task<bool> RunAsync(Func<Task> func, [CallerMemberName] string caller = "", string errorMsgTitle = "")
{
try
{
await func();
return true;
}
catch (Exception ex)
{
Log.Logger.Error($"【{errorMsgTitle}失败 - {caller}】: {ex.Message}");
return false;
}
}
public static async Task<(bool Success, T? Result)> RunAsync<T>(Func<Task<T>> func, [CallerMemberName] string caller = "", string errorMsgTitle = "")
{
try
{
var result = await func();
return (true, result);
}
catch (Exception ex)
{
Log.Logger.Error($"【{errorMsgTitle}失败 - {caller}】: {ex.Message}");
return (false, default);
}
}
}
}

View File

@ -1111,6 +1111,13 @@
<param name="trialId"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.Application.Service.TrialImageDownloadService.DownloadAndUploadTrialData(System.Guid,IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.DicomInstance},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.DicomStudy},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.DicomSeries})">
<summary>
下载影像 维护dir信息 并回传到OSS
</summary>
<param name="trialId"></param>
<returns></returns>
</member>
<member name="T:IRaCIS.Core.Application.Service.AttachmentService">
<summary>
医生文档关联关系维护

View File

@ -5,6 +5,7 @@ using MassTransit;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using SharpCompress.Common;
using System;
using System.Collections.Generic;
using System.Linq;
@ -232,25 +233,32 @@ namespace IRaCIS.Core.Application.Service
}
///// <summary>
///// 下载影像 维护dir信息 并回传到OSS
///// </summary>
///// <param name="trialId"></param>
///// <returns></returns>
//[HttpGet]
//[AllowAnonymous]
//public async Task<IResponseOutput> DownloadAndUploadTrialData(Guid trialId, [FromServices] IRepository<DicomInstance> _instanceRepository,
// [FromServices] IRepository<DicomStudy> _studyRepository,
// [FromServices] IRepository<DicomSeries> _seriesRepository)
//{
// var list = await _instanceRepository.Where(t => t.TrialId == trialId)
// .Select(t => new { t.SeriesId, t.StudyId, t.Id, t.Path }).ToListAsync();
/// <summary>
/// 下载影像 维护dir信息 并回传到OSS
/// </summary>
/// <param name="trialId"></param>
/// <returns></returns>
[HttpGet]
[AllowAnonymous]
public async Task<IResponseOutput> DownloadAndUploadTrialData(Guid trialId, [FromServices] IRepository<DicomInstance> _instanceRepository,
[FromServices] IRepository<DicomStudy> _studyRepository,
[FromServices] IRepository<DicomSeries> _seriesRepository)
{
var list = await _instanceRepository.Where(t => t.TrialId == trialId)
.Select(t => new { t.SeriesId, t.StudyId, t.Id, t.Path }).ToListAsync();
// foreach (var item in list)
// {
foreach (var item in list)
{
var stream = await _oSSService.GetStreamFromOSSAsync(item.Path);
// }
//}
var dicomFile = DicomFile.Open(stream);
var dirInfo = DicomDIRHelper.ReadDicomDIRInfo(dicomFile);
}
return ResponseOutput.Ok();
}
}

View File

@ -520,7 +520,68 @@ namespace IRaCIS.Core.Application.Contracts
}
#region 下载重新生成名字
public class ImageDownloadDto
{
public Guid TrialId { get; set; }
public Guid SubjectId { get; set; }
public string SubjectCode { get; set; }
public string TrialSiteCode { get; set; }
public string VisitName { get; set; }
public string TaskBlindName { get; set; }
public Guid VisitId { get; set; }
public List<DownloadDicomStudyDto> StudyList { get; set; }
public List<DownloadNoneDicomStudyDto> NoneDicomStudyList { get; set; }
}
public class DownloadDicomStudyDto
{
public string PatientId { get; set; }
public DateTime? StudyTime { get; set; }
public string StudyCode { get; set; }
public string StudyInstanceUid { get; set; }
public string StudyDIRPath { get; set; }
public List<DownloadDicomSeriesDto> SeriesList { get; set; }
}
public class DownloadDicomSeriesDto
{
public string Modality { get; set; }
public List<DownloadDicomInstanceDto> InstanceList { get; set; }
}
public class DownloadDicomInstanceDto
{
public Guid InstanceId { get; set; }
public string FileName { get; set; }
public string Path { get; set; }
public long? FileSize { get; set; }
}
public class DownloadNoneDicomStudyDto
{
public string Modality { get; set; }
public string StudyCode { get; set; }
public DateTime? ImageDate { get; set; }
public List<DownloadNoneDicomFileDto> FileList { get; set; } = new();
}
public class DownloadNoneDicomFileDto
{
public string FileName { get; set; }
public string Path { get; set; }
public string FileType { get; set; }
public long? FileSize { get; set; }
}
#endregion
public class CRCUploadedStudyQuqry
{

View File

@ -782,6 +782,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
var imageType = (isQueryDicom && isQueryNoneDicom) ? ImageType.DicomAndNoneDicom : (isQueryDicom ? ImageType.Dicom : ImageType.NoneDicom);
var dirDic = new Dictionary<string, string>();
#region DIR处理导出文件名并将对应关系上传到OSS里面存储
//有传输语法值的导出 才生成DIR
@ -831,10 +832,16 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
{
var ossFolder = $"{pathInfo.TrialId}/Image/{pathInfo.SubjectId}/{pathInfo.VisitId}/{item.Key.StudyInstanceUid}";
await DicomDIRHelper.GenerateStudyDIRAndUploadAsync(item.ToList(), ossFolder, _oSSService);
var (isSucess, dic) = await SafeBussinessHelper.RunAsync(async () => await DicomDIRHelper.GenerateStudyDIRAndUploadAsync(item.ToList(), ossFolder, _oSSService));
dirDic = dic;
if (isSucess)
{
await _dicomStudyRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Key.DicomStudyId, u => new DicomStudy() { StudyDIRPath = $"/{ossFolder}/DICOMDIR" });
}
}
}
@ -843,7 +850,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
var query = from sv in _subjectVisitRepository.Where(t => t.Id == inQuery.SubjectVisitId)
select new
select new ImageDownloadDto()
{
TrialId = sv.TrialId,
SubjectId = sv.SubjectId,
@ -854,48 +861,57 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
StudyList = sv.StudyList.Where(t => isQueryDicom ? inQuery.DicomStudyIdList.Contains(t.Id) : false)
.Select(u => new
.Select(u => new DownloadDicomStudyDto()
{
u.PatientId,
u.StudyTime,
u.StudyCode,
u.StudyInstanceUid,
u.StudyDIRPath,
PatientId = u.PatientId,
StudyTime = u.StudyTime,
StudyCode = u.StudyCode,
StudyInstanceUid = u.StudyInstanceUid,
StudyDIRPath = u.StudyDIRPath,
SeriesList = u.SeriesList.Select(z => new
SeriesList = u.SeriesList.Select(z => new DownloadDicomSeriesDto()
{
z.Modality,
Modality = z.Modality,
InstanceList = z.DicomInstanceList.Select(k => new
InstanceList = z.DicomInstanceList.Select(k => new DownloadDicomInstanceDto()
{
k.Path,
k.FileSize
})
})
InstanceId = k.Id,
FileName = string.Empty,
Path = k.Path,
FileSize = k.FileSize
}).ToList()
}).ToList()
}).ToList(),
NoneDicomStudyList = sv.NoneDicomStudyList.Where(t => isQueryNoneDicom ? inQuery.NoneDicomStudyIdList.Contains(t.Id) : false)
.Select(nd => new
.Select(nd => new DownloadNoneDicomStudyDto()
{
nd.Modality,
nd.StudyCode,
nd.ImageDate,
Modality = nd.Modality,
StudyCode = nd.StudyCode,
ImageDate = nd.ImageDate,
FileList = nd.NoneDicomFileList.Select(file => new
FileList = nd.NoneDicomFileList.Select(file => new DownloadNoneDicomFileDto()
{
file.FileName,
file.Path,
file.FileType,
file.FileSize,
})
FileName = file.FileName,
Path = file.Path,
FileType = file.FileType,
FileSize = file.FileSize
}).ToList()
}).ToList()
};
var result = query.FirstOrDefault();
foreach (var item in result.StudyList.SelectMany(t => t.SeriesList).SelectMany(t => t.InstanceList))
{
var key = item.InstanceId.ToString();
if (dirDic.ContainsKey(key))
{
item.FileName = dirDic[key];
}
}
var preDownloadInfo = new TrialImageDownload()
{
@ -1070,12 +1086,13 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
var trialSiteCode = _visitTaskRepository.Where(t => t.Id == taskIdList.FirstOrDefault()).Select(t => t.IsAnalysisCreate ? t.BlindTrialSiteCode : t.Subject.TrialSite.TrialSiteCode).FirstOrDefault() ?? string.Empty;
var dirDic = new Dictionary<string, string>();
#region 在下载前先处理DIR文件
//有传输语法值的导出 才生成DIR
if (_subjectVisitRepository.Any(t => t.SubjectId == inQuery.SubjectId && t.StudyList.SelectMany(t => t.InstanceList).Any(c => c.TransferSytaxUID != string.Empty)))
{
var list = _subjectRepository.Where(t => t.Id == inQuery.SubjectId).SelectMany(t => t.SubjectVisitList.Where(t => subjectVisitIdList.Contains(t.Id))).SelectMany(t => t.StudyList)
var dirInfolist = _subjectRepository.Where(t => t.Id == inQuery.SubjectId).SelectMany(t => t.SubjectVisitList.Where(t => subjectVisitIdList.Contains(t.Id))).SelectMany(t => t.StudyList)
.Where(t => isQueryDicom ? inQuery.DicomStudyIdList.Contains(t.Id) : false)
.Where(t => info.IsImageFilter ? ("|" + info.CriterionModalitys + "|").Contains("|" + t.ModalityForEdit + "|") : true)
.SelectMany(t => t.InstanceList.Where(t => t.IsReading && t.DicomSerie.IsReading))
@ -1117,15 +1134,21 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
var pathInfo = await _subjectRepository.Where(t => t.Id == inQuery.SubjectId).Select(t => new { t.TrialId, SubjectId = t.Id }).FirstNotNullAsync();
foreach (var item in list.GroupBy(t => new { t.StudyInstanceUid, t.DicomStudyId }))
foreach (var item in dirInfolist.GroupBy(t => new { t.StudyInstanceUid, t.DicomStudyId }))
{
var visitId = item.First().SubjectVisitId;
var ossFolder = $"{pathInfo.TrialId}/Image/{pathInfo.SubjectId}/{visitId}/{item.Key.StudyInstanceUid}";
await DicomDIRHelper.GenerateStudyDIRAndUploadAsync(item.ToList(), ossFolder, _oSSService);
var (isSucess, dic) = await SafeBussinessHelper.RunAsync(async () => await DicomDIRHelper.GenerateStudyDIRAndUploadAsync(item.ToList(), ossFolder, _oSSService));
dirDic = dic;
if (isSucess)
{
await _dicomStudyRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Key.DicomStudyId, u => new DicomStudy() { StudyDIRPath = $"/{ossFolder}/DICOMDIR" });
}
}
}
@ -1136,55 +1159,70 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
join visitTask in _visitTaskRepository.Where(t => taskIdList.Contains(t.Id))
on sv.Id equals visitTask.SourceSubjectVisitId
select new
select new ImageDownloadDto()
{
SubjectCode = inQuery.SubjectCode,
VisitName = sv.VisitName,
TaskBlindName = visitTask.TaskBlindName,
StudyList = sv.StudyList.Where(t => isQueryDicom ? inQuery.DicomStudyIdList.Contains(t.Id) : false)
.Where(t => info.IsImageFilter ? ("|" + info.CriterionModalitys + "|").Contains("|" + t.ModalityForEdit + "|") : true)
.Select(u => new
.Select(u => new DownloadDicomStudyDto()
{
u.PatientId,
u.StudyTime,
u.StudyCode,
u.StudyDIRPath,
PatientId = u.PatientId,
StudyTime = u.StudyTime,
StudyCode = u.StudyCode,
StudyInstanceUid = u.StudyInstanceUid,
StudyDIRPath = u.StudyDIRPath,
SeriesList = u.SeriesList.Where(t => t.IsReading).Select(z => new
SeriesList = u.SeriesList.Where(t => t.IsReading).Select(z => new DownloadDicomSeriesDto()
{
z.Modality,
Modality = z.Modality,
InstancePathList = z.DicomInstanceList.Where(t => t.IsReading).Select(k => new
InstanceList = z.DicomInstanceList.Where(t => t.IsReading).Select(k => new DownloadDicomInstanceDto()
{
k.Path,
k.FileSize
})
})
}),
InstanceId = k.Id,
FileName = string.Empty,
Path = k.Path,
FileSize = k.FileSize
}).ToList()
}).ToList()
}).ToList(),
NoneDicomStudyList = sv.NoneDicomStudyList.Where(t => isQueryNoneDicom ? inQuery.NoneDicomStudyIdList.Contains(t.Id) : false)
.Where(t => info.IsImageFilter ? ("|" + info.CriterionModalitys + "|").Contains("|" + t.Modality + "|") : true)
.Where(t => t.IsReading)
.Select(nd => new
.Select(nd => new DownloadNoneDicomStudyDto()
{
nd.Modality,
nd.StudyCode,
nd.ImageDate,
Modality = nd.Modality,
StudyCode = nd.StudyCode,
ImageDate = nd.ImageDate,
FileList = nd.NoneDicomFileList.Where(t => t.IsReading).Select(file => new
FileList = nd.NoneDicomFileList.Where(t => t.IsReading).Select(file => new DownloadNoneDicomFileDto()
{
file.FileName,
file.Path,
file.FileType,
file.FileSize
})
})
FileName = file.FileName,
Path = file.Path,
FileType = file.FileType,
FileSize = file.FileSize
}).ToList()
}).ToList()
};
var result = await query.ToListAsync();
var list = await query.ToListAsync();
foreach (var result in list)
{
foreach (var item in result.StudyList.SelectMany(t => t.SeriesList).SelectMany(t => t.InstanceList))
{
var key = item.InstanceId.ToString();
if (dirDic.ContainsKey(key))
{
item.FileName = dirDic[key];
}
}
}
@ -1199,17 +1237,17 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
DownloadStartTime = DateTime.Now,
IsSuccess = false,
ImageType = imageType,
VisitName = string.Join(" | ", result.Select(t => t.VisitName).OrderBy(t => t).ToList()),
NoneDicomStudyCount = result.Sum(t => t.NoneDicomStudyList.Count()),
DicomStudyCount = result.Sum(t => t.StudyList.Count()),
ImageCount = result.Sum(t => t.StudyList.Sum(s => s.SeriesList.Sum(s => s.InstancePathList.Count())) + t.NoneDicomStudyList.Sum(s => s.FileList.Count())),
ImageSize = result.Sum(t => t.StudyList.Sum(t => t.SeriesList.Sum(s => s.InstancePathList.Sum(i => i.FileSize))) + t.NoneDicomStudyList.Sum(t => t.FileList.Sum(s => s.FileSize))
VisitName = string.Join(" | ", list.Select(t => t.VisitName).OrderBy(t => t).ToList()),
NoneDicomStudyCount = list.Sum(t => t.NoneDicomStudyList.Count()),
DicomStudyCount = list.Sum(t => t.StudyList.Count()),
ImageCount = list.Sum(t => t.StudyList.Sum(s => s.SeriesList.Sum(s => s.InstanceList.Count())) + t.NoneDicomStudyList.Sum(s => s.FileList.Count())),
ImageSize = list.Sum(t => t.StudyList.Sum(t => t.SeriesList.Sum(s => s.InstanceList.Sum(i => i.FileSize))) + t.NoneDicomStudyList.Sum(t => t.FileList.Sum(s => s.FileSize))
) ?? 0
};
await _trialImageDownloadRepository.AddAsync(preDownloadInfo, true);
return ResponseOutput.Ok(result, new { PreDownloadId = preDownloadInfo.Id, info.IsReadingTaskViewInOrder });
return ResponseOutput.Ok(list, new { PreDownloadId = preDownloadInfo.Id, info.IsReadingTaskViewInOrder });
}
/// <summary>