Skip to content

Commit b9fa212

Browse files
committed
[PixImage] Implement processor API
1 parent 9ff0da9 commit b9fa212

File tree

2 files changed

+380
-27
lines changed

2 files changed

+380
-27
lines changed

src/Aardvark.Base.Tensors.CSharp/PixImage/PixImage.cs

+200-27
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,140 @@ internal static void Ignore<T>(T _) { }
328328

329329
#endregion
330330

331+
#region Processors
332+
333+
private static readonly Dictionary<IPixProcessor, int> s_processors = new()
334+
{
335+
{ PixProcessor.Instance, 1 }
336+
};
337+
338+
/// <summary>
339+
/// Sets the priority of a PixImage processor.
340+
/// The priority determines the order in which proocessors are invoked to scale, rotate, or remap an image.
341+
/// Processors with higher priority are invoked first.
342+
/// If the processor does not exist, it is added with the given priority.
343+
/// </summary>
344+
/// <param name="processor">The processor to modify.</param>
345+
/// <param name="priority">The priority to set.</param>
346+
public static void SetProcessor(IPixProcessor processor, int priority)
347+
{
348+
lock (s_processors)
349+
{
350+
s_processors[processor] = priority;
351+
}
352+
}
353+
354+
/// <summary>
355+
/// Adds a PixImage processor.
356+
/// Assigns a priority that is greater than the highest priority among existing processors, resulting in a LIFO order.
357+
/// If the processor already exists, the priority is modified.
358+
/// </summary>
359+
/// <param name="processor">The processor to add.</param>
360+
public static void AddProcessor(IPixProcessor processor)
361+
{
362+
lock (s_processors)
363+
{
364+
var maxPriority = s_processors.Values.Max(-1);
365+
s_processors[processor] = maxPriority + 1;
366+
}
367+
}
368+
369+
/// <summary>
370+
/// Removes a PixImage processor.
371+
/// </summary>
372+
/// <param name="processor">The processor to remove.</param>
373+
public static void RemoveProcessor(IPixProcessor processor)
374+
{
375+
lock (s_processors) { s_processors.Remove(processor); }
376+
}
377+
378+
/// <summary>
379+
/// Gets a dictionary of registered processors with their associated priority.
380+
/// Only returns processors that have at least the given minimum capabilities.
381+
/// </summary>
382+
/// <param name="minCapabilities">The minimum capabilities for a processor to be considered.</param>
383+
/// <returns>A dictionary of registered processors.</returns>
384+
public static Dictionary<IPixProcessor, int> GetProcessorsWithPriority(PixProcessorCaps minCapabilities = PixProcessorCaps.None)
385+
{
386+
lock (s_processors)
387+
{
388+
var result = new Dictionary<IPixProcessor, int>();
389+
390+
foreach (var p in s_processors)
391+
{
392+
if (p.Key.Capabilities.HasFlag(minCapabilities))
393+
result[p.Key] = p.Value;
394+
}
395+
396+
return result;
397+
}
398+
}
399+
400+
/// <summary>
401+
/// Gets a list of registered processors sorted by priority in descending order.
402+
/// Only returns processors that have at least the given minimum capabilities.
403+
/// </summary>
404+
/// <param name="minCapabilities">The minimum capabilities for a processor to be considered.</param>
405+
/// <returns>A list of registered processors.</returns>
406+
public static List<IPixProcessor> GetProcessors(PixProcessorCaps minCapabilities = PixProcessorCaps.None)
407+
{
408+
lock (s_processors)
409+
{
410+
var list = new List<KeyValuePair<IPixProcessor, int>>();
411+
412+
foreach (var p in s_processors)
413+
{
414+
if (p.Key.Capabilities.HasFlag(minCapabilities))
415+
list.Add(p);
416+
}
417+
418+
list.Sort((x, y) => y.Value - x.Value);
419+
return list.Map(x => x.Key);
420+
}
421+
}
422+
423+
internal static PixImage<T> InvokeProcessors<T>(
424+
Func<IPixProcessor, PixImage<T>> invoke,
425+
PixProcessorCaps minCapabilities,
426+
string operationDescription)
427+
{
428+
PixImage<T> result;
429+
430+
foreach (var p in GetProcessors(minCapabilities))
431+
{
432+
try
433+
{
434+
result = invoke(p);
435+
if (result != null) return result;
436+
}
437+
catch (Exception e)
438+
{
439+
Report.Warn($"Failed to {operationDescription} with {p.Name} image processor: {e.Message}");
440+
}
441+
}
442+
443+
var processors = GetProcessors(PixProcessorCaps.None);
444+
var errorMessage = $"Cannot {operationDescription}";
445+
446+
if (processors.Count == 0)
447+
{
448+
errorMessage += ", no image processors available!";
449+
}
450+
else
451+
{
452+
errorMessage += ", available image processors:" + Environment.NewLine;
453+
454+
foreach (var p in processors)
455+
{
456+
errorMessage += $" - {p.Name}: {p.Capabilities}" + Environment.NewLine;
457+
}
458+
}
459+
460+
throw new NotSupportedException(errorMessage);
461+
}
462+
463+
#endregion
464+
331465
#region Constructors
332466

333467
static PixImage()
@@ -1333,29 +1467,45 @@ public IEnumerable<Matrix<T>> Channels
13331467

13341468
#region Image Manipulation
13351469

1470+
#region Transformed
1471+
13361472
public override PixImage TransformedPixImage(ImageTrafo trafo)
13371473
=> Transformed(trafo);
13381474

13391475
public PixImage<T> Transformed(ImageTrafo trafo)
13401476
=> new PixImage<T>(Format, Volume.Transformed(trafo));
13411477

1478+
#endregion
1479+
1480+
#region Remapped
1481+
13421482
public override PixImage RemappedPixImage(Matrix<float> xMap, Matrix<float> yMap, ImageInterpolation ip = ImageInterpolation.Cubic)
13431483
=> Remapped(xMap, xMap, ip);
13441484

13451485
public PixImage<T> Remapped(Matrix<float> xMap, Matrix<float> yMap, ImageInterpolation ip = ImageInterpolation.Cubic)
13461486
{
1347-
if (s_remappedFun == null)
1348-
{
1349-
throw new NotSupportedException($"No remapping function has been installed via PixImage<{(typeof(T).Name)}>.SetRemappedFun");
1350-
}
1487+
return InvokeProcessors(
1488+
(p) => p.Remap(this, xMap, yMap, ip, default),
1489+
PixProcessorCaps.Remap, "remap image"
1490+
);
1491+
}
13511492

1352-
return new PixImage<T>(Format, s_remappedFun(Volume, xMap, yMap, ip));
1493+
[Obsolete("Use the PixImage processor API instead.")]
1494+
public static void SetRemappedFun(Func<Volume<T>, Matrix<float>, Matrix<float>, ImageInterpolation, Volume<T>> remappedFun)
1495+
{
1496+
LegacyPixProcessor.Instance.SetRemapFun<T>(
1497+
(remappedFun == null) ? null : (pi, xMap, yMap, ip) => new (pi.Format, remappedFun(pi.Volume, xMap, yMap, ip))
1498+
);
1499+
1500+
if (LegacyPixProcessor.Instance.Capabilities != PixProcessorCaps.None)
1501+
SetProcessor(LegacyPixProcessor.Instance, 0);
1502+
else
1503+
RemoveProcessor(LegacyPixProcessor.Instance);
13531504
}
13541505

1355-
private static Func<Volume<T>, Matrix<float>, Matrix<float>, ImageInterpolation, Volume<T>> s_remappedFun = null;
1506+
#endregion
13561507

1357-
public static void SetRemappedFun(Func<Volume<T>, Matrix<float>, Matrix<float>, ImageInterpolation, Volume<T>> remappedFun)
1358-
=> s_remappedFun = remappedFun;
1508+
#region Resized
13591509

