Skip to content
This repository was archived by the owner on Feb 4, 2021. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions filters.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
proc kernel*[T,U](img: Tensor[T], kernel: Tensor[U], scale: U = 1, offset: U = 0): Tensor[T] =
## Applies a kernel matrix to an image and divides the outputs by scale factor
## and them sun offset.
## For more information see https://en.wikipedia.org/wiki/Kernel_(image_processing)
##
## Implementation details:
## This functions does not flip the kernel, so it does image correlation
## instead of convolution.
## The padding borders of the image is replaced with the nearest neighbourhood
## border.

assert kernel.width == kernel.height
let kernel = kernel.bc([img.channels, kernel.height, kernel.width])
let pad = (kernel.width - 1) div 2
var correlated_img = img.correlate2d(kernel, pad, PadNearest)
if scale != 1.U:
correlated_img /= scale
if offset != 0.U:
correlated_img += offset.bc(correlated_img.shape)
result = quantize_bytes(correlated_img, T)

proc filter_blur*[T](img: Tensor[T]): Tensor[T] =
## Blur an image using a predefied kernel
img.kernel([[
[1, 1, 1, 1, 1],
[1, 0, 0, 0, 1],
[1, 0, 0, 0, 1],
[1, 0, 0, 0, 1],
[1, 1, 1, 1, 1]
]].toTensor(), 16)

proc filter_contour*[T](img: Tensor[T]): Tensor[T] =
## Contour an image using a predefied kernel
img.kernel([[
[-1, -1, -1],
[-1, 8, -1],
[-1, -1, -1]
]].toTensor(), 1, 255)

proc filter_detail*[T](img: Tensor[T]): Tensor[T] =
## Detail an image using a predefied kernel
img.kernel([[
[ 0, -1, 0],
[-1, 10, -1],
[ 0, -1, 0],
]].toTensor(), 6)

proc filter_edge_enhance*[T](img: Tensor[T]): Tensor[T] =
## Enhance edges of an image using a predefied kernel
img.kernel([[
[-1, -1, -1],
[-1, 10, -1],
[-1, -1, -1],
]].toTensor(), 2)

proc filter_edge_enhance_more*[T](img: Tensor[T]): Tensor[T] =
## Enhance edges of an image using a predefied kernel
img.kernel([[
[-1, -1, -1],
[-1, 9, -1],
[-1, -1, -1],
]].toTensor(), 1)

proc filter_emboss*[T](img: Tensor[T]): Tensor[T] =
## Enhance an image using a predefied kernel
img.kernel([[
[-1, 0, 0],
[0, 1, 0],
[0, 0, 0],
]].toTensor(), 1, 128)

proc filter_sharpen*[T](img: Tensor[T]): Tensor[T] =
## Sharpen an image using a predefied kernel
img.kernel([[
[-2, -2, -2],
[-2, 32, -2],
[-2, -2, -2],
]].toTensor(), 16)

proc filter_smooth*[T](img: Tensor[T]): Tensor[T] =
## Smooth an image using a predefied kernel
img.kernel([[
[1, 1, 1],
[1, 5, 1],
[1, 1, 1],
]].toTensor(), 13)

proc filter_find_edges*[T](img: Tensor[T]): Tensor[T] =
## Find edges of image using a predefied kernel
img.kernel([[
[-1, -1, -1],
[-1, 8, -1],
[-1, -1, -1],
]].toTensor(), 1)

proc filter_smooth_more*[T](img: Tensor[T]): Tensor[T] =
## Smooth more an image using a predefied kernel
img.kernel([[
[1, 1, 1, 1, 1],
[1, 5, 5, 5, 1],
[1, 5, 44, 5, 1],
[1, 5, 5, 5, 1],
[1, 1, 1, 1, 1]
]].toTensor(), 100)
106 changes: 106 additions & 0 deletions imageio.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
proc channels*[T](img: Tensor[T]): int {.inline.} =
## Return number of channels of the image
img.shape[^3]

proc height*[T](img: Tensor[T]): int {.inline.} =
## Return height of the image
img.shape[^2]

proc width*[T](img: Tensor[T]): int {.inline.} =
## Return width of the image
img.shape[^1]

proc hwc2chw*[T](img: Tensor[T]): Tensor[T] =
## Convert image from HxWxC convetion to the CxHxW convention,
## where C,W,H stands for channels, width, height, note that this library
## only works with CxHxW images for optimization and internal usage reasons
## using CxHxW for images is also a common approach in deep learning
img.permute(2, 0, 1)

proc chw2hwc*[T](img: Tensor[T]): Tensor[T] =
## Convert image from CxHxW convetion to the HxWxC convention,
## where C,W,H stands for channels, width, height, note that this library
## only works with CxHxW images for optimization and internal usage reasons
## using CxHxW for images is also a common approach in deep learning
img.permute(1, 2, 0)

proc pixels*(img: Tensor[uint8]): seq[uint8] =
# Return contiguous pixel data in the HxWxC convetion, method intended
# to use for interfacing with other libraries
img.chw2hwc().asContiguous().data

proc load*(filename: string, desired_channels: int = 0): Tensor[uint8] =
## Load image from file, with the desired number of channels,
## into a contiguous CxHxW Tensor[uint8]. Desired channels defaults to 0 meaning
## that it will auto detect the number of channels, the returned image tensor
## will be in the CxHxW format even for images with a single channel.
##
## Supports PNG, JPG, BMP, TGA and HDR formats
##
## On error an IOError exception will be thrown
var width, height, channels: int
try:
let pixels = stbi.load(filename, width, height, channels, desired_channels)
result = pixels.toTensor.reshape([height, width, channels]).hwc2chw().asContiguous()
assert(desired_channels == 0 or channels == desired_channels)
except STBIException:
raise newException(IOError, getCurrentExceptionMsg())

proc loadFromMemory*(contents: string, desired_channels: int = 0): Tensor[uint8] =
## Like load but loads from memory, the contents must be a buffer
## for a supported image format
var width, height, channels: int
let pixels = stbi.loadFromMemory(cast[seq[uint8]](toSeq(contents.items)), width, height, channels, desired_channels)
result = pixels.toTensor.reshape([height, width, channels]).hwc2chw().asContiguous()
assert(desired_channels == 0 or channels == desired_channels)

proc loadFromDir*(dir: string, desired_channels: int = 0): seq[Tensor[uint8]] =
## Load batch of images from a directory into a seq of tensors,
## the load is non recursive, throws an IOError exception on
## error.

if not dirExists(dir):
raise newException(IOError, "Directory not found: " & dir)

result = newSeq[Tensor[uint8]]()
for kind, path in walkDir(dir):
if kind == pcFile:
result.add(load(path, desired_channels))

if result.len == 0:
raise newException(IOError, "No images found for loading in directory: " & dir)

proc save*(img: Tensor[uint8], filename: string, jpeg_quality: int = 100) =
## Save an image to a file, supports PNG, BMP, TGA and JPG.
## Argument `jpeg_quality` can be passed to inform the saving
## quality from a range 0 to 100, defaults to 100
var ok = false
if filename.endsWith(".png"):
ok = stbiw.writePNG(filename, img.width, img.height, img.channels, img.pixels)
elif filename.endsWith(".bmp"):
ok = stbiw.writeBMP(filename, img.width, img.height, img.channels, img.pixels)
elif filename.endsWith(".tga"):
ok = stbiw.writeTGA(filename, img.width, img.height, img.channels, img.pixels)
elif filename.endsWith(".jpg"):
ok = stbiw.writeJPG(filename, img.width, img.height, img.channels, img.pixels, jpeg_quality)

if not ok:
raise newException(IOError, "Failed to save image to a file: " & filename)

proc toPNG*(img: Tensor[uint8]): seq[byte] =
## Convert an image to PNG into a string of bytes
return stbiw.writePNG(img.width, img.height, img.channels, img.pixels)

proc toBMP*(img: Tensor[uint8]): seq[byte] =
## Convert an image to BMP into a string of bytes
return stbiw.writeBMP(img.width, img.height, img.channels, img.pixels)

proc toTGA*(img: Tensor[uint8]): seq[byte] =
## Convert an image to TGA into a string of bytes
return stbiw.writeTGA(img.width, img.height, img.channels, img.pixels)

proc toJPG*(img: Tensor[uint8], quality: int = 100): seq[byte] =
## Convert an image to JPG into a string of bytes.
## Argument `jpeg_quality` can be passed to inform the saving
## quality from a range 0 to 100, defaults to 100
return stbiw.writeJPG(img.width, img.height, img.channels, img.pixels, quality)
81 changes: 81 additions & 0 deletions scale.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
type
ScaleMode* = enum
ScaleNearest = 0
ScaleBilinear = 1

proc round_pixel(a: float32, U: typedesc): U {.inline.} =
when U is uint8:
clamp(a + 0.5.float32, low(U).float32, high(U).float32).uint8
elif U is float32:
a.float32

proc scale_nearest[T](src: Tensor[T], width, height: int): Tensor[T] {.inline.} =
result = newTensor[T]([src.channels, height, width])
let
step_x = src.height.float32 / height.float32
step_y = src.width.float32 / width.float32
for c in 0..<src.channels:
for y in 0..<height:
let sy = (y.float32 * step_y).int
for x in 0..<width:
let sx = (x.float32 * step_x).int
result[c, y, x] = src[c, sy, sx]

proc scale_linear_vertical[T](src: Tensor[T], width, height: int): Tensor[T] {.inline.} =
result = newTensor[T]([src.channels, height, width])
let
step_y = src.height.float32 / height.float32
max_sy = src.height - 1
for c in 0..<src.channels:
for y in 0..<height:
let
sy = y.float32 * step_y
say = sy.int
sby = min(say+1, max_sy)
sa_factor = sby.float32 - sy
sb_factor = 1.0f - sa_factor
for x in 0..<width:
let
sx = x
sa = src[c, say, sx].float32 * sa_factor
sb = src[c, sby, sx].float32 * sb_factor
result[c, y, x] = round_pixel(sa + sb, T)

proc scale_linear_horizontal[T](src: Tensor[T], width, height: int): Tensor[T] {.inline.} =
result = newTensor[T]([src.channels, height, width])
let
step_x = src.width.float32 / width.float32
max_sx = src.width - 1
for c in 0..<src.channels:
for y in 0..<height:
let sy = y
for x in 0..<width:
let
sx = x.float32 * step_x
sax = sx.int
sbx = min(sax+1, max_sx)
sa_factor = sbx.float32 - sx
sb_factor = 1.0f - sa_factor
sa = src[c, sy, sax].float32 * sa_factor
sb = src[c, sy, sbx].float32 * sb_factor
result[c, y, x] = round_pixel(sa + sb, T)

proc scale_bilinear[T](src: Tensor[T], width, height: int): Tensor[T] {.inline.} =
var tmp : Tensor[T]
if height != src.height:
tmp = scale_linear_vertical(src, src.width, height)
else:
shallowCopy(tmp, src)
if width != src.width:
result = scale_linear_horizontal(tmp, width, height)
else:
result = tmp

proc scale*[T](src: Tensor[T], width, height: int, mode: ScaleMode = ScaleNearest): Tensor[T] =
## Scale an image to a new size, suppored modes are nearest, and bilinear,
## defaults to nearest.
case mode:
of ScaleNearest:
return scale_nearest(src, width, height)
of ScaleBilinear:
return scale_bilinear(src, width, height)
Loading