Created
December 16, 2013 10:06
-
-
Save jandk/7984759 to your computer and use it in GitHub Desktop.
Simple image processing in a generic way (just an idea)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using System.IO; | |
using System.Linq; | |
namespace Util | |
{ | |
public struct Position | |
{ | |
public int X { get; set; } | |
public int Y { get; set; } | |
} | |
public interface IRaster<out T> | |
{ | |
int SizeX { get; } | |
int SizeY { get; } | |
T Get(int x, int y); | |
IEnumerable<T> Enumerate(); | |
} | |
public interface IWritableRaster<T> : IRaster<T> | |
{ | |
void Set(int x, int y, T value); | |
} | |
public interface IRasterCollection<out T> : IRaster<IRaster<T>> | |
{ | |
} | |
public abstract class AbstractRaster<T> : IRaster<T> | |
{ | |
protected int _sizeX; | |
protected int _sizeY; | |
protected AbstractRaster(int sizeX, int sizeY) | |
{ | |
_sizeX = sizeX; | |
_sizeY = sizeY; | |
} | |
protected AbstractRaster() | |
{ | |
} | |
public int SizeX | |
{ | |
get { return _sizeX; } | |
} | |
public int SizeY | |
{ | |
get { return _sizeY; } | |
} | |
public abstract T Get(int x, int y); | |
public abstract IEnumerable<T> Enumerate(); | |
protected int Index(int x, int y) | |
{ | |
return y * _sizeX + x; | |
} | |
} | |
#region Source Rasters | |
public class RawRaster : AbstractRaster<float>, IWritableRaster<float> | |
{ | |
protected readonly float[] _raster; | |
public RawRaster(int sizeX, int sizeY) | |
{ | |
if (sizeX < 0) throw new ArgumentOutOfRangeException("sizeX"); | |
if (sizeY < 0) throw new ArgumentOutOfRangeException("sizeY"); | |
_sizeX = sizeX; | |
_sizeY = sizeY; | |
_raster = new float[sizeX * sizeY]; | |
} | |
public RawRaster(int sizeX, int sizeY, float[] raster) | |
: this(sizeX, sizeY) | |
{ | |
if (_raster.Length != raster.Length) | |
throw new ArgumentException("raster is wrong size"); | |
Array.Copy(raster, _raster, raster.Length); | |
} | |
public RawRaster(string path) | |
{ | |
if (string.IsNullOrWhiteSpace(path)) | |
throw new ArgumentNullException("path"); | |
using (FileStream stream = File.OpenRead(path)) | |
using (var reader = new BinaryReader(stream)) | |
{ | |
_sizeY = reader.ReadInt32BE(); | |
_sizeX = reader.ReadInt32BE(); | |
_raster = new float[_sizeX * _sizeY]; | |
Read(stream); | |
} | |
} | |
public override float Get(int x, int y) | |
{ | |
Debug.Assert(x >= 0 && x < SizeX); | |
Debug.Assert(y >= 0 && y < SizeY); | |
return _raster[Index(x, y)]; | |
} | |
public void Set(int x, int y, float value) | |
{ | |
Debug.Assert(x >= 0 && x < SizeX); | |
Debug.Assert(y >= 0 && y < SizeY); | |
_raster[Index(x, y)] = value; | |
} | |
public override IEnumerable<float> Enumerate() | |
{ | |
return _raster; | |
} | |
private void Read(Stream stream) | |
{ | |
int numPixels = _sizeX * _sizeY; | |
int numBytes = numPixels * 2; | |
// Read raw data | |
var rawRaster = new byte[numBytes]; | |
int bytesRead = stream.Read(rawRaster, 0, numBytes); | |
if (bytesRead != numBytes) | |
throw new IOException("Insufficient data"); | |
// Convert to shorts | |
Memory.Swap2(rawRaster); | |
var shortRaster = new short[numPixels]; | |
Buffer.BlockCopy(rawRaster, 0, shortRaster, 0, numBytes); | |
// Convert to floats | |
for (int i = 0; i < numPixels; i++) | |
_raster[i] = shortRaster[i]; | |
} | |
public void Save(string path) | |
{ | |
if (string.IsNullOrWhiteSpace(path)) | |
throw new ArgumentNullException("path"); | |
using (FileStream stream = File.OpenWrite(path)) | |
using (var writer = new BinaryWriter(stream)) | |
{ | |
writer.WriteBE(_sizeY); | |
writer.WriteBE(_sizeX); | |
// Convert to short | |
int numPixels = _sizeX * _sizeY; | |
var shortRaster = new short[numPixels]; | |
for (int i = 0; i < numPixels; i++) | |
shortRaster[i] = (short)_raster[i]; | |
// Convert to bytes | |
int numBytes = numPixels * 2; | |
var rawRaster = new byte[numBytes]; | |
Buffer.BlockCopy(shortRaster, 0, rawRaster, 0, numBytes); | |
Memory.Swap2(rawRaster); | |
// Write raw data | |
stream.Write(rawRaster, 0, numBytes); | |
} | |
} | |
} | |
#endregion | |
#region Filter Rasters | |
public class CropFilter<T> : AbstractRaster<T> | |
{ | |
protected readonly int _offsetX; | |
protected readonly int _offsetY; | |
protected readonly IRaster<T> _source; | |
public CropFilter(IRaster<T> source, int offsetX, int offsetY, int sizeX, int sizeY) | |
: base(sizeX, sizeY) | |
{ | |
_source = source; | |
_offsetX = offsetX; | |
_offsetY = offsetY; | |
} | |
public override T Get(int x, int y) | |
{ | |
return _source.Get(_offsetX + x, _offsetY + y); | |
} | |
public override IEnumerable<T> Enumerate() | |
{ | |
int limitX = _offsetX + _sizeX; | |
int limitY = _offsetY + _sizeY; | |
for (int y = _offsetY; y < limitY; y++) | |
for (int x = _offsetX; x < limitX; x++) | |
yield return _source.Get(x, y); | |
} | |
} | |
public class SubsampleFilter<T> : AbstractRaster<T> | |
{ | |
protected readonly IRaster<T> _source; | |
protected readonly int _offsetX; | |
protected readonly int _offsetY; | |
protected readonly int _stepX; | |
protected readonly int _stepY; | |
public SubsampleFilter(IRaster<T> source, int stepX, int stepY, int offsetX = 0, int offsetY = 0) | |
{ | |
if (source.SizeX % stepX != 0) | |
throw new ArgumentException("stepX not a multiple of source.SizeX"); | |
if (source.SizeY % stepY != 0) | |
throw new ArgumentException("stepY not a multiple of source.SizeY"); | |
if (offsetX < 0 || offsetX >= stepX) | |
throw new ArgumentOutOfRangeException("offsetX"); | |
if (offsetY < 0 || offsetY >= stepY) | |
throw new ArgumentOutOfRangeException("offsetY"); | |
_source = source; | |
_stepX = stepX; | |
_stepY = stepY; | |
_offsetX = offsetX; | |
_offsetY = offsetY; | |
_sizeX = source.SizeX / stepX; | |
_sizeY = source.SizeY / stepY; | |
} | |
public override T Get(int x, int y) | |
{ | |
return _source.Get(_stepX * x + _offsetX, _stepY * y + _offsetY); | |
} | |
public override IEnumerable<T> Enumerate() | |
{ | |
int limitX = _source.SizeX; | |
int limitY = _source.SizeY; | |
for (int y = _offsetY; y < limitY; y += _stepY) | |
for (int x = _offsetX; x < limitX; x += _stepX) | |
yield return _source.Get(x, y); | |
} | |
} | |
public class TileFilter<T> : IRasterCollection<T> | |
{ | |
protected readonly IRaster<T> _source; | |
protected readonly int _tileSizeX; | |
protected readonly int _tileSizeY; | |
protected readonly IRaster<T>[] _tiles; | |
public TileFilter(IRaster<T> source, int tileSizeX, int tileSizeY) | |
{ | |
_source = source; | |
if (source.SizeX % tileSizeX != 0) | |
throw new ArgumentException("Cannot divide tiles evenly"); | |
if (source.SizeY % tileSizeY != 0) | |
throw new ArgumentException("Cannot divide tiles evenly"); | |
_tileSizeX = tileSizeX; | |
_tileSizeY = tileSizeY; | |
_tiles = new IRaster<T>[SizeX * SizeY]; | |
} | |
public int SizeX | |
{ | |
get { return _source.SizeX / _tileSizeX; } | |
} | |
public int SizeY | |
{ | |
get { return _source.SizeY / _tileSizeY; } | |
} | |
public IRaster<T> Get(int x, int y) | |
{ | |
int index = y * SizeX + x; | |
if (_tiles[index] == null) | |
_tiles[index] = new CropFilter<T>(_source, x * _tileSizeX, y * _tileSizeY, _tileSizeX, _tileSizeY); | |
return _tiles[index]; | |
} | |
public IEnumerable<IRaster<T>> Enumerate() | |
{ | |
return _tiles; | |
} | |
} | |
public class Concrete<T> : IRaster<T> | |
{ | |
protected readonly T[] _raster; | |
protected readonly IRaster<T> _source; | |
public Concrete(IRaster<T> source) | |
{ | |
_source = source; | |
IEnumerable<T> enumerable = source.Enumerate(); | |
_raster = enumerable as T[] ?? enumerable.ToArray(); | |
} | |
public int SizeX | |
{ | |
get { return _source.SizeX; } | |
} | |
public int SizeY | |
{ | |
get { return _source.SizeY; } | |
} | |
public T Get(int x, int y) | |
{ | |
Debug.Assert(x >= 0 && x < SizeX, "x out of range"); | |
Debug.Assert(y >= 0 && y < SizeY, "y out of range"); | |
return _raster[y * SizeX + x]; | |
} | |
public IEnumerable<T> Enumerate() | |
{ | |
return _raster; | |
} | |
} | |
public class ColumnFilter<T> : TileFilter<T> | |
{ | |
public ColumnFilter(IRaster<T> source) | |
: base(source, 1, source.SizeY) | |
{ | |
} | |
} | |
public class RowFilter<T> : TileFilter<T> | |
{ | |
public RowFilter(IRaster<T> source) | |
: base(source, source.SizeX, 1) | |
{ | |
} | |
} | |
#endregion | |
#region Algorithm | |
public interface IAlgorithm | |
{ | |
double Calculate(IEnumerable<float> input); | |
} | |
public class MeanAlgorithm : IAlgorithm | |
{ | |
public double Calculate(IEnumerable<float> input) | |
{ | |
return input.Average(); | |
} | |
} | |
public class MedianAlgorithm : IAlgorithm | |
{ | |
public double Calculate(IEnumerable<float> input) | |
{ | |
float[] inputArray = input as float[] ?? input.ToArray(); | |
if (!inputArray.Any()) | |
throw new ArgumentException("input is empty"); | |
Array.Sort(inputArray); | |
int len = inputArray.Length; | |
if (len % 2 == 0) | |
return (inputArray[len / 2 - 1] + inputArray[len / 2]) * .5; | |
return inputArray[len / 2]; | |
} | |
} | |
public class AlgorithmRunner : IRaster<double> | |
{ | |
private readonly IAlgorithm _algorithm; | |
private readonly IRasterCollection<float> _source; | |
public AlgorithmRunner(IRasterCollection<float> source, IAlgorithm algorithm) | |
{ | |
_source = source; | |
_algorithm = algorithm; | |
} | |
public int SizeX | |
{ | |
get { return _source.SizeX; } | |
} | |
public int SizeY | |
{ | |
get { return _source.SizeY; } | |
} | |
public double Get(int x, int y) | |
{ | |
Debug.Assert(x >= 0 && x < SizeX, "x out of range"); | |
Debug.Assert(y >= 0 && y < SizeY, "y out of range"); | |
return _algorithm.Calculate(_source.Get(x, y).Enumerate()); | |
} | |
public IEnumerable<double> Enumerate() | |
{ | |
int sizeX = SizeX; | |
int sizeY = SizeY; | |
for (int y = 0; y < sizeY; y++) | |
for (int x = 0; x < sizeX; x++) | |
yield return Get(x, y); | |
} | |
} | |
#endregion | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment