263 lines
8.6 KiB
C#
263 lines
8.6 KiB
C#
using System.Windows.Media;
|
|
using FellowOakDicom.Imaging;
|
|
using System.Windows.Media.Imaging;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using FellowOakDicom;
|
|
using Livia.Utility;
|
|
using Microsoft.Extensions.Logging;
|
|
using Livia.Utility.DependencyInjection;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
namespace Livia.Models;
|
|
|
|
public interface IImageSeries : IDisposable
|
|
{
|
|
ImageSource? CurrentBitmapImage { get; }
|
|
double WindowCenterModifier { get; set; }
|
|
double WindowWidthModifier { get; set; }
|
|
bool WindowCenterModifiable { get; }
|
|
bool IsMask { set; }
|
|
bool ShowBoundary { get; set; }
|
|
//An index of the series where you can find the mask, used to locate ROI
|
|
int? MaskJumpToIndex { get; }
|
|
int Count { get; }
|
|
public void SetImageIndex(int n);
|
|
Task LoadData(string loadPath);
|
|
}
|
|
|
|
internal class PlainImageSeries : ObservableObject, IImageSeries
|
|
{
|
|
public ImageSource? CurrentBitmapImage { get => _currentBitmapImage; private set => SetProperty(ref _currentBitmapImage, value); }
|
|
public double WindowCenterModifier { get; set; }
|
|
public double WindowWidthModifier { get; set; }
|
|
public bool WindowCenterModifiable => false;
|
|
public bool IsMask { set => throw new NotImplementedException(); }
|
|
public bool ShowBoundary
|
|
{
|
|
get => throw new NotImplementedException();
|
|
set => throw new NotImplementedException();
|
|
}
|
|
|
|
public int? MaskJumpToIndex => -1;
|
|
|
|
public int Count => _bitmapImages.Count;
|
|
|
|
private readonly List<BitmapImage> _bitmapImages = [];
|
|
private ImageSource? _currentBitmapImage;
|
|
|
|
private BitmapImage? GetImage(int n)
|
|
{
|
|
if (n < 0 || n >= Count)
|
|
return null;
|
|
|
|
return _bitmapImages[n];
|
|
}
|
|
|
|
public void SetImageIndex(int n)
|
|
{
|
|
CurrentBitmapImage = GetImage(n);
|
|
}
|
|
|
|
public async Task LoadData(string loadPath)
|
|
{
|
|
_bitmapImages.Clear();
|
|
foreach (string file in LiviaUtility.ReadFilesFromDir(loadPath))
|
|
{
|
|
_bitmapImages.Add(await LiviaUtility.LoadBitmapAsync(file));
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
CurrentBitmapImage = null;
|
|
_bitmapImages.Clear();
|
|
}
|
|
}
|
|
|
|
internal class Dicom2DImageSeries : ObservableObject, IImageSeries
|
|
{
|
|
public ImageSource? CurrentBitmapImage { get => _currentBitmapImage; private set => SetProperty(ref _currentBitmapImage, value); }
|
|
public double WindowCenterModifier { get; set; } = 1;
|
|
public double WindowWidthModifier { get; set; } = 1;
|
|
public bool WindowCenterModifiable => Count > 0 && _defaultWindowCenter > 0 && _defaultWindowWidth > 0;
|
|
public bool IsMask { set => throw new NotImplementedException(); }
|
|
public bool ShowBoundary
|
|
{
|
|
get => throw new NotImplementedException();
|
|
set => throw new NotImplementedException();
|
|
}
|
|
|
|
public int? MaskJumpToIndex => -1;
|
|
public int Count => _dicomImages.Count;
|
|
|
|
private readonly List<DicomImage> _dicomImages = [];
|
|
private double _defaultWindowCenter;
|
|
private double _defaultWindowWidth;
|
|
|
|
private readonly SemaphoreSlim _lock = new(1, 1);
|
|
private string _loadedPath = string.Empty;
|
|
private readonly ILogger _logger = ActivatorUtilities.GetServiceOrCreateInstance<ILogger>(ServiceProviderFactory.ServiceProvider);
|
|
private ImageSource? _currentBitmapImage;
|
|
|
|
private BitmapImage? GetImage(int n)
|
|
{
|
|
if (n < 0 || n >= Count)
|
|
return null;
|
|
|
|
DicomImage dicomImage = _dicomImages[n];
|
|
dicomImage.WindowCenter = _defaultWindowCenter * WindowCenterModifier;
|
|
dicomImage.WindowWidth = Math.Max(_defaultWindowWidth * WindowWidthModifier, 1);
|
|
|
|
BitmapImage bitmapImage = dicomImage.RenderImage().AsClonedBitmap().ToBitmapImage();
|
|
bitmapImage.Freeze();
|
|
|
|
return bitmapImage;
|
|
}
|
|
|
|
public void SetImageIndex(int n)
|
|
{
|
|
CurrentBitmapImage = GetImage(n);
|
|
}
|
|
|
|
public async Task LoadData(string loadPath)
|
|
{
|
|
await _lock.WaitAsync();
|
|
try
|
|
{
|
|
if (loadPath == _loadedPath)
|
|
{
|
|
_logger.LogInformation("{path} is already loaded, move on", loadPath);
|
|
return;
|
|
}
|
|
|
|
_dicomImages.Clear();
|
|
double min = double.MaxValue;
|
|
double max = double.MinValue;
|
|
foreach (string file in LiviaUtility.ReadFilesFromDir(loadPath))
|
|
{
|
|
DicomFile dicomFile = await DicomFile.OpenAsync(file);
|
|
_dicomImages.Add(new DicomImage(dicomFile.Dataset));
|
|
int windowCenter = dicomFile.Dataset.GetSingleValueOrDefault(DicomTag.WindowCenter, 0);
|
|
double windowWidthHalf = dicomFile.Dataset.GetSingleValueOrDefault(DicomTag.WindowWidth, 0) / 2.0;
|
|
min = Math.Min(min, windowCenter - windowWidthHalf);
|
|
max = Math.Max(max, windowCenter + windowWidthHalf);
|
|
}
|
|
|
|
_defaultWindowCenter = (max + min) / 2;
|
|
_defaultWindowWidth = max - min;
|
|
|
|
_loadedPath = loadPath;
|
|
}
|
|
finally
|
|
{
|
|
_lock.Release();
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
CurrentBitmapImage = null;
|
|
_dicomImages.Clear();
|
|
}
|
|
}
|
|
|
|
internal class Dicom3DImageSeries : ObservableObject, IImageSeries
|
|
{
|
|
public ImageSource? CurrentBitmapImage { get => _currentBitmapImage; private set => SetProperty(ref _currentBitmapImage, value); }
|
|
public double WindowCenterModifier { get; set; } = 1;
|
|
public double WindowWidthModifier { get; set; } = 1;
|
|
public bool WindowCenterModifiable => Count > 0 && _defaultWindowCenter > 0 && _defaultWindowWidth > 0;
|
|
public bool IsMask { get; set; }
|
|
|
|
public bool ShowBoundary
|
|
{
|
|
get => _showBoundary;
|
|
set
|
|
{
|
|
_showBoundary = value;
|
|
//reload stuff
|
|
SetImageIndex(_currentImageIndex);
|
|
}
|
|
}
|
|
|
|
public int? MaskJumpToIndex { get => _maskJumpToIndex; private set => SetProperty(ref _maskJumpToIndex, value); }
|
|
|
|
public int Count => _dicomImage?.NumberOfFrames ?? 0;
|
|
|
|
private DicomImage? _dicomImage;
|
|
private ImageSource? _currentBitmapImage;
|
|
private int? _maskJumpToIndex;
|
|
private bool _showBoundary = true;
|
|
private int _currentImageIndex;
|
|
private int _defaultWindowCenter;
|
|
private int _defaultWindowWidth;
|
|
|
|
public void SetImageIndex(int n)
|
|
{
|
|
if (_dicomImage == null || n < 0 || n >= Count)
|
|
return;
|
|
|
|
_currentImageIndex = n;
|
|
if (_defaultWindowCenter != 0 && _defaultWindowWidth != 0)
|
|
{
|
|
_dicomImage.WindowCenter = _defaultWindowCenter * WindowCenterModifier;
|
|
_dicomImage.WindowWidth = Math.Max(_defaultWindowWidth * WindowWidthModifier, 1);
|
|
}
|
|
BitmapImage image = _dicomImage.RenderImage(n).AsClonedBitmap().ToBitmapImage();
|
|
|
|
image.Freeze();
|
|
if (!IsMask)
|
|
{
|
|
CurrentBitmapImage = image;
|
|
return;
|
|
}
|
|
|
|
BitmapSource transparencyImage = LiviaUtility.CreateTransparency(image, ShowBoundary);
|
|
CurrentBitmapImage = transparencyImage;
|
|
}
|
|
|
|
public async Task LoadData(string loadPath)
|
|
{
|
|
DicomFile dcmFile = await DicomFile.OpenAsync(loadPath);
|
|
_dicomImage = new DicomImage(dcmFile.Dataset);
|
|
_defaultWindowCenter = dcmFile.Dataset.GetSingleValueOrDefault<int?>(DicomTag.WindowCenter, null) ?? 0;
|
|
_defaultWindowWidth = dcmFile.Dataset.GetSingleValueOrDefault<int?>(DicomTag.WindowWidth, null) ?? 0;
|
|
|
|
if (!IsMask)
|
|
return;
|
|
|
|
//just run it. Do not care how long it takes, as long as it is not blocking ui thread.
|
|
_ = Task.Run(UpdateMaskJumpToIndex);
|
|
}
|
|
|
|
private void UpdateMaskJumpToIndex()
|
|
{
|
|
if (_dicomImage == null)
|
|
return;
|
|
|
|
//find am index with max pixel count. When locate this roi, jump here.
|
|
int[] result = new int[Count];
|
|
Parallel.For(0, Count, i =>
|
|
{
|
|
BitmapImage image = _dicomImage.RenderImage(i).AsClonedBitmap().ToBitmapImage();
|
|
image.Freeze();
|
|
int bytesPerPixel = (image.Format.BitsPerPixel + 7) / 8;
|
|
int stride = bytesPerPixel * image.PixelWidth;
|
|
byte[] buffer = new byte[stride * image.PixelHeight];
|
|
int alphaCount = buffer.Length / 4;
|
|
image.CopyPixels(buffer, stride, 0);
|
|
|
|
int count = buffer.Count(b => b > 0) - alphaCount;
|
|
result[i] = count;
|
|
});
|
|
int maxCount = result.Max();
|
|
|
|
MaskJumpToIndex = maxCount == 0 ? null : Array.IndexOf(result, maxCount);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
CurrentBitmapImage = null;
|
|
_dicomImage = null;
|
|
}
|
|
} |