First Commit
This commit is contained in:
parent
bb40883c7d
commit
696158848f
18 changed files with 787 additions and 0 deletions
172
StitchATon/Services/ImageProvider.cs
Normal file
172
StitchATon/Services/ImageProvider.cs
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
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];
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue