影像阅片排序换到后端做
continuous-integration/drone/push Build is passing

This commit is contained in:
2026-03-26 15:21:46 +08:00
parent 3f9e506d09
commit 52f49994ab
4 changed files with 253 additions and 8 deletions
@@ -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;
}
}