@@ -0,0 +1,193 @@
|
||||
|
||||
using FellowOakDicom.Imaging;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Formats.Jpeg;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
|
||||
namespace IRaCIS.Core.Application.Helper;
|
||||
|
||||
|
||||
public static class DicomSliceSorterFast
|
||||
{
|
||||
public static List<T> Sort<T>(
|
||||
IEnumerable<T> source,
|
||||
Func<T, string?> ippSelector,
|
||||
Func<T, string?> iopSelector,
|
||||
Func<T, int?> instanceSelector)
|
||||
{
|
||||
var items = source.ToList();
|
||||
if (items.Count < 2)
|
||||
return items;
|
||||
|
||||
var refIPP = Parse(ippSelector(items[0]));
|
||||
var refIOP = Parse(iopSelector(items[0]));
|
||||
|
||||
if (refIPP == null || refIOP == null || refIOP.Length != 6)
|
||||
return items.OrderBy(instanceSelector).ToList();
|
||||
|
||||
// normal
|
||||
var nx = refIOP[1] * refIOP[5] - refIOP[2] * refIOP[4];
|
||||
var ny = refIOP[2] * refIOP[3] - refIOP[0] * refIOP[5];
|
||||
var nz = refIOP[0] * refIOP[4] - refIOP[1] * refIOP[3];
|
||||
|
||||
var projections = new (T item, double dist)[items.Count];
|
||||
|
||||
double min = double.MaxValue;
|
||||
double max = double.MinValue;
|
||||
|
||||
// ---------- projection pass ----------
|
||||
for (int i = 0; i < items.Count; i++)
|
||||
{
|
||||
var ipp = Parse(ippSelector(items[i]));
|
||||
if (ipp == null)
|
||||
return items.OrderBy(instanceSelector).ToList();
|
||||
|
||||
var dx = refIPP[0] - ipp[0];
|
||||
var dy = refIPP[1] - ipp[1];
|
||||
var dz = refIPP[2] - ipp[2];
|
||||
|
||||
var dist = dx * nx + dy * ny + dz * nz;
|
||||
|
||||
projections[i] = (items[i], dist);
|
||||
|
||||
if (dist < min) min = dist;
|
||||
if (dist > max) max = dist;
|
||||
}
|
||||
|
||||
// ---------- estimate spacing ----------
|
||||
var spacing = (max - min) / (items.Count - 1);
|
||||
if (Math.Abs(spacing) < 1e-6)
|
||||
return items.OrderBy(instanceSelector).ToList();
|
||||
|
||||
var result = new T[items.Count];
|
||||
|
||||
// ---------- O(n) placement ----------
|
||||
foreach (var p in projections)
|
||||
{
|
||||
var index = (int)Math.Round((p.dist - min) / spacing);
|
||||
|
||||
index = Math.Clamp(index, 0, items.Count - 1);
|
||||
|
||||
// collision fallback(极少发生)
|
||||
while (result[index] != null!)
|
||||
index = Math.Min(index + 1, items.Count - 1);
|
||||
|
||||
result[index] = p.item;
|
||||
}
|
||||
|
||||
return result.ToList();
|
||||
}
|
||||
|
||||
private static double[]? Parse(string? value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
return null;
|
||||
|
||||
var s = value.Split('\\');
|
||||
var r = new double[s.Length];
|
||||
|
||||
for (int i = 0; i < s.Length; i++)
|
||||
if (!double.TryParse(s[i], out r[i]))
|
||||
return null;
|
||||
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
public static class DicomSortHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// DICOM Slice 排序(IPP + IOP)
|
||||
/// 自动 fallback InstanceNumber
|
||||
/// </summary>
|
||||
public static List<T> SortSlices<T>(
|
||||
IEnumerable<T> source,
|
||||
Func<T, string?> ippSelector,
|
||||
Func<T, string?> iopSelector,
|
||||
Func<T, int?> instanceNumberSelector)
|
||||
{
|
||||
var list = source.ToList();
|
||||
if (list.Count < 2)
|
||||
return list;
|
||||
|
||||
var first = list[0];
|
||||
|
||||
var reference = ParseVector(ippSelector(first));
|
||||
var iop = ParseVector(iopSelector(first));
|
||||
|
||||
// ===== fallback 条件 =====
|
||||
if (reference == null || iop == null || iop.Length != 6)
|
||||
return list.OrderBy(instanceNumberSelector).ToList();
|
||||
|
||||
// row / column direction
|
||||
var row = new[] { iop[0], iop[1], iop[2] };
|
||||
var col = new[] { iop[3], iop[4], iop[5] };
|
||||
|
||||
// normal = row × col
|
||||
var normal = new[]
|
||||
{
|
||||
row[1]*col[2] - row[2]*col[1],
|
||||
row[2]*col[0] - row[0]*col[2],
|
||||
row[0]*col[1] - row[1]*col[0]
|
||||
};
|
||||
|
||||
// 如果法向量异常 → fallback
|
||||
if (IsZeroVector(normal))
|
||||
return list.OrderBy(instanceNumberSelector).ToList();
|
||||
|
||||
// ===== 计算距离 =====
|
||||
var sorted = list
|
||||
.Select(item =>
|
||||
{
|
||||
var ipp = ParseVector(ippSelector(item));
|
||||
|
||||
if (ipp == null)
|
||||
return (item, distance: double.MinValue);
|
||||
|
||||
var vec0 = reference[0] - ipp[0];
|
||||
var vec1 = reference[1] - ipp[1];
|
||||
var vec2 = reference[2] - ipp[2];
|
||||
|
||||
var distance =
|
||||
vec0 * normal[0] +
|
||||
vec1 * normal[1] +
|
||||
vec2 * normal[2];
|
||||
|
||||
return (item, distance);
|
||||
})
|
||||
.OrderByDescending(x => x.distance)
|
||||
.Select(x => x.item)
|
||||
.ToList();
|
||||
|
||||
return sorted;
|
||||
}
|
||||
|
||||
// ---------------- helpers ----------------
|
||||
|
||||
private static double[]? ParseVector(string? value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
return null;
|
||||
|
||||
var parts = value.Split('\\');
|
||||
var result = new double[parts.Length];
|
||||
|
||||
for (int i = 0; i < parts.Length; i++)
|
||||
{
|
||||
if (!double.TryParse(parts[i], out result[i]))
|
||||
return null;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static bool IsZeroVector(double[] v)
|
||||
{
|
||||
const double eps = 1e-6;
|
||||
return Math.Abs(v[0]) < eps &&
|
||||
Math.Abs(v[1]) < eps &&
|
||||
Math.Abs(v[2]) < eps;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user