伪彩图像解析遮盖
parent
736266d8e8
commit
5fd2f782c2
|
|
@ -204,6 +204,8 @@ public interface IOSSService
|
|||
public Task<long> GetObjectSizeAsync(string sourcePath);
|
||||
|
||||
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 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>
|
||||
|
|
@ -1507,20 +1594,20 @@ public class OSSService(IOptionsMonitor<ObjectStoreServiceOptions> options,
|
|||
|
||||
GetObjectStoreTempToken(objectUse: config.Primary);
|
||||
|
||||
await DeleteFromPrefixInternal(config.Primary,prefix, isCache);
|
||||
await DeleteFromPrefixInternal(config.Primary, prefix, isCache);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
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")
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using FellowOakDicom.IO.Buffer;
|
|||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Formats.Jpeg;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using System.Data;
|
||||
|
||||
namespace IRaCIS.Core.Application.Helper;
|
||||
|
||||
|
|
@ -158,8 +159,16 @@ public sealed class DicomMaskOptions
|
|||
/// <summary>
|
||||
/// 当 AutoSelectGrayscaleMaskValue=false 时,使用该值。
|
||||
/// 当为 null 且 AutoSelectGrayscaleMaskValue=false 时,默认 0。
|
||||
/// 灰度图像的遮挡像素值,默认 0。
|
||||
/// 注意:这表示写入的原始像素值,不保证视觉上一定为黑色。
|
||||
/// </summary>
|
||||
public int? GrayscaleMaskValue { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// PALETTE COLOR 图像使用的遮盖索引。
|
||||
/// null 表示自动从调色板中选择最暗的一个索引。
|
||||
/// </summary>
|
||||
public int? PaletteColorMaskIndex { get; init; }
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -213,38 +222,46 @@ public static class DicomPixelMasker
|
|||
var planarConfiguration = originalDataset.GetSingleValueOrDefault(DicomTag.PlanarConfiguration, (ushort)0);
|
||||
Console.WriteLine($"Rows={rows}, Cols={cols}, BitsAllocated={bitsAllocated}, BitsStored={bitsStored}, SamplesPerPixel={samplesPerPixel}, PixelRepresentation={pixelRepresentation}, PlanarConfiguration={planarConfiguration}");
|
||||
|
||||
EnsureSupportedPhotometric(originalPhotometric, samplesPerPixel);
|
||||
var isSupport = IsSupportedPhotometric(originalPhotometric, samplesPerPixel);
|
||||
|
||||
// 1. 转为工作用的未压缩 DICOM
|
||||
var workingFile = await EnsureUncompressedAsync(originalFile, cancellationToken).ConfigureAwait(false);
|
||||
// 2. 修改像素
|
||||
MaskPixelDataInPlace(workingFile.Dataset, regionList, options);
|
||||
// 3. 保持原 PhotometricInterpretation
|
||||
if (!string.IsNullOrWhiteSpace(originalPhotometric))
|
||||
if (isSupport)
|
||||
{
|
||||
workingFile.Dataset.AddOrUpdate(DicomTag.PhotometricInterpretation, originalPhotometric);
|
||||
// 1. 转为工作用的未压缩 DICOM
|
||||
var workingFile = await EnsureUncompressedAsync(originalFile, cancellationToken).ConfigureAwait(false);
|
||||
// 2. 修改像素
|
||||
MaskPixelDataInPlace(workingFile.Dataset, regionList, options);
|
||||
// 3. 保持原 PhotometricInterpretation
|
||||
if (!string.IsNullOrWhiteSpace(originalPhotometric))
|
||||
{
|
||||
workingFile.Dataset.AddOrUpdate(DicomTag.PhotometricInterpretation, originalPhotometric);
|
||||
}
|
||||
// 4. 可选更新 BurnedInAnnotation
|
||||
if (options.UpdateBurnedInAnnotationToNo)
|
||||
{
|
||||
workingFile.Dataset.AddOrUpdate(DicomTag.BurnedInAnnotation, "NO");
|
||||
}
|
||||
// 5. 编码回原始传输语法
|
||||
var finalFile = await ReEncodeToOriginalTransferSyntaxAsync(
|
||||
workingFile,
|
||||
originalTs,
|
||||
options.StrictKeepTransferSyntax,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
finalFile.FileMetaInfo.TransferSyntax = originalTs;
|
||||
|
||||
if (output.CanSeek)
|
||||
output.SetLength(0);
|
||||
|
||||
await finalFile.SaveAsync(output).ConfigureAwait(false);
|
||||
|
||||
if (output.CanSeek)
|
||||
output.Position = 0;
|
||||
}
|
||||
// 4. 可选更新 BurnedInAnnotation
|
||||
if (options.UpdateBurnedInAnnotationToNo)
|
||||
else
|
||||
{
|
||||
workingFile.Dataset.AddOrUpdate(DicomTag.BurnedInAnnotation, "NO");
|
||||
|
||||
}
|
||||
// 5. 编码回原始传输语法
|
||||
var finalFile = await ReEncodeToOriginalTransferSyntaxAsync(
|
||||
workingFile,
|
||||
originalTs,
|
||||
options.StrictKeepTransferSyntax,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
finalFile.FileMetaInfo.TransferSyntax = originalTs;
|
||||
|
||||
if (output.CanSeek)
|
||||
output.SetLength(0);
|
||||
|
||||
await finalFile.SaveAsync(output).ConfigureAwait(false);
|
||||
|
||||
if (output.CanSeek)
|
||||
output.Position = 0;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -264,23 +281,24 @@ public static class DicomPixelMasker
|
|||
_ = 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 (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 (string.Equals(photometric, "RGB", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(photometric, "YBR_FULL", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
// 对 YBR_FULL_422 不建议直接按当前布局改
|
||||
if (string.Equals(photometric, "YBR_FULL_422", StringComparison.OrdinalIgnoreCase) ||
|
||||
|
|
@ -290,9 +308,12 @@ public static class DicomPixelMasker
|
|||
throw new NotSupportedException(
|
||||
$"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>
|
||||
|
|
@ -402,7 +423,7 @@ public static class DicomPixelMasker
|
|||
photometric: photometric,
|
||||
planarConfiguration: planarConfiguration,
|
||||
regions: regions,
|
||||
options: options);
|
||||
options: options, dataset);
|
||||
}
|
||||
replacementFrames.Add(new MemoryByteBuffer(bytes));
|
||||
}
|
||||
|
|
@ -455,28 +476,56 @@ public static class DicomPixelMasker
|
|||
string photometric,
|
||||
ushort planarConfiguration,
|
||||
IReadOnlyList<MaskRegion> regions,
|
||||
DicomMaskOptions options)
|
||||
DicomMaskOptions options,
|
||||
DicomDataset dataset)
|
||||
{
|
||||
if (samplesPerPixel == 1)
|
||||
{
|
||||
if (bitsAllocated == 8)
|
||||
|
||||
if (string.Equals(photometric, "PALETTE COLOR", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
byte maskValue8 = ResolveGrayscaleMaskValue8(photometric, options);
|
||||
ApplyMask_Grayscale8(frameData, rows, cols, regions, maskValue8);
|
||||
return;
|
||||
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}");
|
||||
}
|
||||
if (bitsAllocated == 16)
|
||||
else
|
||||
{
|
||||
ushort maskValue16 = ResolveGrayscaleMaskValue16(
|
||||
photometric,
|
||||
bitsStored,
|
||||
pixelRepresentation,
|
||||
options);
|
||||
ApplyMask_Grayscale16(frameData, rows, cols, regions, maskValue16);
|
||||
return;
|
||||
if (bitsAllocated == 8)
|
||||
{
|
||||
byte maskValue8 = ResolveGrayscaleMaskValue8(photometric, options);
|
||||
ApplyMask_Grayscale8(frameData, rows, cols, regions, maskValue8);
|
||||
return;
|
||||
}
|
||||
if (bitsAllocated == 16)
|
||||
{
|
||||
ushort maskValue16 = ResolveGrayscaleMaskValue16(
|
||||
photometric,
|
||||
bitsStored,
|
||||
pixelRepresentation,
|
||||
options);
|
||||
ApplyMask_Grayscale16(frameData, rows, cols, regions, maskValue16);
|
||||
return;
|
||||
}
|
||||
throw new NotSupportedException(
|
||||
$"Unsupported grayscale image: BitsAllocated={bitsAllocated}, Photometric={photometric}");
|
||||
}
|
||||
throw new NotSupportedException(
|
||||
$"Unsupported grayscale image: BitsAllocated={bitsAllocated}, Photometric={photometric}");
|
||||
|
||||
|
||||
}
|
||||
if (samplesPerPixel == 3)
|
||||
{
|
||||
|
|
@ -501,6 +550,276 @@ public static class DicomPixelMasker
|
|||
throw new NotSupportedException(
|
||||
$"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)
|
||||
{
|
||||
if (!options.AutoSelectGrayscaleMaskValue)
|
||||
|
|
|
|||
|
|
@ -15917,6 +15917,12 @@
|
|||
<param name="modelVerify"></param>
|
||||
<returns></returns>
|
||||
</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})">
|
||||
<summary>
|
||||
测试疗效评估
|
||||
|
|
@ -16100,6 +16106,12 @@
|
|||
<param name="isDelete">默认是添加/更新 </param>
|
||||
</member>
|
||||
<!-- 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)">
|
||||
<summary>
|
||||
坑方法,会清空之前的规则
|
||||
|
|
@ -16184,6 +16196,14 @@
|
|||
<summary>
|
||||
当 AutoSelectGrayscaleMaskValue=false 时,使用该值。
|
||||
当为 null 且 AutoSelectGrayscaleMaskValue=false 时,默认 0。
|
||||
灰度图像的遮挡像素值,默认 0。
|
||||
注意:这表示写入的原始像素值,不保证视觉上一定为黑色。
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:IRaCIS.Core.Application.Helper.DicomMaskOptions.PaletteColorMaskIndex">
|
||||
<summary>
|
||||
PALETTE COLOR 图像使用的遮盖索引。
|
||||
null 表示自动从调色板中选择最暗的一个索引。
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:IRaCIS.Core.Application.Helper.DicomPixelMasker.ValidateDataset(FellowOakDicom.DicomDataset)">
|
||||
|
|
|
|||
|
|
@ -152,17 +152,20 @@ namespace IRaCIS.Core.Application.Service
|
|||
|
||||
try
|
||||
{
|
||||
Console.WriteLine($"开始处理: {relativePath}");
|
||||
|
||||
await DicomPixelMasker.MaskAsync(input, output, regions);
|
||||
|
||||
}
|
||||
catch(Exception ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 跳过该文件
|
||||
|
||||
Console.WriteLine($"error: {ex.Message}");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine($"error: {relativePath} —————— {ex.Message}");
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
Console.WriteLine($"Done: {relativePath}");
|
||||
Console.WriteLine($"处理结束: {relativePath}");
|
||||
}
|
||||
|
||||
return ResponseOutput.Ok();
|
||||
|
|
@ -195,6 +198,18 @@ namespace IRaCIS.Core.Application.Service
|
|||
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]
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue