172 lines
No EOL
4.5 KiB
C#
172 lines
No EOL
4.5 KiB
C#
using OpenCvSharp;
|
|
using StitchATon.Utility;
|
|
|
|
namespace StitchATon.Services;
|
|
|
|
public class ImageProvider
|
|
{
|
|
// Terminology
|
|
// Chunk: 720*720 region thingy
|
|
// Sector: Area defined in image chunk coordinates
|
|
// Region: Area defined in image pixel coordinates
|
|
|
|
public class Config(string imagePath, int sectorDim, int w, int h)
|
|
{
|
|
public string ImagePath = imagePath;
|
|
public int SectorDim = sectorDim;
|
|
public int W = w;
|
|
public int H = h;
|
|
}
|
|
|
|
private Grid2D<ImageStatus> _ready;
|
|
private Mat _canvas;
|
|
private readonly Config _config;
|
|
private ILogger<ImageProvider> _logger;
|
|
|
|
enum ImageStatus
|
|
{
|
|
Blank,
|
|
Loading,
|
|
Ready
|
|
}
|
|
|
|
public ImageProvider( Config config, ILogger<ImageProvider> logger )
|
|
{
|
|
_config = config;
|
|
_logger = logger;
|
|
_ready = new Grid2D<ImageStatus>( _config.W, _config.H );
|
|
_canvas = new Mat(
|
|
_config.H * _config.SectorDim,
|
|
_config.W * _config.SectorDim,
|
|
MatType.CV_8UC3
|
|
);
|
|
}
|
|
|
|
string GetImagePath( int x, int y )
|
|
{
|
|
x++;
|
|
y++;
|
|
|
|
string letter = string.Empty;
|
|
|
|
while (y > 0)
|
|
{
|
|
y--; // Adjust to make A=0, B=1, ..., Z=25 for modulo operation
|
|
int remainder = y % 26;
|
|
char digit = (char)('A' + remainder);
|
|
letter = digit + letter;
|
|
y /= 26;
|
|
}
|
|
|
|
var filename = $"{letter}{x}.png";
|
|
return Path.Join( _config.ImagePath, filename );
|
|
}
|
|
|
|
async Task LoadImage( int x, int y )
|
|
{
|
|
_ready[x, y] = ImageStatus.Loading;
|
|
string path = GetImagePath( x, y );
|
|
_logger.LogInformation( $"{path} not loaded yet, reading" );
|
|
using Mat image = await Task.Run( () => Cv2.ImRead( path ) );
|
|
image.CopyTo( GetChunkMat(x, y) );
|
|
_ready[x, y] = ImageStatus.Ready;
|
|
}
|
|
|
|
// After this function is run, it is guaranteed that all images concerned within the SoI is loaded to the grand canvas.
|
|
// Has a flagging mechanism to just wait if another call of this function is currently loading it.
|
|
async Task LoadImages(Rect soi)
|
|
{
|
|
|
|
_logger.LogInformation( $"{soi.Width * soi.Height} chunks required" );
|
|
|
|
List<Task>? loadTasks = null;
|
|
List<(int x, int y)>? loadedByOthers = null;
|
|
|
|
for( int x = soi.Left; x < soi.Right; x++ )
|
|
for( int y = soi.Top; y < soi.Bottom; y++ )
|
|
switch( _ready[x, y] )
|
|
{
|
|
case ImageStatus.Blank:
|
|
if( loadTasks == null )
|
|
loadTasks = new List<Task>( soi.Width * soi.Height );
|
|
loadTasks.Add( LoadImage( x, y ) );
|
|
break;
|
|
case ImageStatus.Loading:
|
|
if( loadedByOthers == null )
|
|
loadedByOthers = new List<(int, int)>( 5 );
|
|
loadedByOthers.Add( (x, y) );
|
|
break;
|
|
}
|
|
|
|
if( loadTasks != null )
|
|
{
|
|
await Task.WhenAll( loadTasks );
|
|
_logger.LogInformation( $"Finished loading {loadTasks.Count} images" );
|
|
}
|
|
|
|
// Spinlock until all images are loaded. 1ms delay to prevent processor overload
|
|
while( loadedByOthers != null && loadedByOthers.Count != 0 )
|
|
{
|
|
await Task.Delay( 1 );
|
|
loadedByOthers.RemoveAll( coord => _ready[coord.x, coord.y] == ImageStatus.Ready );
|
|
}
|
|
}
|
|
|
|
Mat GetChunkMat( int x, int y )
|
|
{
|
|
var roi = new Rect(
|
|
_config.SectorDim * x,
|
|
_config.SectorDim * y,
|
|
_config.SectorDim,
|
|
_config.SectorDim
|
|
);
|
|
|
|
return _canvas[roi];
|
|
}
|
|
|
|
Rect GetGlobalRoi( Rect soi, Point2f roiOffsetRatio, Point2f roiSizeRatio )
|
|
{
|
|
var soiSizePx = new Size(
|
|
soi.Size.Width * _config.SectorDim,
|
|
soi.Size.Height * _config.SectorDim );
|
|
|
|
return new Rect(
|
|
( soi.X * _config.SectorDim ) + (int)( soiSizePx.Width * roiOffsetRatio.X ),
|
|
( soi.Y * _config.SectorDim ) + (int)( soiSizePx.Height * roiOffsetRatio.Y ),
|
|
(int)( soiSizePx.Width * roiSizeRatio.X ),
|
|
(int)( soiSizePx.Height * roiSizeRatio.Y )
|
|
);
|
|
}
|
|
|
|
Rect GetSoi( Rect roi )
|
|
{
|
|
var tl = roi.TopLeft;
|
|
var br = roi.BottomRight;
|
|
|
|
var soiTl = new Point(
|
|
tl.X / _config.SectorDim,
|
|
tl.Y / _config.SectorDim
|
|
);
|
|
|
|
var soiBr = new Point(
|
|
(int) Math.Ceiling( br.X / (float) _config.SectorDim ),
|
|
(int) Math.Ceiling( br.Y / (float) _config.SectorDim )
|
|
);
|
|
|
|
return new Rect(
|
|
soiTl.X,
|
|
soiTl.Y,
|
|
soiBr.X - soiTl.X,
|
|
soiBr.Y - soiTl.Y );
|
|
}
|
|
|
|
public async Task<Mat> GetImage( Rect soi, Point2f roiOffsetRatio, Point2f roiSizeRatio )
|
|
{
|
|
var globalRoi = GetGlobalRoi( soi, roiOffsetRatio, roiSizeRatio);
|
|
var adaptedSoi = GetSoi( globalRoi );
|
|
|
|
await LoadImages( adaptedSoi );
|
|
|
|
return _canvas[globalRoi];
|
|
}
|
|
} |