diff --git a/.gitignore b/.gitignore index f2999ca..31acff3 100644 --- a/.gitignore +++ b/.gitignore @@ -28,7 +28,6 @@ [Oo]bj/ _UpgradeReport_Files/ [Pp]ackages/ -/.contest Thumbs.db Desktop.ini diff --git a/.idea/.idea.StitchATon/.idea/.gitignore b/.idea/.idea.StitchATon/.idea/.gitignore deleted file mode 100644 index 78e63ec..0000000 --- a/.idea/.idea.StitchATon/.idea/.gitignore +++ /dev/null @@ -1,13 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Rider ignored files -/.idea.StitchATon.iml -/contentModel.xml -/projectSettingsUpdater.xml -/modules.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/.idea.StitchATon/.idea/encodings.xml b/.idea/.idea.StitchATon/.idea/encodings.xml deleted file mode 100644 index df87cf9..0000000 --- a/.idea/.idea.StitchATon/.idea/encodings.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/.idea.StitchATon/.idea/indexLayout.xml b/.idea/.idea.StitchATon/.idea/indexLayout.xml deleted file mode 100644 index 7b08163..0000000 --- a/.idea/.idea.StitchATon/.idea/indexLayout.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/.idea.StitchATon/.idea/vcs.xml b/.idea/.idea.StitchATon/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/.idea.StitchATon/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/LocalNuget/.gitignore b/LocalNuget/.gitignore deleted file mode 100644 index fbe7bcd..0000000 --- a/LocalNuget/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.nupkg \ No newline at end of file diff --git a/NuGet.Config b/NuGet.Config deleted file mode 100644 index 954ec62..0000000 --- a/NuGet.Config +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/README.md b/README.md deleted file mode 100644 index ba4bfaa..0000000 --- a/README.md +++ /dev/null @@ -1,120 +0,0 @@ -# I Paid for 4 GB of RAM So I Will Use The Whole 4 GB of RAM (and Probably More) -Submission for Stitch-A-Ton Contest. - -## Prerequisites -- **Dotnet installation** -- **~4 GB** of memory on heavier operations. Command below sets the swap file size to 4GB. - ``` - sudo dphys-swapfile swapoff - sudo sed -i 's/^\(CONF_SWAPSIZE=\).*/\14096/' /etc/dphys-swapfile - sudo dphys-swapfile setup - sudo dphys-swapfile swapon - sudo reboot - ``` - If the test cases does not request a large portion of the canvas ***AND*** resizes it to a relatively still large ratio (i,e, ~0.9) **at the same time**, this might not be necessary. -- **Access to** the `mkfifo` command, satisfied by default bar very special cases -- **OpenCVSharp4 Runtime for Raspberry Pi 5** - - If running via dotnet run, Download the `.nupkg` file [here](https://null.formulatrix.dev/reinardras/stitch_something/releases/tag/0.0.0) and save on `LocalNuget` before running. Otherwise it shouldn't be necessary. - - If there's still a problem with library being missing, download the `.so` file and put it on `/usr/local/lib`. - - -## Running - -Either of these method works. - -### Via dotnet run - -On root directory: -``` -ASSET_PATH_RO= dotnet run --project StitchATon --profile deploy -``` - -The API is accessible at `:5255`, providing the following api: -- [POST] `api/image/generate`: complies with competition guidelines -- [GET] `api/image/sanity`: generates a predefined crop region: - - `G6:I8`, `(.1, .1)` offset, `(.8,.8)` crop, at 0.6 scale. - -To browse the API, prepend `ASPNETCORE_ENVIRONMENT=Development` to the command and go to `/swagger/index.html`. - - -### Via releases - -Download [here](https://null.formulatrix.dev/reinardras/stitch_something/releases/). - -Run with the same parameters: - -``` -ASSET_PATH_RO= ./StitchATon --profile deploy -``` - -## Writeup - -This submission contains no specific magic in the image processing, just OpenCVSharp stretched to the best of its ability according to my knowledge. This section contains a brief overview of the main features. - -Per the writer's knowledge, the end result is **fast enough for the operation to be bottlenecked by network transfer speed** instead of image processing, except when resizing. - -*(note: I don't do rigorous benchmarks for that, take it with a grain of salt)* - -### Canvas Memory Usage -During initialization, a blank 55x31 of 720x720 canvas is created, along with a 55*31 grid of enums indicating whether a chunk is already loaded, is currently being loaded, or ready to use. - -The memory usage of this canvas follows what chunks has been loaded into it, topping at ~2.6GB when all chunks are loaded. - -When multiple requests refer to the same region of the canvas, it doesn't need to be loaded again. - -### Coordinate Processing -When parsing the request, it's possible that the requested canvas size doesn't correspond to what chunks that will actually be read; for example `A1:A3` at no offset and `(0.2, 1)` crop will only read some parts of `A1` chunk. - -``` - canvas -┌─────────────────┬─────────────────┬─────────────────┐ -│ ┌───────────┐ │ │ │ -│ │ │ │ │ │ -│ │ final │ │ │ │ -│ │ result │ │ │ │ -│ │ │ │ │ │ -│ └───────────┘ │ │ │ -└─────────────────┴─────────────────┴─────────────────┘ -``` - -To handle that, the resulting global crop RoI is calculated first and the chunks that are *actually* needed is identified (referred as *Adapted Sectors of Interest*). - -### Chunk Loading -OpenCV Mats are thread safe given any operation is performed on non-overlapping regions of it. This allows multitasking reading the chunk to the main canvas. - -After coordinate processing, chunks in the adapted SoI are checked for their load status, then processed accordingly. - -The status "Currently being loaded" is relevant when multiple requests requiring the same chunk(s) are underway; on such case the loader that checks later spinlocks until it's done loading. - -**This mechanism enables the shared canvas to serve multiple processes.** - -### Encoding and Serving Cropped Image -After the needed chunks are certain to be loaded to the main canvas, next is cropping it; which is a trivial operation in OpenCV, not requiring any extra memory since it still refers to the main canvas. - -What's not trivial is *encoding* it, which after some quick tests shows to take longer than reading and decoding multiple images from the canvas. - -OpenCV provides two functions that can help decode to PNG: -- `Cv2.ImWrite` that writes to a file, and -- `Cv2.ImEncode` that writes to a byte array. - -Both of these requires the encoding to finish before the resulting data can be used. - -To alleviate this problem, a **named pipe** (some sort of file pointer that works as a pipe buffer) is used; `ImWrite`-ing to said named pipe and have ASP.NET read from it. By doing this: -- The encode and send process is parallelized -- No extra memory needs to be allocated to encode the image; either on storage or RAM - -### (Unsolved) Resizing - -This remains as the only pain point that's not straightforward to solve. If no resize is requested, it's solvable by cropping off the main canvas and encoding it to a named pipe; eliminating a lot of time and memory overhead on the way. - -If resize is requested, a new Mat containing the resized image needs to be allocated. - -Ideas for this problem: -- resize function that outputs a stream, -- Imencode/imwrite function that accepts a stream. - - - diff --git a/StitchATon.Bench/Program.cs b/StitchATon.Bench/Program.cs deleted file mode 100644 index c0ad218..0000000 --- a/StitchATon.Bench/Program.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System.Diagnostics; -using BenchmarkDotNet.Attributes; -using Microsoft.Extensions.Logging.Abstractions; -using OpenCvSharp; -using StitchATon.Services; - -namespace StitchATon.Bench; - -public class Program -{ - static readonly ImageProvider Ip = new ( new ImageProvider.Config( - "/home/retorikal/Downloads/tiles1705/", - 720, - 55, - 31), - NullLogger.Instance ); - // static ImageProvider ip = new ( "/mnt/ramdisk/" ); - - static async Task Fn1() - { - using var im = await Ip.GetImage( - new Rect( new Point( 0, 0 ), new Size( 2, 1 ) ), - new Point2f( 0, 0 ), - new Point2f( 1, 1 ) ); - // Cv2.ImEncode(".png", im, out _); - Cv2.ImWrite("/tmp/im1.png", im ); - } - - static async Task Fn2() - { - using var im = await Ip.GetImage( - new Rect( new Point( 1, 1 ), new Size( 3, 3 ) ), - new Point2f( 0, 0 ), - new Point2f( 1, 1 ) ); - // Cv2.ImEncode(".png", im, out _); - // Cv2.ImWrite("/tmp/im2.png", im2 ); - } - - static async Task Fn3() - { - using var im = await Ip.GetImage( - new Rect( new Point( 2, 2 ), new Size( 3, 3 ) ), - new Point2f( 0.2f, 0.2f ), - new Point2f( .6f, .6f ) ); - // Cv2.ImEncode(".png", im, out _); - // Cv2.ImWrite("/tmp/im3.png", im3 ); - } - - static async Task Fn4() - { - using var im = await Ip.GetImage( - new Rect( new Point( 0, 0 ), new Size( 10, 10 ) ), - new Point2f( 0.2f, 0.2f ), - new Point2f( .6f, .6f ) ); - // Cv2.ImEncode(".png", im, out _); - // Cv2.ImWrite("/tmp/im4.png", im4 ); - } - - - // [Benchmark] - static async Task Conc() - { - await Task.WhenAll( - Fn1(), - Fn2(), - Fn3(), - Fn4() - ); - } - - // [Benchmark] - static async Task Serial() - { - await Fn1(); - await Fn2(); - await Fn3(); - await Fn4(); - } - - public static ProcessStartInfo MkfifoPs = new("mkfifo", "/tmp/out.png"); - public static ProcessStartInfo RmfifoPs = new("rm", "/tmp/out.png"); - - [Benchmark] - public void Mkfifo() - { - Process.Start( MkfifoPs )?.WaitForExit(); - Process.Start( RmfifoPs )?.WaitForExit(); - } - - public async static Task Main( string[] args ) - { - - await Fn1(); - // var summary = BenchmarkRunner.Run(); - // var stopwatch = Stopwatch.StartNew(); - // - // - // stopwatch.Stop(); - // Console.WriteLine( $"Elapsed time: {stopwatch.ElapsedMilliseconds} ms" ); - // - // var stopwatchAfter = Stopwatch.StartNew(); - // await Fn4(); - // stopwatchAfter.Stop(); - // Console.WriteLine( $"Elapsed time: {stopwatchAfter.ElapsedMilliseconds} ms" ); - } -} \ No newline at end of file diff --git a/StitchATon.Bench/StitchATon.Bench.csproj b/StitchATon.Bench/StitchATon.Bench.csproj deleted file mode 100644 index 64451d0..0000000 --- a/StitchATon.Bench/StitchATon.Bench.csproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - Exe - net8.0 - enable - enable - StitchATon.Bench - - - - - - - - - - - - - diff --git a/StitchATon.sln b/StitchATon.sln deleted file mode 100644 index e553226..0000000 --- a/StitchATon.sln +++ /dev/null @@ -1,20 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StitchATon", "StitchATon\StitchATon.csproj", "{CE9E5242-9FC1-4B19-BD20-C6A2103C2DC8}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StitchATon.Bench", "StitchATon.Bench\StitchATon.Bench.csproj", "{84B2813B-56FB-4DB8-999D-9F363DBF5E19}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {CE9E5242-9FC1-4B19-BD20-C6A2103C2DC8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CE9E5242-9FC1-4B19-BD20-C6A2103C2DC8}.Release|Any CPU.Build.0 = Release|Any CPU - {CE9E5242-9FC1-4B19-BD20-C6A2103C2DC8}.Debug|Any CPU.ActiveCfg = Release|Any CPU - {CE9E5242-9FC1-4B19-BD20-C6A2103C2DC8}.Debug|Any CPU.Build.0 = Release|Any CPU - {84B2813B-56FB-4DB8-999D-9F363DBF5E19}.Debug|Any CPU.ActiveCfg = Release|Any CPU - {84B2813B-56FB-4DB8-999D-9F363DBF5E19}.Debug|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection -EndGlobal diff --git a/StitchATon/Controller.cs b/StitchATon/Controller.cs deleted file mode 100644 index 0b8aa0a..0000000 --- a/StitchATon/Controller.cs +++ /dev/null @@ -1,98 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.ObjectPool; -using OpenCvSharp; -using StitchATon.DTO; -using StitchATon.Services; -using StitchATon.Utility; - -namespace StitchATon; - -[ApiController] -[Route("api/image/")] -public class ImageController( ImageProvider ip, ObjectPool npPool, ILogger logger ) : ControllerBase -{ - [HttpPost] - [Route("generate")] - public async Task GetImage([FromBody] GenerateInput generateInput) - { - logger.LogInformation( $"GetImage requested at {generateInput.CanvasRect}" ); - - var namedPipe = npPool.Get(); - var imagePath = namedPipe.PipeFullname; - - using var roiIm = await ip.GetImage( - generateInput.ParsedCanvasRect(), - generateInput.ParsedCropOffset(), - generateInput.ParsedCropSize() - ); - - var scaledSize = new Size( - roiIm.Cols * generateInput.OutputScale, - roiIm.Rows * generateInput.OutputScale - ); - Mat resizedIm = new(); - - if( scaledSize == roiIm.Size() ) - resizedIm = roiIm; - else - Cv2.Resize( roiIm, resizedIm, scaledSize ); - - // Spawn new task to write to the named pipe - Task.Run( () => - { - Cv2.ImWrite( imagePath, resizedIm ); - resizedIm.Dispose(); - } ); - - // Stream the named pipe as output - var fileStream = new FileStream( - imagePath, - FileMode.Open, - FileAccess.Read, - FileShare.Read, - 4096, - FileOptions.Asynchronous | FileOptions.SequentialScan); - - return File(fileStream, "image/png"); - } - - [HttpGet] - [Route("sanity")] - public async Task GetImageSanityTest() - { - var namedPipe = npPool.Get(); - var imagePath = namedPipe.PipeFullname; - - using var roiIm = await ip.GetImage( - new Rect( new Point( 5, 6 ), new Size( 3, 3 ) ), - new Point2f( .1f, .1f ), - new Point2f( .8f, .8f ) ); - - var mul = .7; - var scaledSize = new Size(roiIm.Cols * mul , roiIm.Rows * mul); - Mat resizedIm = new(); - - if( scaledSize == roiIm.Size() ) - resizedIm = roiIm; - else - Cv2.Resize( roiIm, resizedIm, scaledSize ); - - // Spawn new task to write to the named pipe - Task.Run( () => - { - Cv2.ImWrite( imagePath, resizedIm ); - resizedIm.Dispose(); - } ); - - // Stream the named pipe as output - var fileStream = new FileStream( - imagePath, - FileMode.Open, - FileAccess.Read, - FileShare.Read, - 4096, - FileOptions.Asynchronous | FileOptions.SequentialScan); - - return File(fileStream, "image/png"); - } -} diff --git a/StitchATon/DTO/Generate.cs b/StitchATon/DTO/Generate.cs deleted file mode 100644 index 8e05eb3..0000000 --- a/StitchATon/DTO/Generate.cs +++ /dev/null @@ -1,48 +0,0 @@ -using JetBrains.Annotations; -using OpenCvSharp; - -namespace StitchATon.DTO; - -public class GenerateInput -{ - public required string CanvasRect { get; [UsedImplicitly] init; } - public required float[] CropOffset { get; [UsedImplicitly] init; } - public required float[] CropSize { get; [UsedImplicitly] init; } - public required float OutputScale { get; [UsedImplicitly] init; } - - public Rect ParsedCanvasRect() - { - var corners = CanvasRect.Split( ":" ); - var c1 = ParseCanvasCoord( corners[0] ); - var c2 = ParseCanvasCoord( corners[1] ); - - return new Rect( - int.Min( c1.X, c2.X ), - int.Min( c1.Y, c2.Y ), - int.Abs( c1.X - c2.X ) + 1, // Inclusive bbox - int.Abs( c1.Y - c2.Y ) + 1 // Inclusive bbox - ); - } - - private Point ParseCanvasCoord(string labwareCoord) - { - int y = 0; - int x = 0; - foreach( var c in labwareCoord ) - { - if( 'A' <= c && c <= 'Z' ) - y = ( y * 26 ) + ( c - 'A' + 1); - else - x = ( x * 10 ) + ( c - '0' ); - } - - y--; - x--; - - return new(x, y); - } - - public Point2f ParsedCropOffset() => new( CropOffset[0], CropOffset[1] ); - - public Point2f ParsedCropSize() => new( CropSize[0], CropSize[1] ); -} \ No newline at end of file diff --git a/StitchATon/Program.cs b/StitchATon/Program.cs deleted file mode 100644 index 5b69be0..0000000 --- a/StitchATon/Program.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.Text.Json; -using Microsoft.Extensions.ObjectPool; -using StitchATon.Services; -using StitchATon.Utility; - - -var builder = WebApplication.CreateBuilder( args ); - -builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(); - -var path = Environment.GetEnvironmentVariable( "ASSET_PATH_RO" ); -if( path == null ) - throw new ArgumentException("Please supply the directory path on ASSET_PATH_RO env var"); - -// Image Loader -builder.Services.AddSingleton( new ImageProvider.Config( - path, - 720, - 55, - 31) ); -builder.Services.AddSingleton(); - -// FIFO named pipe pool -builder.Services.AddSingleton>( new DefaultObjectPool( - new DefaultPooledObjectPolicy(), - 10 ) ); -builder.Services.AddControllers().AddJsonOptions( options => - { - options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower; - options.JsonSerializerOptions.WriteIndented = true; - } - - ); -builder.Logging.AddConsole(); - -var app = builder.Build(); - -// Configure the HTTP request pipeline. -if( app.Environment.IsDevelopment() ) -{ - app.UseSwagger(); - app.UseSwaggerUI(); -} - -app.UseCors(); -app.UseRouting(); -app.MapControllers(); - -app.Run(); diff --git a/StitchATon/Properties/launchSettings.json b/StitchATon/Properties/launchSettings.json deleted file mode 100644 index c8b7b7d..0000000 --- a/StitchATon/Properties/launchSettings.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "$schema": "http://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:24734", - "sslPort": 44313 - } - }, - "profiles": { - "deploy": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "launchUrl": "swagger", - "applicationUrl": "http://localhost:5255" - }, - "http": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "launchUrl": "swagger", - "applicationUrl": "http://localhost:5255", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "ASSET_PATH_RO": "/home/retorikal/Downloads/tiles1705/" - } - }, - "https": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "launchUrl": "swagger", - "applicationUrl": "https://localhost:7105;http://localhost:5255", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "ASSET_PATH_RO": "/home/retorikal/Downloads/tiles1705/" - } - }, - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "swagger", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "ASSET_PATH_RO": "/home/retorikal/Downloads/tiles1705/" - } - } - } -} diff --git a/StitchATon/Services/ImageProvider.cs b/StitchATon/Services/ImageProvider.cs deleted file mode 100644 index ea119b4..0000000 --- a/StitchATon/Services/ImageProvider.cs +++ /dev/null @@ -1,172 +0,0 @@ -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 _ready; - private Mat _canvas; - private readonly Config _config; - private ILogger _logger; - - enum ImageStatus - { - Blank, - Loading, - Ready - } - - public ImageProvider( Config config, ILogger logger ) - { - _config = config; - _logger = logger; - _ready = new Grid2D( _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? 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( 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 GetImage( Rect soi, Point2f roiOffsetRatio, Point2f roiSizeRatio ) - { - var globalRoi = GetGlobalRoi( soi, roiOffsetRatio, roiSizeRatio); - var adaptedSoi = GetSoi( globalRoi ); - - await LoadImages( adaptedSoi ); - - return _canvas[globalRoi]; - } -} \ No newline at end of file diff --git a/StitchATon/StitchATon.csproj b/StitchATon/StitchATon.csproj deleted file mode 100644 index c0dc6f0..0000000 --- a/StitchATon/StitchATon.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - net8.0 - enable - enable - - - - - - - - - - - - diff --git a/StitchATon/Utility/Grid2D.cs b/StitchATon/Utility/Grid2D.cs deleted file mode 100644 index 46a922d..0000000 --- a/StitchATon/Utility/Grid2D.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace StitchATon.Utility; - -public class Grid2D -{ - private T[] _buffer; - public readonly int W; - public readonly int H; - - public Grid2D( int width, int height ) - { - W = width; - H = height; - _buffer = new T[W * H]; - } - - private int Map( int x, int y ) - { - return x + ( y * W ); - } - - public T this[ int x, int y ] - { - get => _buffer[Map( x, y )]; - set => _buffer[Map( x, y )] = value; - } -} \ No newline at end of file diff --git a/StitchATon/Utility/NamedPipe.cs b/StitchATon/Utility/NamedPipe.cs deleted file mode 100644 index 0fea233..0000000 --- a/StitchATon/Utility/NamedPipe.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Diagnostics; - -namespace StitchATon.Utility; - -public class PngNamedPipe : IDisposable -{ - private ProcessStartInfo _mkfifoPs = new("mkfifo"); - private ProcessStartInfo _rmfifoPs = new("rm"); - public readonly string PipeFullname = Path.Join( Path.GetTempPath(), Guid.NewGuid() + ".png" ); - - public PngNamedPipe( ) - { - _mkfifoPs.Arguments = PipeFullname; - Process.Start( _mkfifoPs )?.WaitForExit(); - } - - public void Dispose() - { - _rmfifoPs.Arguments = PipeFullname; - Process.Start( _rmfifoPs ); - } -} \ No newline at end of file diff --git a/StitchATon/appsettings.Development.json b/StitchATon/appsettings.Development.json deleted file mode 100644 index 7e8b4b2..0000000 --- a/StitchATon/appsettings.Development.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning", - "StitchATon.Controller": "Information" - } - } -} diff --git a/StitchATon/appsettings.json b/StitchATon/appsettings.json deleted file mode 100644 index f769bb8..0000000 --- a/StitchATon/appsettings.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning", - "StitchATon.Controller": "Information" - } - }, - "AllowedHosts": "*" -} diff --git a/mise.toml b/mise.toml deleted file mode 100644 index 5b17a42..0000000 --- a/mise.toml +++ /dev/null @@ -1,92 +0,0 @@ -# Quick Guide -# - install mise -# - download the asset and extract them to ASSET_PATH_RO -# - mise trust mise.toml -# - mise run verify-asset -# - require hashdeep -# `hashdeep` package in debian -# or `md5deep` package in fedora -# or uncomment `tools."http:hashdeep"` below in windows -# - mise run serve -# - mise run arrange -# - mise run action -# - mise run assert -# - mise run bench - -[env] -ASSET_PATH_RO = "{{ [xdg_cache_home, 'stitch-a-ton', 'asset'] | join_path }}" -CONTEST_HOST = "http://localhost:7007" -CONTEST_API = "/api/image/generate" -CONTEST_OUTPUT = "{{ [cwd, '.contest'] | join_path }}" -DOTNET_ENVIRONMENT = "Production" -ANSWER_COMMIT_HASH = "89a07b40bf0414212c96945671a012035d375a25" - -[tools] -dotnet = "8" -xh = "latest" -uv = "latest" -k6 = "latest" - -# uncomment these if you're on windows -#[tools."http:hashdeep"] -#version = "4.4" - -#[tools."http:hashdeep".platforms] -#windows-x64 = {url = "https://github.com/jessek/hashdeep/releases/download/v4.4/md5deep-4.4.zip"} - -[tasks.setup] -run = ''' -{% if env.CONTEST_OUTPUT is not exists %} -mkdir .contest -{% endif %} -''' - -[tasks.verify-asset] -dir = "{{ env.ASSET_PATH_RO }}" -run = ''' -xh get https://null.formulatrix.dev/Contest/stitch-a-ton-answer/raw/commit/{{ env.ANSWER_COMMIT_HASH }}/asset.txt -o ../asset.txt -hashdeep -arbvk ../asset.txt . -''' - -[tasks.arrange] -depends = ['setup'] -dir = "{{ env.CONTEST_OUTPUT }}" -outputs = ['answer.json', 'action.py', 'assert.py', 'bench.js', 'fuzzy.json'] -run = ''' -xh get https://null.formulatrix.dev/Contest/stitch-a-ton-answer/raw/commit/{{ env.ANSWER_COMMIT_HASH }}/answer.json -o answer.json -xh get https://null.formulatrix.dev/Contest/stitch-a-ton-answer/raw/commit/{{ env.ANSWER_COMMIT_HASH }}/action.py -o action.py -xh get https://null.formulatrix.dev/Contest/stitch-a-ton-answer/raw/commit/{{ env.ANSWER_COMMIT_HASH }}/assert.py -o assert.py -xh get https://null.formulatrix.dev/Contest/stitch-a-ton-answer/raw/commit/{{ env.ANSWER_COMMIT_HASH }}/bench.js -o bench.js -xh get https://null.formulatrix.dev/Contest/stitch-a-ton-answer/raw/commit/{{ env.ANSWER_COMMIT_HASH }}/fuzzy.json -o fuzzy.json -''' - -[tasks.serve] -run = "dotnet run -c Release --no-launch-profile --urls {{env.CONTEST_HOST}} --project StitchATon" - -[tasks.quick] -depends = ['arrange'] -dir = "{{ env.CONTEST_OUTPUT }}" -run = ''' -xh post {{env.CONTEST_HOST}}{{env.CONTEST_ENDPOINT}} canvas_rect=A1:H12 crop_offset:=[0,0] crop_size:=[1,1] output_scale:=0.25 -o quick.png -''' - -[tasks.action] -depends = ['arrange'] -dir = "{{ env.CONTEST_OUTPUT }}" -run = ''' -uv run --no-config --script {{ [env.CONTEST_OUTPUT, 'action.py'] | join_path }} -''' - -[tasks.assert] -depends = ['arrange'] -dir = "{{ env.CONTEST_OUTPUT }}" -run = ''' -uvx --no-config --with-requirements assert.py pytest assert.py -''' - -[tasks.bench] -depends = ['arrange'] -dir = "{{ env.CONTEST_OUTPUT }}" -run = ''' -k6 run -e TARGET_URL="{{ env.CONTEST_HOST }}{{ env.CONTEST_API }}" bench.js -'''