diff --git a/filters.nim b/filters.nim new file mode 100644 index 0000000..95e8425 --- /dev/null +++ b/filters.nim @@ -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) diff --git a/imageio.nim b/imageio.nim new file mode 100644 index 0000000..4c4986d --- /dev/null +++ b/imageio.nim @@ -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) diff --git a/scale.nim b/scale.nim new file mode 100644 index 0000000..e284f6c --- /dev/null +++ b/scale.nim @@ -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..= height or col >= width: + case mode: + of PadConstant: + result[c, offset_col + w] = pad_constant + of PadNearest: + result[c, offset_col + w] = input[c_offset, clamp(row, 0, height-1), clamp(col, 0, width-1)] + else: + result[c, offset_col + w] = input[c_offset, row, col] + +proc correlate2d*[T,U](input: Tensor[T], weights: Tensor[U], pad: int = 0, mode: PadMode = PadConstant, cval: U = 0): Tensor[int] = + ## Correlate an image with the given kernel weights, this is a convolution + ## without flipping the kernel + let ksize = weights.width + + assert input.rank == 3 + assert weights.rank == 3 + assert weights.width == weights.height + assert ksize > 0 and ksize mod 2 == 1 + + let + channels = input.channels + height = input.height + (2 * pad) - ksize + 1 + width = input.width + (2 * pad) - ksize + 1 + channel_ksize = ksize*ksize + + var w = weights.reshape([channels, 1, ksize*ksize]) + var x = im2col(input.astype(U), ksize, pad, mode, cval).reshape([channels, channel_ksize, height*width]) + var res_channels = newSeq[Tensor[U]](channels) + + for c in 0.. 0: + params["win"] = % window + + let url = "http://" & self.host & ":" & $self.port & "/events" + postJson(url, params) + +proc image*(vis: VisdomClient, + img: Tensor[uint8], + window: string = "", + caption: string = "", + title: string = "") = + ## Show image into visdom with the given title and specified window + + let opts = %*{ + "title": if title.len > 0: title else: window, + "height": img.height, + "width": img.width + } + + let data = %*[{ + "content": { + "src": img.toJPG().webEncodeData("image/jpg"), + "caption": caption + }, + "type": "image" + }] + + vis.sendEvent(opts, data, window)