Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
commit
ebf6c225f3
|
|
@ -204,6 +204,8 @@ public interface IOSSService
|
||||||
public Task<long> GetObjectSizeAsync(string sourcePath);
|
public Task<long> GetObjectSizeAsync(string sourcePath);
|
||||||
|
|
||||||
public Task SyncFileAsync(string objectKey, ObjectStoreUse source, ObjectStoreUse destination, CancellationToken ct = default);
|
public Task SyncFileAsync(string objectKey, ObjectStoreUse source, ObjectStoreUse destination, CancellationToken ct = default);
|
||||||
|
|
||||||
|
public void ConvertPrefixToStandard(string prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -215,6 +217,7 @@ public class OSSService(IOptionsMonitor<ObjectStoreServiceOptions> options,
|
||||||
private AliyunOSSTempToken AliyunOSSTempToken { get; set; }
|
private AliyunOSSTempToken AliyunOSSTempToken { get; set; }
|
||||||
|
|
||||||
private AWSTempToken AWSTempToken { get; set; }
|
private AWSTempToken AWSTempToken { get; set; }
|
||||||
|
public object result { get; private set; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -652,6 +655,90 @@ public class OSSService(IOptionsMonitor<ObjectStoreServiceOptions> options,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将某个路径下的归档的文件 转为标准存储
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="prefix"></param>
|
||||||
|
public void ConvertPrefixToStandard(string prefix)
|
||||||
|
{
|
||||||
|
|
||||||
|
BackBatchGetToken();
|
||||||
|
|
||||||
|
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 bucketName = aliConfig.BucketName;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ObjectListing objectListing = null;
|
||||||
|
string nextMarker = null;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
// 使用 prefix 模拟目录结构,设置 MaxKeys 和 NextMarker
|
||||||
|
objectListing = _ossClient.ListObjects(new Aliyun.OSS.ListObjectsRequest(bucketName)
|
||||||
|
{
|
||||||
|
Prefix = prefix,
|
||||||
|
MaxKeys = 1000,
|
||||||
|
Marker = nextMarker
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
foreach (var obj in objectListing.ObjectSummaries)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 👇 跳过已经是标准存储的(优化)
|
||||||
|
if (obj.StorageClass == StorageClass.Standard.ToString())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var metadata = new ObjectMetadata();
|
||||||
|
|
||||||
|
// 👇 关键:手动加 Header
|
||||||
|
metadata.AddHeader("x-oss-storage-class", "Standard");
|
||||||
|
|
||||||
|
var copyRequest = new Aliyun.OSS.CopyObjectRequest(bucketName, obj.Key, bucketName, obj.Key) { NewObjectMetadata = metadata };
|
||||||
|
|
||||||
|
|
||||||
|
_ossClient.CopyObject(copyRequest);
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Logger.Error($"❌ 失败: {obj.Key}, 错误: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 设置 NextMarker 以获取下一页的数据
|
||||||
|
nextMarker = objectListing.NextMarker;
|
||||||
|
|
||||||
|
} while (objectListing.IsTruncated);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Logger.Error($"Error: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (ObjectStoreServiceOptions.ObjectStoreUse == "AWS")
|
||||||
|
{
|
||||||
|
throw new BusinessValidationFailedException("未定义的存储介质类型");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 坑方法,会清空之前的规则
|
/// 坑方法,会清空之前的规则
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -1507,20 +1594,20 @@ public class OSSService(IOptionsMonitor<ObjectStoreServiceOptions> options,
|
||||||
|
|
||||||
GetObjectStoreTempToken(objectUse: config.Primary);
|
GetObjectStoreTempToken(objectUse: config.Primary);
|
||||||
|
|
||||||
await DeleteFromPrefixInternal(config.Primary,prefix, isCache);
|
await DeleteFromPrefixInternal(config.Primary, prefix, isCache);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
GetObjectStoreTempToken();
|
GetObjectStoreTempToken();
|
||||||
|
|
||||||
await DeleteFromPrefixInternal(ObjectStoreServiceOptions.ObjectStoreUse,prefix, isCache);
|
await DeleteFromPrefixInternal(ObjectStoreServiceOptions.ObjectStoreUse, prefix, isCache);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private async Task DeleteFromPrefixInternal(string objectUse ,string prefix, bool isCache = false)
|
private async Task DeleteFromPrefixInternal(string objectUse, string prefix, bool isCache = false)
|
||||||
{
|
{
|
||||||
if (objectUse == "AliyunOSS")
|
if (objectUse == "AliyunOSS")
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using FellowOakDicom.IO.Buffer;
|
||||||
using SixLabors.ImageSharp;
|
using SixLabors.ImageSharp;
|
||||||
using SixLabors.ImageSharp.Formats.Jpeg;
|
using SixLabors.ImageSharp.Formats.Jpeg;
|
||||||
using SixLabors.ImageSharp.Processing;
|
using SixLabors.ImageSharp.Processing;
|
||||||
|
using System.Data;
|
||||||
|
|
||||||
namespace IRaCIS.Core.Application.Helper;
|
namespace IRaCIS.Core.Application.Helper;
|
||||||
|
|
||||||
|
|
@ -158,8 +159,16 @@ public sealed class DicomMaskOptions
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 当 AutoSelectGrayscaleMaskValue=false 时,使用该值。
|
/// 当 AutoSelectGrayscaleMaskValue=false 时,使用该值。
|
||||||
/// 当为 null 且 AutoSelectGrayscaleMaskValue=false 时,默认 0。
|
/// 当为 null 且 AutoSelectGrayscaleMaskValue=false 时,默认 0。
|
||||||
|
/// 灰度图像的遮挡像素值,默认 0。
|
||||||
|
/// 注意:这表示写入的原始像素值,不保证视觉上一定为黑色。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int? GrayscaleMaskValue { get; init; }
|
public int? GrayscaleMaskValue { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// PALETTE COLOR 图像使用的遮盖索引。
|
||||||
|
/// null 表示自动从调色板中选择最暗的一个索引。
|
||||||
|
/// </summary>
|
||||||
|
public int? PaletteColorMaskIndex { get; init; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -213,8 +222,10 @@ public static class DicomPixelMasker
|
||||||
var planarConfiguration = originalDataset.GetSingleValueOrDefault(DicomTag.PlanarConfiguration, (ushort)0);
|
var planarConfiguration = originalDataset.GetSingleValueOrDefault(DicomTag.PlanarConfiguration, (ushort)0);
|
||||||
Console.WriteLine($"Rows={rows}, Cols={cols}, BitsAllocated={bitsAllocated}, BitsStored={bitsStored}, SamplesPerPixel={samplesPerPixel}, PixelRepresentation={pixelRepresentation}, PlanarConfiguration={planarConfiguration}");
|
Console.WriteLine($"Rows={rows}, Cols={cols}, BitsAllocated={bitsAllocated}, BitsStored={bitsStored}, SamplesPerPixel={samplesPerPixel}, PixelRepresentation={pixelRepresentation}, PlanarConfiguration={planarConfiguration}");
|
||||||
|
|
||||||
EnsureSupportedPhotometric(originalPhotometric, samplesPerPixel);
|
var isSupport = IsSupportedPhotometric(originalPhotometric, samplesPerPixel);
|
||||||
|
|
||||||
|
if (isSupport)
|
||||||
|
{
|
||||||
// 1. 转为工作用的未压缩 DICOM
|
// 1. 转为工作用的未压缩 DICOM
|
||||||
var workingFile = await EnsureUncompressedAsync(originalFile, cancellationToken).ConfigureAwait(false);
|
var workingFile = await EnsureUncompressedAsync(originalFile, cancellationToken).ConfigureAwait(false);
|
||||||
// 2. 修改像素
|
// 2. 修改像素
|
||||||
|
|
@ -245,6 +256,12 @@ public static class DicomPixelMasker
|
||||||
|
|
||||||
if (output.CanSeek)
|
if (output.CanSeek)
|
||||||
output.Position = 0;
|
output.Position = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -264,23 +281,24 @@ public static class DicomPixelMasker
|
||||||
_ = dataset.GetSingleValue<int>(DicomTag.SamplesPerPixel);
|
_ = dataset.GetSingleValue<int>(DicomTag.SamplesPerPixel);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void EnsureSupportedPhotometric(string photometric, int samplesPerPixel, Action<string>? log = null)
|
private static bool IsSupportedPhotometric(string photometric, int samplesPerPixel)
|
||||||
{
|
{
|
||||||
if (samplesPerPixel == 1)
|
if (samplesPerPixel == 1)
|
||||||
{
|
{
|
||||||
if (string.Equals(photometric, "MONOCHROME1", StringComparison.OrdinalIgnoreCase) ||
|
if (string.Equals(photometric, "MONOCHROME1", StringComparison.OrdinalIgnoreCase) ||
|
||||||
string.Equals(photometric, "MONOCHROME2", StringComparison.OrdinalIgnoreCase))
|
string.Equals(photometric, "MONOCHROME2", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(photometric, "PALETTE COLOR", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
throw new NotSupportedException($"Unsupported grayscale PhotometricInterpretation: {photometric}");
|
return false;
|
||||||
}
|
}
|
||||||
if (samplesPerPixel == 3)
|
if (samplesPerPixel == 3)
|
||||||
{
|
{
|
||||||
if (string.Equals(photometric, "RGB", StringComparison.OrdinalIgnoreCase) ||
|
if (string.Equals(photometric, "RGB", StringComparison.OrdinalIgnoreCase) ||
|
||||||
string.Equals(photometric, "YBR_FULL", StringComparison.OrdinalIgnoreCase))
|
string.Equals(photometric, "YBR_FULL", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
// 对 YBR_FULL_422 不建议直接按当前布局改
|
// 对 YBR_FULL_422 不建议直接按当前布局改
|
||||||
if (string.Equals(photometric, "YBR_FULL_422", StringComparison.OrdinalIgnoreCase) ||
|
if (string.Equals(photometric, "YBR_FULL_422", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
|
@ -290,9 +308,12 @@ public static class DicomPixelMasker
|
||||||
throw new NotSupportedException(
|
throw new NotSupportedException(
|
||||||
$"PhotometricInterpretation={photometric} uses subsampling layout and is not safely supported by this pixel masking implementation.");
|
$"PhotometricInterpretation={photometric} uses subsampling layout and is not safely supported by this pixel masking implementation.");
|
||||||
}
|
}
|
||||||
throw new NotSupportedException($"Unsupported color PhotometricInterpretation: {photometric}");
|
|
||||||
|
return false;
|
||||||
|
//throw new NotSupportedException($"Unsupported color PhotometricInterpretation: {photometric}");
|
||||||
}
|
}
|
||||||
throw new NotSupportedException($"Unsupported SamplesPerPixel={samplesPerPixel}");
|
return false;
|
||||||
|
//throw new NotSupportedException($"Unsupported SamplesPerPixel={samplesPerPixel}");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -402,7 +423,7 @@ public static class DicomPixelMasker
|
||||||
photometric: photometric,
|
photometric: photometric,
|
||||||
planarConfiguration: planarConfiguration,
|
planarConfiguration: planarConfiguration,
|
||||||
regions: regions,
|
regions: regions,
|
||||||
options: options);
|
options: options, dataset);
|
||||||
}
|
}
|
||||||
replacementFrames.Add(new MemoryByteBuffer(bytes));
|
replacementFrames.Add(new MemoryByteBuffer(bytes));
|
||||||
}
|
}
|
||||||
|
|
@ -455,9 +476,34 @@ public static class DicomPixelMasker
|
||||||
string photometric,
|
string photometric,
|
||||||
ushort planarConfiguration,
|
ushort planarConfiguration,
|
||||||
IReadOnlyList<MaskRegion> regions,
|
IReadOnlyList<MaskRegion> regions,
|
||||||
DicomMaskOptions options)
|
DicomMaskOptions options,
|
||||||
|
DicomDataset dataset)
|
||||||
{
|
{
|
||||||
if (samplesPerPixel == 1)
|
if (samplesPerPixel == 1)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (string.Equals(photometric, "PALETTE COLOR", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
if (pixelRepresentation != 0)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("PALETTE COLOR with signed pixel representation is not supported.");
|
||||||
|
}
|
||||||
|
if (bitsAllocated == 8)
|
||||||
|
{
|
||||||
|
byte maskIndex = (byte)ResolvePaletteColorMaskIndex(dataset, bitsStored, options);
|
||||||
|
ApplyMask_Grayscale8(frameData, rows, cols, regions, maskIndex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (bitsAllocated == 16)
|
||||||
|
{
|
||||||
|
ushort maskIndex = ResolvePaletteColorMaskIndex(dataset, bitsStored, options);
|
||||||
|
ApplyMask_Grayscale16(frameData, rows, cols, regions, maskIndex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new NotSupportedException(
|
||||||
|
$"Unsupported PALETTE COLOR image: BitsAllocated={bitsAllocated}");
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
if (bitsAllocated == 8)
|
if (bitsAllocated == 8)
|
||||||
{
|
{
|
||||||
|
|
@ -478,6 +524,9 @@ public static class DicomPixelMasker
|
||||||
throw new NotSupportedException(
|
throw new NotSupportedException(
|
||||||
$"Unsupported grayscale image: BitsAllocated={bitsAllocated}, Photometric={photometric}");
|
$"Unsupported grayscale image: BitsAllocated={bitsAllocated}, Photometric={photometric}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
if (samplesPerPixel == 3)
|
if (samplesPerPixel == 3)
|
||||||
{
|
{
|
||||||
if (bitsAllocated != 8)
|
if (bitsAllocated != 8)
|
||||||
|
|
@ -501,6 +550,276 @@ public static class DicomPixelMasker
|
||||||
throw new NotSupportedException(
|
throw new NotSupportedException(
|
||||||
$"Unsupported format: SamplesPerPixel={samplesPerPixel}, BitsAllocated={bitsAllocated}, Photometric={photometric}");
|
$"Unsupported format: SamplesPerPixel={samplesPerPixel}, BitsAllocated={bitsAllocated}, Photometric={photometric}");
|
||||||
}
|
}
|
||||||
|
private static ushort ResolvePaletteColorMaskIndex(
|
||||||
|
DicomDataset dataset,
|
||||||
|
int bitsStored,
|
||||||
|
DicomMaskOptions options)
|
||||||
|
{
|
||||||
|
int pixelMin = 0;
|
||||||
|
int pixelMax = bitsStored == 16 ? ushort.MaxValue : ((1 << bitsStored) - 1);
|
||||||
|
|
||||||
|
|
||||||
|
if (options.PaletteColorMaskIndex.HasValue)
|
||||||
|
{
|
||||||
|
int manual = options.PaletteColorMaskIndex.Value;
|
||||||
|
if (manual < pixelMin || manual > pixelMax)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(
|
||||||
|
nameof(options.PaletteColorMaskIndex),
|
||||||
|
$"PaletteColorMaskIndex must be in range [{pixelMin}, {pixelMax}] for BitsStored={bitsStored}.");
|
||||||
|
}
|
||||||
|
return (ushort)manual;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 尝试从 Descriptor 读取 FirstMappedPixelValue
|
||||||
|
if (TryGetPaletteFirstMappedPixelValue(dataset, out int firstMapped))
|
||||||
|
{
|
||||||
|
if (firstMapped < pixelMin || firstMapped > pixelMax)
|
||||||
|
{
|
||||||
|
throw new InvalidDataException(
|
||||||
|
$"Palette LUT FirstMappedPixelValue={firstMapped} is out of pixel range [{pixelMin}, {pixelMax}].");
|
||||||
|
}
|
||||||
|
return (ushort)firstMapped;
|
||||||
|
}
|
||||||
|
// 3. 实在没有就退化为 0(可改成直接抛异常)
|
||||||
|
if (0 >= pixelMin && 0 <= pixelMax)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidDataException(
|
||||||
|
"Cannot resolve palette color mask index because palette LUT descriptor is missing.");
|
||||||
|
|
||||||
|
//int autoIndex = FindDarkestPaletteIndex(dataset, maxValue);
|
||||||
|
//return (byte)autoIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryGetPaletteFirstMappedPixelValue(DicomDataset dataset, out int firstMappedPixelValue)
|
||||||
|
{
|
||||||
|
firstMappedPixelValue = 0;
|
||||||
|
|
||||||
|
if (TryGetPaletteDescriptor(dataset, DicomTag.RedPaletteColorLookupTableDescriptor, out var redDesc))
|
||||||
|
{
|
||||||
|
firstMappedPixelValue = redDesc.FirstMappedPixelValue;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryGetPaletteDescriptor(dataset, DicomTag.GreenPaletteColorLookupTableDescriptor, out var greenDesc))
|
||||||
|
{
|
||||||
|
firstMappedPixelValue = greenDesc.FirstMappedPixelValue;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryGetPaletteDescriptor(dataset, DicomTag.BluePaletteColorLookupTableDescriptor, out var blueDesc))
|
||||||
|
{
|
||||||
|
firstMappedPixelValue = blueDesc.FirstMappedPixelValue;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class PaletteLutDescriptor
|
||||||
|
{
|
||||||
|
public int NumberOfEntries { get; init; }
|
||||||
|
public int FirstMappedPixelValue { get; init; }
|
||||||
|
public int BitsPerEntry { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryGetPaletteDescriptor(
|
||||||
|
DicomDataset dataset,
|
||||||
|
DicomTag tag,
|
||||||
|
out PaletteLutDescriptor descriptor)
|
||||||
|
{
|
||||||
|
descriptor = null!;
|
||||||
|
|
||||||
|
if (!dataset.Contains(tag))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// LUT Descriptor 可能是 US 或 SS,fo-dicom 通常可直接按 short/ushort 读
|
||||||
|
if (dataset.TryGetValues<short>(tag, out short[]? ssValues) && ssValues != null && ssValues.Length >= 3)
|
||||||
|
{
|
||||||
|
descriptor = new PaletteLutDescriptor
|
||||||
|
{
|
||||||
|
NumberOfEntries = ssValues[0] == 0 ? 65536 : ssValues[0],
|
||||||
|
FirstMappedPixelValue = ssValues[1],
|
||||||
|
BitsPerEntry = ssValues[2]
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dataset.TryGetValues<ushort>(tag, out ushort[]? usValues) && usValues != null && usValues.Length >= 3)
|
||||||
|
{
|
||||||
|
descriptor = new PaletteLutDescriptor
|
||||||
|
{
|
||||||
|
NumberOfEntries = usValues[0] == 0 ? 65536 : usValues[0],
|
||||||
|
FirstMappedPixelValue = usValues[1],
|
||||||
|
BitsPerEntry = usValues[2]
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region 尾彩
|
||||||
|
|
||||||
|
//private static ushort ResolvePaletteColorMaskIndex16(
|
||||||
|
//DicomDataset dataset,
|
||||||
|
//int bitsStored,
|
||||||
|
//DicomMaskOptions options)
|
||||||
|
//{
|
||||||
|
// int maxValue = bitsStored >= 16 ? 65535 : (1 << bitsStored) - 1;
|
||||||
|
|
||||||
|
// if (options.PaletteColorMaskIndex.HasValue)
|
||||||
|
// {
|
||||||
|
// int v = options.PaletteColorMaskIndex.Value;
|
||||||
|
// if (v < 0 || v > maxValue)
|
||||||
|
// {
|
||||||
|
// throw new ArgumentOutOfRangeException(
|
||||||
|
// nameof(options.PaletteColorMaskIndex),
|
||||||
|
// $"PaletteColorMaskIndex must be in range [0, {maxValue}] for BitsStored={bitsStored}");
|
||||||
|
// }
|
||||||
|
// return (ushort)v;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// int autoIndex = FindDarkestPaletteIndex(dataset, maxValue);
|
||||||
|
// return (ushort)autoIndex;
|
||||||
|
//}
|
||||||
|
|
||||||
|
//private static int FindDarkestPaletteIndex(DicomDataset dataset, int maxPixelValue)
|
||||||
|
//{
|
||||||
|
// var redDesc = GetPaletteDescriptor(dataset, DicomTag.RedPaletteColorLookupTableDescriptor);
|
||||||
|
// var greenDesc = GetPaletteDescriptor(dataset, DicomTag.GreenPaletteColorLookupTableDescriptor);
|
||||||
|
// var blueDesc = GetPaletteDescriptor(dataset, DicomTag.BluePaletteColorLookupTableDescriptor);
|
||||||
|
|
||||||
|
// if (redDesc.EntryCount != greenDesc.EntryCount || redDesc.EntryCount != blueDesc.EntryCount ||
|
||||||
|
// redDesc.FirstMappedValue != greenDesc.FirstMappedValue || redDesc.FirstMappedValue != blueDesc.FirstMappedValue ||
|
||||||
|
// redDesc.BitsPerEntry != greenDesc.BitsPerEntry || redDesc.BitsPerEntry != blueDesc.BitsPerEntry)
|
||||||
|
// {
|
||||||
|
// throw new NotSupportedException("RGB palette LUT descriptors are inconsistent.");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// ushort[] red = GetPaletteLutDataAsUShortArray(dataset, DicomTag.RedPaletteColorLookupTableData, redDesc);
|
||||||
|
// ushort[] green = GetPaletteLutDataAsUShortArray(dataset, DicomTag.GreenPaletteColorLookupTableData, greenDesc);
|
||||||
|
// ushort[] blue = GetPaletteLutDataAsUShortArray(dataset, DicomTag.BluePaletteColorLookupTableData, blueDesc);
|
||||||
|
|
||||||
|
// if (red.Length != redDesc.EntryCount || green.Length != greenDesc.EntryCount || blue.Length != blueDesc.EntryCount)
|
||||||
|
// {
|
||||||
|
// throw new NotSupportedException("Palette LUT data length does not match LUT descriptor.");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// long bestScore = long.MaxValue;
|
||||||
|
// int bestLutIndex = 0;
|
||||||
|
|
||||||
|
// for (int i = 0; i < redDesc.EntryCount; i++)
|
||||||
|
// {
|
||||||
|
// long score = (long)red[i] + green[i] + blue[i];
|
||||||
|
// if (score < bestScore)
|
||||||
|
// {
|
||||||
|
// bestScore = score;
|
||||||
|
// bestLutIndex = i;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// int pixelValue = redDesc.FirstMappedValue + bestLutIndex;
|
||||||
|
|
||||||
|
// if (pixelValue < 0)
|
||||||
|
// pixelValue = 0;
|
||||||
|
// if (pixelValue > maxPixelValue)
|
||||||
|
// pixelValue = maxPixelValue;
|
||||||
|
|
||||||
|
// return pixelValue;
|
||||||
|
//}
|
||||||
|
|
||||||
|
//private static ushort[] GetPaletteLutDataAsUShortArray(
|
||||||
|
//DicomDataset dataset,
|
||||||
|
//DicomTag dataTag,
|
||||||
|
//PaletteLutDescriptor descriptor)
|
||||||
|
//{
|
||||||
|
// if (!dataset.TryGetSingleValue<byte[]>(dataTag, out var rawBytes) || rawBytes == null || rawBytes.Length == 0)
|
||||||
|
// {
|
||||||
|
// throw new NotSupportedException($"Missing palette LUT data: {dataTag}");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// int entryCount = descriptor.EntryCount;
|
||||||
|
// int bitsPerEntry = descriptor.BitsPerEntry;
|
||||||
|
|
||||||
|
// if (bitsPerEntry <= 8)
|
||||||
|
// {
|
||||||
|
// if (rawBytes.Length < entryCount)
|
||||||
|
// {
|
||||||
|
// throw new NotSupportedException(
|
||||||
|
// $"Palette LUT data too short for {dataTag}. Expected at least {entryCount} bytes, actual {rawBytes.Length}.");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// var result = new ushort[entryCount];
|
||||||
|
// for (int i = 0; i < entryCount; i++)
|
||||||
|
// {
|
||||||
|
// result[i] = rawBytes[i];
|
||||||
|
// }
|
||||||
|
// return result;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (bitsPerEntry <= 16)
|
||||||
|
// {
|
||||||
|
// int requiredBytes = entryCount * 2;
|
||||||
|
// if (rawBytes.Length < requiredBytes)
|
||||||
|
// {
|
||||||
|
// throw new NotSupportedException(
|
||||||
|
// $"Palette LUT data too short for {dataTag}. Expected at least {requiredBytes} bytes, actual {rawBytes.Length}.");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// var result = new ushort[entryCount];
|
||||||
|
// for (int i = 0; i < entryCount; i++)
|
||||||
|
// {
|
||||||
|
// int byteIndex = i * 2;
|
||||||
|
// result[i] = (ushort)(rawBytes[byteIndex] | (rawBytes[byteIndex + 1] << 8));
|
||||||
|
// }
|
||||||
|
// return result;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// throw new NotSupportedException(
|
||||||
|
// $"Unsupported palette LUT bits per entry {bitsPerEntry} for {dataTag}.");
|
||||||
|
//}
|
||||||
|
|
||||||
|
//private readonly struct PaletteLutDescriptor
|
||||||
|
//{
|
||||||
|
// public int EntryCount { get; }
|
||||||
|
// public int FirstMappedValue { get; }
|
||||||
|
// public int BitsPerEntry { get; }
|
||||||
|
|
||||||
|
// public PaletteLutDescriptor(int entryCount, int firstMappedValue, int bitsPerEntry)
|
||||||
|
// {
|
||||||
|
// EntryCount = entryCount;
|
||||||
|
// FirstMappedValue = firstMappedValue;
|
||||||
|
// BitsPerEntry = bitsPerEntry;
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
//private static PaletteLutDescriptor GetPaletteDescriptor(DicomDataset dataset, DicomTag tag)
|
||||||
|
//{
|
||||||
|
// var values = dataset.GetValues<ushort>(tag);
|
||||||
|
// if (values == null || values.Length < 3)
|
||||||
|
// throw new NotSupportedException($"Missing or invalid palette LUT descriptor: {tag}");
|
||||||
|
|
||||||
|
// int entryCount = values[0] == 0 ? 65536 : values[0];
|
||||||
|
|
||||||
|
// // 第二个值在 DICOM 标准中是 signed short
|
||||||
|
// int firstMappedValue = (short)values[1];
|
||||||
|
|
||||||
|
// int bitsPerEntry = values[2];
|
||||||
|
// if (bitsPerEntry <= 0)
|
||||||
|
// throw new NotSupportedException($"Invalid palette LUT descriptor bits per entry: {bitsPerEntry}");
|
||||||
|
|
||||||
|
// return new PaletteLutDescriptor(entryCount, firstMappedValue, bitsPerEntry);
|
||||||
|
//}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
private static byte ResolveGrayscaleMaskValue8(string photometric, DicomMaskOptions options)
|
private static byte ResolveGrayscaleMaskValue8(string photometric, DicomMaskOptions options)
|
||||||
{
|
{
|
||||||
if (!options.AutoSelectGrayscaleMaskValue)
|
if (!options.AutoSelectGrayscaleMaskValue)
|
||||||
|
|
|
||||||
|
|
@ -15927,6 +15927,12 @@
|
||||||
<param name="modelVerify"></param>
|
<param name="modelVerify"></param>
|
||||||
<returns></returns>
|
<returns></returns>
|
||||||
</member>
|
</member>
|
||||||
|
<member name="M:IRaCIS.Core.Application.Service.TestService.ConvertPrefixToStandard">
|
||||||
|
<summary>
|
||||||
|
归档直读
|
||||||
|
</summary>
|
||||||
|
<returns></returns>
|
||||||
|
</member>
|
||||||
<member name="M:IRaCIS.Core.Application.Service.TestService.TestTrialEfficacyEvaluationStat(System.Collections.Generic.List{IRaCIS.Core.Application.Service.TestService.TestEfficacyEvaluation})">
|
<member name="M:IRaCIS.Core.Application.Service.TestService.TestTrialEfficacyEvaluationStat(System.Collections.Generic.List{IRaCIS.Core.Application.Service.TestService.TestEfficacyEvaluation})">
|
||||||
<summary>
|
<summary>
|
||||||
测试疗效评估
|
测试疗效评估
|
||||||
|
|
@ -16110,6 +16116,12 @@
|
||||||
<param name="isDelete">默认是添加/更新 </param>
|
<param name="isDelete">默认是添加/更新 </param>
|
||||||
</member>
|
</member>
|
||||||
<!-- Badly formed XML comment ignored for member "M:IRaCIS.Core.Application.Helper.OSSService.RestoreFilesByPrefixAsync(System.String,System.Int32,System.Int32)" -->
|
<!-- Badly formed XML comment ignored for member "M:IRaCIS.Core.Application.Helper.OSSService.RestoreFilesByPrefixAsync(System.String,System.Int32,System.Int32)" -->
|
||||||
|
<member name="M:IRaCIS.Core.Application.Helper.OSSService.ConvertPrefixToStandard(System.String)">
|
||||||
|
<summary>
|
||||||
|
将某个路径下的归档的文件 转为标准存储
|
||||||
|
</summary>
|
||||||
|
<param name="prefix"></param>
|
||||||
|
</member>
|
||||||
<member name="M:IRaCIS.Core.Application.Helper.OSSService.SetLifecycle(System.String,System.String)">
|
<member name="M:IRaCIS.Core.Application.Helper.OSSService.SetLifecycle(System.String,System.String)">
|
||||||
<summary>
|
<summary>
|
||||||
坑方法,会清空之前的规则
|
坑方法,会清空之前的规则
|
||||||
|
|
@ -16194,6 +16206,14 @@
|
||||||
<summary>
|
<summary>
|
||||||
当 AutoSelectGrayscaleMaskValue=false 时,使用该值。
|
当 AutoSelectGrayscaleMaskValue=false 时,使用该值。
|
||||||
当为 null 且 AutoSelectGrayscaleMaskValue=false 时,默认 0。
|
当为 null 且 AutoSelectGrayscaleMaskValue=false 时,默认 0。
|
||||||
|
灰度图像的遮挡像素值,默认 0。
|
||||||
|
注意:这表示写入的原始像素值,不保证视觉上一定为黑色。
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:IRaCIS.Core.Application.Helper.DicomMaskOptions.PaletteColorMaskIndex">
|
||||||
|
<summary>
|
||||||
|
PALETTE COLOR 图像使用的遮盖索引。
|
||||||
|
null 表示自动从调色板中选择最暗的一个索引。
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
<member name="M:IRaCIS.Core.Application.Helper.DicomPixelMasker.ValidateDataset(FellowOakDicom.DicomDataset)">
|
<member name="M:IRaCIS.Core.Application.Helper.DicomPixelMasker.ValidateDataset(FellowOakDicom.DicomDataset)">
|
||||||
|
|
|
||||||
|
|
@ -152,17 +152,20 @@ namespace IRaCIS.Core.Application.Service
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
Console.WriteLine($"开始处理: {relativePath}");
|
||||||
|
|
||||||
await DicomPixelMasker.MaskAsync(input, output, regions);
|
await DicomPixelMasker.MaskAsync(input, output, regions);
|
||||||
|
|
||||||
}
|
}
|
||||||
catch(Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// 跳过该文件
|
// 跳过该文件
|
||||||
|
Console.WriteLine();
|
||||||
Console.WriteLine($"error: {ex.Message}");
|
Console.WriteLine($"error: {relativePath} —————— {ex.Message}");
|
||||||
|
Console.WriteLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.WriteLine($"Done: {relativePath}");
|
Console.WriteLine($"处理结束: {relativePath}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return ResponseOutput.Ok();
|
return ResponseOutput.Ok();
|
||||||
|
|
@ -195,6 +198,18 @@ namespace IRaCIS.Core.Application.Service
|
||||||
return ResponseOutput.Ok();
|
return ResponseOutput.Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 归档直读
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
[AllowAnonymous]
|
||||||
|
public async Task<IResponseOutput> ConvertPrefixToStandard()
|
||||||
|
{
|
||||||
|
_IOSSService.ConvertPrefixToStandard("601a0000-3e2c-0016-56de-08dae983124b/Image");
|
||||||
|
|
||||||
|
return ResponseOutput.Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue