Skip to content

Commit

Permalink
impl: vectorized matrix ops
Browse files Browse the repository at this point in the history
  • Loading branch information
4e6 committed Feb 6, 2021
1 parent 3d2b471 commit db3e25f
Show file tree
Hide file tree
Showing 12 changed files with 305 additions and 14 deletions.
22 changes: 22 additions & 0 deletions distribution/std-lib/Base/src/Data/Vector.enso
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,28 @@ type Vector
map_with_index : (Int -> Any -> Any) -> Vector
map_with_index function = here.new this.length i-> function i (this.at i)

## Applies a function to each element of the vector, returning the vector of
results.

> Example
Replace elements of the vector wiht `[1, 0]`, flattening the result.
[1, 2, 3] . flat_map (_ -> [1, 0]) == [1, 0, 1, 0, 1, 0]
> Example
Replace `1` with `[1, 1]`, ignoring zeroes.
[0, 1, 0] . flat_map (i -> if i == 1 then [1, 1] else i) == [0, 1, 1, 0]
> Example
Return existing vector when mapping an element to itself.
[1, 2, 3] . flat_map (i -> i) === [1, 2, 3]
flat_map : (Any -> Vector) -> Vector
flat_map function =
append builder elem =
res = function elem
case res of
Vector _ -> res.each builder.append
_ -> builder.append res
builder
this.fold Builder.new append . to_vector

## Applies a function to each element of the vector.

Unlike `map`, this method does not return the individual results,
Expand Down
2 changes: 2 additions & 0 deletions distribution/std-lib/Image/src/Data/Color.enso
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,5 @@ from_array arr =
if arr.length == 3 then Channels_3 (arr.at 0) (arr.at 1) (arr.at 2) else
if arr.length == 4 then Channels_4 (arr.at 0) (arr.at 1) (arr.at 2) (arr.at 3) else
Error.throw (Unsupported_Channels_Number arr.length)

Vector.Vector.to_color = here.from_vector this
9 changes: 9 additions & 0 deletions distribution/std-lib/Image/src/Data/Color/Internal.enso
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from Base import all
import Image.Data.Color

polyglot java import org.opencv.core.Scalar

## PRIVATE
add color x = case color of
Color.Channels_1 c1 -> Color.Channels_1 c1+x
Expand Down Expand Up @@ -28,3 +30,10 @@ divide color x = case color of
Color.Channels_2 c1 c2 -> Color.Channels_2 c1/x c2/x
Color.Channels_3 c1 c2 c3 -> Color.Channels_3 c1/x c2/x c3/x
Color.Channels_4 c1 c2 c3 c4 -> Color.Channels_4 c1/x c2/x c3/x c4/x

## PRIVATE
to_scalar color = case color of
Color.Channels_1 c1 -> Scalar.new c1
Color.Channels_2 c1 c2 -> Scalar.new c1 c2
Color.Channels_3 c1 c2 c3 -> Scalar.new c1 c2 c3
Color.Channels_4 c1 c2 c3 c4 -> Scalar.new c1 c2 c3 c4
57 changes: 46 additions & 11 deletions distribution/std-lib/Image/src/Data/Matrix.enso
Original file line number Diff line number Diff line change
@@ -1,28 +1,33 @@
from Base import all
import Image.Data.Color
import Image.Data.Matrix.Internal
import Image.Data.Pixel

polyglot java import org.enso.image.data.Matrix as Java_Matrix
polyglot java import org.opencv.core.Core
polyglot java import org.opencv.core.Scalar

type Index_Out_Of_Bounds_Error index

type Matrix

type Matrix opencv_mat

columns : Integer
columns = this.opencv_mat.cols

cols : Integer
cols = this.columns

rows : Integer
rows = this.opencv_mat.rows

columns : Integer
columns = this.opencv_mat.cols

copy : Matrix
copy =
mat_type = Polyglot.invoke this.opencv_mat "type" (Array.new 0)
new_mat = Java_Matrix.zeros this.opencv_mat.rows this.opencv_mat.cols mat_type
this.opencv_mat.copyTo new_mat
Matrix new_mat
copy = Matrix this.opencv_mat.clone

get : Integer -> Integer -> Vector ! Index_Out_Of_Bounds_Error
get row column =
if (row < 0) || (row >= this.rows) then Error.throw (Index_Out_Of_Bounds_Error row) else
if (column < 0) || (column >= this.columns) then Error.throw (Index_Out_Of_Bounds_Error column) else
arr = this.opencv_mat.get row column
Vector.Vector arr

map : (Pixel -> Pixel) -> Matrix
map f =
Expand All @@ -41,3 +46,33 @@ type Matrix
Color.Channels_2 c1 c2 -> this.opencv_mat.put pixel.row pixel.col c1 c2
Color.Channels_3 c1 c2 c3 -> this.opencv_mat.put pixel.row pixel.col c1 c2 c3
Color.Channels_4 c1 c2 c3 c4 -> this.opencv_mat.put pixel.row pixel.col c1 c2 c3 c4

## Calculates the per-element sum of two matrices or a matrix and a scalar.
+ : (Number | Vector | Color | Matrix) -> Matrix ! Color.Unsupported_Channels_Number
+ value =
Panic.recover (Internal.core_op this.opencv_mat value (Core.add _ _ _)) . catch Internal.core_op_handler

## Calculates the per-element difference between two matrices or matrix and
a scalar.
- : (Number | Vector | Color | Matrix) -> Matrix ! Color.Unsupported_Channels_Number
- value = Panic.recover (Internal.core_op this.opencv_mat value (Core.subtract _ _ _)) . catch Internal.core_op_handler

## Calculates the per-element scaled product of two matrices or a matrix and
a scalar.
* : (Number | Vector | Color | Matrix) -> Matrix ! Color.Unsupported_Channels_Number
* value = Panic.recover (Internal.core_op this.opencv_mat value (Core.multiply _ _ _)) . catch Internal.core_op_handler

## Performs per-element division of two matrices or a matrix and a scalar.
/ : (Number | Vector | Color | Matrix) -> Matrix ! Color.Unsupported_Channels_Number
/ value = Panic.recover (Internal.core_op this.opencv_mat value (Core.divide _ _ _)) . catch Internal.core_op_handler

## Calculates the sum of the elements.
sum : Color
sum =
scalar = Core.sumElems this.opencv_mat
Color.from_array scalar.val

to_vector : Vector
to_vector =
arr = Java_Matrix.to_vector this.opencv_mat
Vector.Vector arr
40 changes: 40 additions & 0 deletions distribution/std-lib/Image/src/Data/Matrix/Internal.enso
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from Base import all
import Image.Data.Color
import Image.Data.Color.Internal as Color_Internal
import Image.Data.Matrix

polyglot java import org.enso.image.data.Matrix as Java_Matrix
polyglot java import org.opencv.core.Mat
polyglot java import org.opencv.core.Scalar

## PRIVATE
create rows cols mat_type values =
opencv_scalar = Scalar.new values.to_array
opencv_mat = Mat.new rows cols mat_type opencv_scalar
Matrix.Matrix opencv_mat

## PRIVATE
core_op mat value op =
new_mat = Java_Matrix.zeros mat
scalar = case value of
Vector.Vector arr ->
Color_Internal.to_scalar (Color.from_array arr) . catch (Panic.throw _)
Color.Channels_1 c1 ->
Scalar.new c1
Color.Channels_2 c1 c2 ->
Scalar.new c1 c2
Color.Channels_3 c1 c2 c3 ->
Scalar.new c1 c2 c3
Color.Channels_4 c1 c2 c3 c4 ->
Scalar.new c1 c2 c3 c4
Matrix.Matrix m ->
m
_ ->
Scalar.new value
op mat scalar new_mat
Matrix.Matrix new_mat

## PRIVATE
core_op_handler error = case error of
Color.Unsupported_Channels_Number _ -> Error.throw error
_ -> Panic.throw error
1 change: 1 addition & 0 deletions distribution/std-lib/Image/src/Data/Pixel.enso
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ type Pixel
column : Integer
column = this.col

copy : Integer -> Integer -> Color -> Pixel
copy row=this.row column=this.col color=this.color =
Pixel row column color
65 changes: 65 additions & 0 deletions image/src/main/java/org/enso/image/data/Matrix.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,75 @@
package org.enso.image.data;

import org.opencv.core.CvType;
import org.opencv.core.Mat;

public class Matrix {

// type depth constants
public static final int
CV_8U = CvType.CV_8U,
CV_8UC1 = CvType.CV_8UC1,
CV_8S = CvType.CV_8S,
CV_16U = CvType.CV_16U,
CV_16S = CvType.CV_16S,
CV_32S = CvType.CV_32S,
CV_32F = CvType.CV_32F,
CV_64F = CvType.CV_64F,
CV_16F = CvType.CV_16F;

public static int CV_8UC(int channels) {
return CvType.CV_8UC(channels);
}

public static Mat zeros(Mat m) {
return Mat.zeros(m.size(), m.type());
}

public static Mat zeros(int rows, int cols, int type) {
return Mat.zeros(rows, cols, type);
}

public static Mat ones(int rows, int cols, int type) {
return Mat.ones(rows, cols, type);
}

public static Mat eye(int rows, int cols, int type) {
return Mat.eye(rows, cols, type);
}

public static Object to_vector(Mat mat) {
switch (dataSize(mat.type())) {
case CvType.CV_8U:
case CvType.CV_8S:
byte[] buf8 = new byte[(int) mat.total() * mat.channels()];
mat.get(0, 0, buf8);
return buf8;
case CvType.CV_16U:
case CvType.CV_16S:
short[] buf16 = new short[(int) mat.total() * mat.channels()];
mat.get(0, 0, buf16);
return buf16;
case CvType.CV_32S:
int[] buf32s = new int[(int) mat.total() * mat.channels()];
mat.get(0, 0, buf32s);
return buf32s;
case CvType.CV_32F:
float[] buf32f = new float[(int) mat.total() * mat.channels()];
mat.get(0, 0, buf32f);
return buf32f;
case CvType.CV_64F:
double[] buf64f = new double[(int) mat.total() * mat.channels()];
mat.get(0, 0, buf64f);
return buf64f;
case CvType.CV_16F:
short[] buf16f = new short[(int) mat.total() * mat.channels()];
mat.get(0, 0, buf16f);
return buf16f;
}
return null;
}

private static int dataSize(int type) {
return CvType.ELEM_SIZE(type) / CvType.channels(type);
}
}
10 changes: 8 additions & 2 deletions test/Image_Tests/src/Data/Color_Spec.enso
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,14 @@ spec =
Color.from_vector [1, 2] . should_equal (Color.Channels_2 1 2)
Color.from_vector [1, 2, 3] . should_equal (Color.Channels_3 1 2 3)
Color.from_vector [1, 2, 3, 4] . should_equal (Color.Channels_4 1 2 3 4)
Test.expect_error_with (Color.from_vector []) (Color.Unsupported_Channels_Number 0)
Test.expect_error_with (Color.from_vector [1, 2, 3, 4, 5]) (Color.Unsupported_Channels_Number 5)
Test.expect_error_with (Color.from_vector []) Color.Unsupported_Channels_Number
Test.expect_error_with (Color.from_vector [1, 2, 3, 4, 5]) Color.Unsupported_Channels_Number
Test.specify "should convert vector to a color" <|
[1].to_color . should_equal (Color.Channels_1 1)
[1, 2].to_color . should_equal (Color.Channels_2 1 2)
[1, 2, 3].to_color . should_equal (Color.Channels_3 1 2 3)
[1, 2, 3, 4].to_color . should_equal (Color.Channels_4 1 2 3 4)
Test.expect_error_with ([].to_color) Color.Unsupported_Channels_Number
Test.specify "should convert to vector" <|
Color.Channels_1 1 . to_vector . should_equal [1]
Color.Channels_2 1 2 . to_vector . should_equal [1, 2]
Expand Down
103 changes: 103 additions & 0 deletions test/Image_Tests/src/Data/Matrix_Spec.enso
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
from Base import all
import Image.Data.Matrix
import Image.Data.Color
import Test

polyglot java import org.enso.image.data.Matrix as Java_Matrix

spec =
Test.group "Matrix channels 1" <|
zeros = Matrix.Matrix (Java_Matrix.zeros 3 3 0)
ones = Matrix.Matrix (Java_Matrix.ones 3 3 0)
eye = Matrix.Matrix (Java_Matrix.eye 3 3 0)
Test.specify "should convert to vector" <|
zeros.to_vector . should_equal (Vector.fill 9 0)
ones.to_vector . should_equal (Vector.fill 9 1)
eye.to_vector . should_equal [1, 0, 0, 0, 1, 0, 0, 0, 1]
Test.specify "should get value" <|
eye.get 0 0 . should_equal [1]
eye.get 1 0 . should_equal [0]
eye.get 1 1 . should_equal [1]
Test.expect_error_with (eye.get 10 10) Matrix.Index_Out_Of_Bounds_Error
Test.expect_error_with (eye.get -1 -1) Matrix.Index_Out_Of_Bounds_Error

Test.specify "should add scalar" <|
(zeros + 1).to_vector . should_equal (Vector.fill 9 1)
(ones + 1).to_vector . should_equal (Vector.fill 9 2)
Test.specify "should add vector" <|
(zeros + [1]).to_vector . should_equal (Vector.fill 9 1)
(ones + [1, 1]).to_vector . should_equal (Vector.fill 9 2)
Test.expect_error_with (eye + []) Color.Unsupported_Channels_Number
Test.specify "should add color" <|
c1 = Color.from_vector [1]
(zeros + c1).to_vector . should_equal (Vector.fill 9 1)
Test.specify "should add matrix" <|
(zeros + ones).to_vector . should_equal (Vector.fill 9 1)
(eye + eye).to_vector . should_equal [2, 0, 0, 0, 2, 0, 0, 0, 2]
(ones + eye).to_vector . should_equal [2, 1, 1, 1, 2, 1, 1, 1, 2]

Test.specify "should subtract scalar" <|
(ones - 1).to_vector . should_equal (Vector.fill 9 0)
(zeros - 1).to_vector . should_equal (Vector.fill 9 0)
Test.specify "should subtract vector" <|
(zeros - [1]).to_vector . should_equal (Vector.fill 9 0)
(ones - [1, 1]).to_vector . should_equal (Vector.fill 9 0)
Test.expect_error_with (eye - []) Color.Unsupported_Channels_Number
Test.specify "should subtract color" <|
c1 = Color.from_vector [1]
(ones - c1).to_vector . should_equal (Vector.fill 9 0)
Test.specify "should subtract matrix" <|
(ones - zeros).to_vector . should_equal (Vector.fill 9 1)
(ones - eye).to_vector . should_equal [0, 1, 1, 1, 0, 1, 1, 1, 0]

Test.specify "should multiply scalar" <|
(ones * 3).to_vector . should_equal (Vector.fill 9 3)
(zeros * 4).to_vector . should_equal (Vector.fill 9 0)
(eye * 5).to_vector . should_equal [5, 0, 0, 0, 5, 0, 0, 0, 5]
Test.specify "should multiply vector" <|
(zeros * [2]).to_vector . should_equal (Vector.fill 9 0)
(ones + [2, 2]).to_vector . should_equal (Vector.fill 9 3)
Test.expect_error_with (eye * []) Color.Unsupported_Channels_Number
Test.specify "should multiply color" <|
c1 = Color.from_vector [2]
(ones * c1).to_vector . should_equal (Vector.fill 9 2)
Test.specify "should multiply matrix" <|
(ones * zeros).to_vector . should_equal (Vector.fill 9 0)
(ones * ones).to_vector . should_equal (Vector.fill 9 1)
(ones * eye).to_vector . should_equal [1, 0, 0, 0, 1, 0, 0, 0, 1]

Test.specify "should divide scalar" <|
(zeros / 2).to_vector . should_equal (Vector.fill 9 0)
(ones / 2).to_vector . should_equal (Vector.fill 9 0)
Test.specify "should divide vector" <|
(zeros / [2]).to_vector . should_equal (Vector.fill 9 0)
(ones / [2, 2]).to_vector . should_equal (Vector.fill 9 0)
Test.expect_error_with (eye / []) Color.Unsupported_Channels_Number
Test.specify "should divide color" <|
c1 = Color.from_vector [1]
(ones / c1).to_vector . should_equal (Vector.fill 9 1)
Test.specify "should divide matrix" <|
(zeros / ones).to_vector . should_equal (Vector.fill 9 0)
(ones / ones).to_vector . should_equal (Vector.fill 9 1)

Test.specify "should sum" <|
zeros.sum . should_equal (Color.from_vector [0, 0, 0, 0])
ones.sum . should_equal (Color.from_vector [9, 0, 0, 0])

Test.group "Matrix channels 3" <|
zeros = Matrix.Matrix (Java_Matrix.zeros 3 3 (Java_Matrix.CV_8UC 3))
ones = Matrix.Matrix (Java_Matrix.ones 3 3 (Java_Matrix.CV_8UC 3))
eye = Matrix.Matrix (Java_Matrix.eye 3 3 (Java_Matrix.CV_8UC 3))
zeros_idx = Vector.fill 9 0
#ones_idx = Vector.fill 1 0
eye_idx = [1, 0, 0, 0, 1, 0, 0, 0, 1]
Test.specify "should convert to vector" <|
zeros.to_vector . should_equal (Vector.fill 3*3*3 0)
ones.to_vector . should_equal (zeros_idx.flat_map (_ -> [1, 0, 0]))
eye.to_vector . should_equal (eye_idx.flat_map (i -> if i == 1 then [1, 0, 0] else [0, 0, 0]))
Test.specify "should get value" <|
eye.get 0 0 . should_equal [1, 0, 0]
eye.get 1 0 . should_equal [0, 0, 0]
eye.get 1 1 . should_equal [1, 0, 0]
Test.expect_error_with (eye.get 10 10) Matrix.Index_Out_Of_Bounds_Error
Test.expect_error_with (eye.get -1 -1) Matrix.Index_Out_Of_Bounds_Error
2 changes: 1 addition & 1 deletion test/Image_Tests/src/Io_Spec.enso
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ spec =
lena = Enso_Project.data / "lena.png"
mat = Io.read lena
mat.rows.should_equal 256
mat.cols.should_equal 256
mat.columns.should_equal 256
2 changes: 2 additions & 0 deletions test/Image_Tests/src/Main.enso
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import Test
import Image_Tests.Io_Spec
import Image_Tests.Data.Color_Spec
import Image_Tests.Data.Matrix_Spec

main = Test.Suite.runMain <|
Io_Spec.spec
Color_Spec.spec
Matrix_Spec.spec
Loading

0 comments on commit db3e25f

Please sign in to comment.