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 running
Details
continuous-integration/drone/push Build is running
Details
commit
be5c25ba5f
|
|
@ -196,72 +196,60 @@ public static class DicomPixelMasker
|
|||
{
|
||||
if (input == null) throw new ArgumentNullException(nameof(input));
|
||||
if (output == null) throw new ArgumentNullException(nameof(output));
|
||||
|
||||
var regionList = regions?.ToList() ?? throw new ArgumentNullException(nameof(regions));
|
||||
if (regionList.Count == 0)
|
||||
throw new ArgumentException("At least one mask region is required.", nameof(regions));
|
||||
options ??= new DicomMaskOptions();
|
||||
|
||||
if (input.CanSeek)
|
||||
input.Position = 0;
|
||||
|
||||
var originalFile = await DicomFile.OpenAsync(input, FileReadOption.ReadAll).ConfigureAwait(false);
|
||||
var originalDataset = originalFile.Dataset;
|
||||
|
||||
ValidateDataset(originalDataset);
|
||||
|
||||
var originalTs = originalFile.FileMetaInfo.TransferSyntax;
|
||||
// 先解压到工作格式
|
||||
var workingFile = await EnsureUncompressedAsync(originalFile, cancellationToken).ConfigureAwait(false);
|
||||
var workingDataset = workingFile.Dataset;
|
||||
|
||||
var originalPhotometric = originalDataset.GetSingleValueOrDefault(DicomTag.PhotometricInterpretation, string.Empty);
|
||||
Console.WriteLine($"Original Photometric={originalPhotometric}, Original TS={originalTs.UID.UID}");
|
||||
|
||||
var rows = originalDataset.GetSingleValue<int>(DicomTag.Rows);
|
||||
var cols = originalDataset.GetSingleValue<int>(DicomTag.Columns);
|
||||
var bitsAllocated = originalDataset.GetSingleValue<int>(DicomTag.BitsAllocated);
|
||||
var bitsStored = originalDataset.GetSingleValueOrDefault(DicomTag.BitsStored, bitsAllocated);
|
||||
var samplesPerPixel = originalDataset.GetSingleValue<int>(DicomTag.SamplesPerPixel);
|
||||
var pixelRepresentation = originalDataset.GetSingleValueOrDefault(DicomTag.PixelRepresentation, (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}");
|
||||
var rows = workingDataset.GetSingleValue<int>(DicomTag.Rows);
|
||||
var cols = workingDataset.GetSingleValue<int>(DicomTag.Columns);
|
||||
var bitsAllocated = workingDataset.GetSingleValue<int>(DicomTag.BitsAllocated);
|
||||
var bitsStored = workingDataset.GetSingleValueOrDefault(DicomTag.BitsStored, bitsAllocated);
|
||||
var samplesPerPixel = workingDataset.GetSingleValue<int>(DicomTag.SamplesPerPixel);
|
||||
var pixelRepresentation = workingDataset.GetSingleValueOrDefault(DicomTag.PixelRepresentation, (ushort)0);
|
||||
var planarConfiguration = workingDataset.GetSingleValueOrDefault(DicomTag.PlanarConfiguration, (ushort)0);
|
||||
var workingPhotometric = workingDataset.GetSingleValueOrDefault(DicomTag.PhotometricInterpretation, string.Empty);
|
||||
|
||||
var isSupport = IsSupportedPhotometric(originalPhotometric, samplesPerPixel);
|
||||
Console.WriteLine($"Working Photometric={workingPhotometric}, Working TS={workingFile.FileMetaInfo.TransferSyntax.UID.UID}");
|
||||
|
||||
if (isSupport)
|
||||
Console.WriteLine($"Working Rows={rows}, Cols={cols}, BitsAllocated={bitsAllocated}, BitsStored={bitsStored}, SamplesPerPixel={samplesPerPixel}, PixelRepresentation={pixelRepresentation}, PlanarConfiguration={planarConfiguration}, Photometric={workingPhotometric}, TransferSyntax={workingFile.FileMetaInfo.TransferSyntax.UID.UID}");
|
||||
var isSupport = IsSupportedPhotometric(workingPhotometric, samplesPerPixel);
|
||||
if (!isSupport)
|
||||
{
|
||||
// 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;
|
||||
throw new NotSupportedException(
|
||||
$"Unsupported PhotometricInterpretation after decode: {workingPhotometric}, SamplesPerPixel={samplesPerPixel}");
|
||||
}
|
||||
else
|
||||
// 修改 working dataset 的像素
|
||||
MaskPixelDataInPlace(workingDataset, regionList, options);
|
||||
if (options.UpdateBurnedInAnnotationToNo)
|
||||
{
|
||||
|
||||
workingDataset.AddOrUpdate(DicomTag.BurnedInAnnotation, "NO");
|
||||
}
|
||||
|
||||
// 不要把 original photometric 强行写回
|
||||
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;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -285,35 +273,21 @@ public static class DicomPixelMasker
|
|||
{
|
||||
if (samplesPerPixel == 1)
|
||||
{
|
||||
if (string.Equals(photometric, "MONOCHROME1", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(photometric, "MONOCHROME2", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(photometric, "PALETTE COLOR", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return string.Equals(photometric, "MONOCHROME1", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(photometric, "MONOCHROME2", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(photometric, "PALETTE COLOR", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
if (samplesPerPixel == 3)
|
||||
{
|
||||
if (string.Equals(photometric, "RGB", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(photometric, "YBR_FULL", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// 对 YBR_FULL_422 不建议直接按当前布局改
|
||||
if (string.Equals(photometric, "YBR_FULL_422", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(photometric, "YBR_PARTIAL_422", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(photometric, "YBR_PARTIAL_420", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
$"PhotometricInterpretation={photometric} uses subsampling layout and is not safely supported by this pixel masking implementation.");
|
||||
}
|
||||
|
||||
return false;
|
||||
//throw new NotSupportedException($"Unsupported color PhotometricInterpretation: {photometric}");
|
||||
return string.Equals(photometric, "RGB", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(photometric, "YBR_FULL", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(photometric, "YBR_FULL_422", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(photometric, "YBR_PARTIAL_422", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(photometric, "YBR_PARTIAL_420", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
return false;
|
||||
//throw new NotSupportedException($"Unsupported SamplesPerPixel={samplesPerPixel}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -491,13 +465,13 @@ public static class DicomPixelMasker
|
|||
if (bitsAllocated == 8)
|
||||
{
|
||||
byte maskIndex = (byte)ResolvePaletteColorMaskIndex(dataset, bitsStored, options);
|
||||
ApplyMask_Grayscale8(frameData, rows, cols, regions, maskIndex);
|
||||
ApplyMask_SingleSample8(frameData, rows, cols, regions, maskIndex);
|
||||
return;
|
||||
}
|
||||
if (bitsAllocated == 16)
|
||||
{
|
||||
ushort maskIndex = ResolvePaletteColorMaskIndex(dataset, bitsStored, options);
|
||||
ApplyMask_Grayscale16(frameData, rows, cols, regions, maskIndex);
|
||||
ApplyMask_SingleSample16(frameData, rows, cols, regions, maskIndex);
|
||||
return;
|
||||
}
|
||||
throw new NotSupportedException(
|
||||
|
|
@ -508,7 +482,7 @@ public static class DicomPixelMasker
|
|||
if (bitsAllocated == 8)
|
||||
{
|
||||
byte maskValue8 = ResolveGrayscaleMaskValue8(photometric, options);
|
||||
ApplyMask_Grayscale8(frameData, rows, cols, regions, maskValue8);
|
||||
ApplyMask_SingleSample8(frameData, rows, cols, regions, maskValue8);
|
||||
return;
|
||||
}
|
||||
if (bitsAllocated == 16)
|
||||
|
|
@ -518,7 +492,7 @@ public static class DicomPixelMasker
|
|||
bitsStored,
|
||||
pixelRepresentation,
|
||||
options);
|
||||
ApplyMask_Grayscale16(frameData, rows, cols, regions, maskValue16);
|
||||
ApplyMask_SingleSample16(frameData, rows, cols, regions, maskValue16);
|
||||
return;
|
||||
}
|
||||
throw new NotSupportedException(
|
||||
|
|
@ -534,22 +508,268 @@ public static class DicomPixelMasker
|
|||
throw new NotSupportedException(
|
||||
$"Unsupported color image: SamplesPerPixel=3, BitsAllocated={bitsAllocated}, Photometric={photometric}");
|
||||
}
|
||||
if (planarConfiguration == 0)
|
||||
int expectedFull = rows * cols * 3;
|
||||
int expected422 = rows * cols * 2;
|
||||
int chromaRows = (rows + 1) / 2;
|
||||
int chromaCols = (cols + 1) / 2;
|
||||
int expected420 = rows * cols + 2 * chromaRows * chromaCols;
|
||||
// 根据 photometric 自动解析默认颜色
|
||||
var colorMask = ResolveColorMaskValue(photometric, options);
|
||||
// 1) 先处理能通过长度明确识别的 subsampled YBR
|
||||
if (frameData.Length == expected422)
|
||||
{
|
||||
ApplyMask_Color8_Interleaved(frameData, rows, cols, regions, options.ColorMaskValue);
|
||||
if (string.Equals(photometric, "YBR_FULL_422", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ApplyMask_YbrFull422(frameData, rows, cols, regions);
|
||||
return;
|
||||
}
|
||||
if (string.Equals(photometric, "YBR_PARTIAL_422", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ApplyMask_YbrPartial422(frameData, rows, cols, regions);
|
||||
return;
|
||||
}
|
||||
throw new NotSupportedException(
|
||||
$"Frame length matches 4:2:2 layout, but Photometric={photometric} is not supported for 422 masking.");
|
||||
}
|
||||
if (frameData.Length == expected420)
|
||||
{
|
||||
if (string.Equals(photometric, "YBR_PARTIAL_420", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ApplyMask_YbrPartial420(frameData, rows, cols, regions);
|
||||
return;
|
||||
}
|
||||
throw new NotSupportedException(
|
||||
$"Frame length matches 4:2:0 layout, but Photometric={photometric} is not supported for 420 masking.");
|
||||
}
|
||||
// 2) full-resolution 三通道数据,长度无法区分 planar / interleaved
|
||||
// 必须依赖 PlanarConfiguration
|
||||
if (frameData.Length == expectedFull)
|
||||
{
|
||||
if (planarConfiguration == 1)
|
||||
{
|
||||
ApplyMask_Color8_Planar(frameData, rows, cols, regions, colorMask);
|
||||
return;
|
||||
}
|
||||
// 包括 planarConfiguration == 0 以及很多解码后默认输出
|
||||
ApplyMask_Color8_Interleaved(frameData, rows, cols, regions, colorMask);
|
||||
return;
|
||||
}
|
||||
// 3) 最后兜底:如果长度异常,尝试按 PlanarConfiguration 处理
|
||||
if (planarConfiguration == 1)
|
||||
{
|
||||
ApplyMask_Color8_Planar(frameData, rows, cols, regions, options.ColorMaskValue);
|
||||
ApplyMask_Color8_Planar(frameData, rows, cols, regions, colorMask);
|
||||
return;
|
||||
}
|
||||
if (planarConfiguration == 0)
|
||||
{
|
||||
ApplyMask_Color8_Interleaved(frameData, rows, cols, regions, colorMask);
|
||||
return;
|
||||
}
|
||||
throw new NotSupportedException(
|
||||
$"Unsupported PlanarConfiguration={planarConfiguration}, Photometric={photometric}");
|
||||
$"Unsupported color frame layout: SamplesPerPixel={samplesPerPixel}, BitsAllocated={bitsAllocated}, " +
|
||||
$"Photometric={photometric}, PlanarConfiguration={planarConfiguration}, FrameLength={frameData.Length}");
|
||||
}
|
||||
throw new NotSupportedException(
|
||||
$"Unsupported format: SamplesPerPixel={samplesPerPixel}, BitsAllocated={bitsAllocated}, Photometric={photometric}");
|
||||
}
|
||||
|
||||
private static byte[] ResolveColorMaskValue(string photometric, DicomMaskOptions options)
|
||||
{
|
||||
if (options.ColorMaskValue != null && options.ColorMaskValue.Length >= 3)
|
||||
return options.ColorMaskValue;
|
||||
|
||||
if (string.Equals(photometric, "YBR_FULL", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(photometric, "YBR_FULL_422", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// 黑色 in YCbCr full range
|
||||
return new byte[] { 0, 128, 128 };
|
||||
}
|
||||
|
||||
if (string.Equals(photometric, "YBR_PARTIAL_422", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(photometric, "YBR_PARTIAL_420", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// 黑色 in YCbCr video range
|
||||
return new byte[] { 16, 128, 128 };
|
||||
}
|
||||
|
||||
// RGB 默认黑
|
||||
return new byte[] { 0, 0, 0 };
|
||||
}
|
||||
|
||||
#region ybr 422...
|
||||
private static void ApplyMask_YbrFull422(
|
||||
byte[] frameData,
|
||||
int rows,
|
||||
int cols,
|
||||
IReadOnlyList<MaskRegion> regions)
|
||||
{
|
||||
const byte maskY = 0;
|
||||
const byte maskCb = 128;
|
||||
const byte maskCr = 128;
|
||||
|
||||
int bytesPerRow = cols * 2; // 2 pixels -> 4 bytes
|
||||
|
||||
foreach (var region in regions)
|
||||
{
|
||||
var (left, top, right, bottom) = ClipRegion(region, cols, rows);
|
||||
if (left >= right || top >= bottom)
|
||||
continue;
|
||||
|
||||
int alignedLeft = left & ~1;
|
||||
int alignedRight = (right + 1) & ~1;
|
||||
if (alignedRight > cols) alignedRight = cols;
|
||||
|
||||
for (int y = top; y < bottom; y++)
|
||||
{
|
||||
int rowOffset = y * bytesPerRow;
|
||||
|
||||
for (int x = alignedLeft; x < alignedRight; x += 2)
|
||||
{
|
||||
int offset = rowOffset + (x / 2) * 4;
|
||||
|
||||
// Y0 Cb Y1 Cr
|
||||
frameData[offset + 0] = maskY;
|
||||
frameData[offset + 1] = maskCb;
|
||||
frameData[offset + 2] = maskY;
|
||||
frameData[offset + 3] = maskCr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyMask_YbrPartial422(
|
||||
byte[] frameData,
|
||||
int rows,
|
||||
int cols,
|
||||
IReadOnlyList<MaskRegion> regions)
|
||||
{
|
||||
const byte maskY = 16;
|
||||
const byte maskCb = 128;
|
||||
const byte maskCr = 128;
|
||||
|
||||
int bytesPerRow = cols * 2; // 2 pixels -> 4 bytes
|
||||
|
||||
foreach (var region in regions)
|
||||
{
|
||||
var (left, top, right, bottom) = ClipRegion(region, cols, rows);
|
||||
if (left >= right || top >= bottom)
|
||||
continue;
|
||||
|
||||
int alignedLeft = left & ~1;
|
||||
int alignedRight = (right + 1) & ~1;
|
||||
if (alignedRight > cols) alignedRight = cols;
|
||||
|
||||
for (int y = top; y < bottom; y++)
|
||||
{
|
||||
int rowOffset = y * bytesPerRow;
|
||||
|
||||
for (int x = alignedLeft; x < alignedRight; x += 2)
|
||||
{
|
||||
int offset = rowOffset + (x / 2) * 4;
|
||||
|
||||
// Y0 Cb Y1 Cr
|
||||
frameData[offset + 0] = maskY;
|
||||
frameData[offset + 1] = maskCb;
|
||||
frameData[offset + 2] = maskY;
|
||||
frameData[offset + 3] = maskCr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyMask_YbrPartial420(
|
||||
byte[] frameData,
|
||||
int rows,
|
||||
int cols,
|
||||
IReadOnlyList<MaskRegion> regions)
|
||||
{
|
||||
const byte maskY = 16;
|
||||
const byte maskCb = 128;
|
||||
const byte maskCr = 128;
|
||||
|
||||
int yPlaneSize = rows * cols;
|
||||
int chromaRows = (rows + 1) / 2;
|
||||
int chromaCols = (cols + 1) / 2;
|
||||
int chromaPlaneSize = chromaRows * chromaCols;
|
||||
|
||||
int cbBase = yPlaneSize;
|
||||
int crBase = yPlaneSize + chromaPlaneSize;
|
||||
|
||||
foreach (var region in regions)
|
||||
{
|
||||
var (left, top, right, bottom) = ClipRegion(region, cols, rows);
|
||||
if (left >= right || top >= bottom)
|
||||
continue;
|
||||
|
||||
int alignedLeft = left & ~1;
|
||||
int alignedTop = top & ~1;
|
||||
int alignedRight = (right + 1) & ~1;
|
||||
int alignedBottom = (bottom + 1) & ~1;
|
||||
|
||||
if (alignedRight > cols) alignedRight = cols;
|
||||
if (alignedBottom > rows) alignedBottom = rows;
|
||||
|
||||
// Y plane
|
||||
for (int y = top; y < bottom; y++)
|
||||
{
|
||||
int rowOffset = y * cols;
|
||||
for (int x = left; x < right; x++)
|
||||
{
|
||||
frameData[rowOffset + x] = maskY;
|
||||
}
|
||||
}
|
||||
|
||||
// Cb / Cr plane, one chroma sample for each 2x2 block
|
||||
for (int y = alignedTop; y < alignedBottom; y += 2)
|
||||
{
|
||||
int chromaY = y / 2;
|
||||
|
||||
for (int x = alignedLeft; x < alignedRight; x += 2)
|
||||
{
|
||||
int chromaX = x / 2;
|
||||
int chromaIndex = chromaY * chromaCols + chromaX;
|
||||
|
||||
frameData[cbBase + chromaIndex] = maskCb;
|
||||
frameData[crBase + chromaIndex] = maskCr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static bool TryApplyMaskForSubsampledYbr(
|
||||
byte[] frameData,
|
||||
int rows,
|
||||
int cols,
|
||||
string photometric,
|
||||
IReadOnlyList<MaskRegion> regions)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(photometric))
|
||||
return false;
|
||||
|
||||
if (string.Equals(photometric, "YBR_FULL_422", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ApplyMask_YbrFull422(frameData, rows, cols, regions);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (string.Equals(photometric, "YBR_PARTIAL_422", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ApplyMask_YbrPartial422(frameData, rows, cols, regions);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (string.Equals(photometric, "YBR_PARTIAL_420", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ApplyMask_YbrPartial420(frameData, rows, cols, regions);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private static ushort ResolvePaletteColorMaskIndex(
|
||||
DicomDataset dataset,
|
||||
int bitsStored,
|
||||
|
|
@ -664,162 +884,6 @@ public static class DicomPixelMasker
|
|||
|
||||
|
||||
|
||||
|
||||
#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)
|
||||
|
|
@ -864,7 +928,7 @@ public static class DicomPixelMasker
|
|||
return unchecked((ushort)selected);
|
||||
}
|
||||
}
|
||||
private static void ApplyMask_Grayscale8(
|
||||
private static void ApplyMask_SingleSample8(
|
||||
byte[] data,
|
||||
int rows,
|
||||
int cols,
|
||||
|
|
@ -885,7 +949,7 @@ public static class DicomPixelMasker
|
|||
}
|
||||
}
|
||||
}
|
||||
private static void ApplyMask_Grayscale16(
|
||||
private static void ApplyMask_SingleSample16(
|
||||
byte[] data,
|
||||
int rows,
|
||||
int cols,
|
||||
|
|
|
|||
|
|
@ -17242,17 +17242,17 @@
|
|||
</member>
|
||||
<member name="F:IRaCIS.Core.Application.ViewModel.AccessToDialogueEnum.Question">
|
||||
<summary>
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
质疑
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:IRaCIS.Core.Application.ViewModel.AccessToDialogueEnum.Consistency">
|
||||
<summary>
|
||||
һ<EFBFBD><EFBFBD><EFBFBD>Ժ˲<EFBFBD>
|
||||
一致性核查
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:IRaCIS.Core.Application.ViewModel.CopyFrontAuditConfigItemDto">
|
||||
<summary>
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
复制
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:IRaCIS.Core.Application.ViewModel.SystemNoticeView">
|
||||
|
|
|
|||
Loading…
Reference in New Issue