13601510
public override PixImage ResizedPixImage(V2i newSize, ImageInterpolation ip = ImageInterpolation.Cubic)
13611511
=> Scaled((V2d)newSize / (V2d)Size, ip);
@@ -1366,47 +1516,68 @@ public PixImage<T> Resized(V2i newSize, ImageInterpolation ip = ImageInterpolati
13661516
public PixImage<T> Resized(int xSize, int ySize, ImageInterpolation ip = ImageInterpolation.Cubic)
13671517
=> Scaled(new V2d(xSize, ySize) / (V2d)Size, ip);
13681518

1519+
#endregion
1520+
1521+
#region Rotated
1522+
13691523
public override PixImage RotatedPixImage(double angleInRadiansCCW, bool resize = true, ImageInterpolation ip = ImageInterpolation.Cubic)
13701524
=> Rotated(angleInRadiansCCW, resize, ip);
13711525

13721526
public PixImage<T> Rotated(double angleInRadiansCCW, bool resize = true, ImageInterpolation ip = ImageInterpolation.Cubic)
13731527
{
1374-
if (s_rotatedFun == null)
1375-
{
1376-
throw new NotSupportedException($"No rotating function has been installed via PixImage<{(typeof(T).Name)}>.SetRotatedFun");
1377-
}
1528+
return InvokeProcessors(
1529+
(p) => p.Rotate(this, angleInRadiansCCW, resize, ip, default),
1530+
PixProcessorCaps.Rotate, "rotate image"
1531+
);
1532+
}
13781533

1379-
return new PixImage<T>(Format, s_rotatedFun(Volume, angleInRadiansCCW, resize, ip));
1534+
[Obsolete("Use the PixImage processor API instead.")]
1535+
public static void SetRotatedFun(Func<Volume<T>, double, bool, ImageInterpolation, Volume<T>> rotatedFun)
1536+
{
1537+
LegacyPixProcessor.Instance.SetRotateFun<T>(
1538+
(rotatedFun == null) ? null : (pi, angle, resize, ip) => new (pi.Format, rotatedFun(pi.Volume, angle, resize, ip))
1539+
);
1540+
1541+
if (LegacyPixProcessor.Instance.Capabilities != PixProcessorCaps.None)
1542+
SetProcessor(LegacyPixProcessor.Instance, 0);
1543+
else
1544+
RemoveProcessor(LegacyPixProcessor.Instance);
13801545
}
13811546

1382-
private static Func<Volume<T>, double, bool, ImageInterpolation, Volume<T>> s_rotatedFun = null;
1547+
#endregion
13831548

1384-
public static void SetRotatedFun(Func<Volume<T>, double, bool, ImageInterpolation, Volume<T>> rotatedFun)
1385-
=> s_rotatedFun = rotatedFun;
1549+
#region Scaled
13861550

13871551
public override PixImage ScaledPixImage(V2d scaleFactor, ImageInterpolation ip = ImageInterpolation.Cubic)
13881552
=> Scaled(scaleFactor, ip);
13891553

13901554
public PixImage<T> Scaled(V2d scaleFactor, ImageInterpolation ip = ImageInterpolation.Cubic)
13911555
{
1392-
if (s_scaledFun == null)
1393-
{
1394-
throw new NotSupportedException($"No scaling function has been installed via PixImage<{(typeof(T).Name)}>.SetScaledFun");
1395-
}
1396-
1397-
if (!(scaleFactor.X > 0.0 && scaleFactor.Y > 0.0)) throw new ArgumentOutOfRangeException(nameof(scaleFactor));
1556+
if (scaleFactor.AnySmallerOrEqual(0))
1557+
throw new ArgumentOutOfRangeException($"Scale factor must be positive ({scaleFactor}).");
13981558

13991559
// SuperSample is only available for scale factors < 1; fall back to Cubic
1400-
if ((scaleFactor.X >= 1.0 || scaleFactor.Y >= 1.0) && ip == ImageInterpolation.SuperSample)
1560+
if (scaleFactor.AnyGreater(1.0) && ip == ImageInterpolation.SuperSample)
14011561
ip = ImageInterpolation.Cubic;
14021562

1403-
return new PixImage<T>(Format, s_scaledFun(Volume, scaleFactor, ip));
1563+
return InvokeProcessors(
1564+
(p) => p.Scale(this, scaleFactor, ip),
1565+
PixProcessorCaps.Scale, "scale image"
1566+
);
14041567
}
14051568

1406-
private static Func<Volume<T>, V2d, ImageInterpolation, Volume<T>> s_scaledFun = TensorExtensions.Scaled;
1407-
1569+
[Obsolete("Use the PixImage processor API instead.")]
14081570
public static void SetScaledFun(Func<Volume<T>, V2d, ImageInterpolation, Volume<T>> scaledFun)
1409-
=> s_scaledFun = scaledFun;
1571+
{
1572+
LegacyPixProcessor.Instance.SetScaleFun<T>(
1573+
(scaledFun == null) ? null : (pi, scaleFactor, ip) => new (pi.Format, scaledFun(pi.Volume, scaleFactor, ip))
1574+
);
1575+
1576+
if (LegacyPixProcessor.Instance.Capabilities != PixProcessorCaps.None)
1577+
SetProcessor(LegacyPixProcessor.Instance, 0);
1578+
else
1579+
RemoveProcessor(LegacyPixProcessor.Instance);
1580+
}
14101581

14111582
public PixImage<T> Scaled(double scaleFactor, ImageInterpolation ip = ImageInterpolation.Cubic)
14121583
=> Scaled(new V2d(scaleFactor, scaleFactor), ip);
@@ -1416,6 +1587,8 @@ public PixImage<T> Scaled(double xScaleFactor, double yScaleFactor, ImageInterpo
14161587

14171588
#endregion
14181589

1590+
#endregion
1591+
14191592
#region SubImages
14201593

14211594
/// <summary>

0 commit comments

Comments
 (0)