This post is first for me, nice to meet you!
In this post, I’d like to introduce Matft
library in swift to you.
Here is the source code link.
About Matft
Matft
is easy and fast to operate vector, matrix, tensor(N dimensional array), and even complex and image data!
The name Matft
comes from mat-rix operation library in swi-ft.
If you are accustomed to using Numpy
in python, Matft
will be the best option to achieve your matrix operation because Matft
's function names are similar to Numpy
's ones. (I'll show the examples later)
Many Numpy
's functions has already implemented in Matft
.
You can operate complex number as Numpy
can, and there is conversion function that converts CGImage
into MfArray
(Matft
's object of n-dimensional array) vice versa.
Also, because Matft
uses the Accelerate
framework (link), Matft
keeps high performance (some functions even achieves higher than Numpy
).
That's why I recommend to use Matft
when you'd like to operate vector, matrix, tensor(N dimensional array), and even complex and image data
Basic usage
MfType
Matft
's object to operate n-dimensional array is named MfArray
in Matft
. And MfArray
has mftype
property to pretend the n-dimensional array's type. (=Numpy
's dtype)
Pretending means that the stored data type will be Float
or Double
only even if you set MfType.Int
. So, if you input big number to MfArray, it may be cause to overflow or strange results in any calculation (+, -, *, /,... etc.). But I believe this is not problem in practical use.
MfType
supports many types such like;
public enum MfType: Int{
case None // Unsupportted
case Bool
case UInt8
case UInt16
case UInt32
case UInt64
case UInt
case Int8
case Int16
case Int32
case Int64
case Int
case Float
case Double
case Object // Unsupported
}
Initialization
In this section, I'll show the basic initialization of MfArray
.
From Swift Array
You can initialize MfArray
from Swift's array. The MfType
will be inferred properly.
let a = MfArray([[[ -8, -7, -6, -5],
[ -4, -3, -2, -1]],
[[ 0, 1, 2, 3],
[ 4, 5, 6, 7]]])
print(a)
/*
mfarray =
[[[ -8, -7, -6, -5],
[ -4, -3, -2, -1]],
[[ 0, 1, 2, 3],
[ 4, 5, 6, 7]]], type=Int, shape=[2, 2, 4]
*/
Arrange
You can create the sequence array.
let a = Matft.arange(start: -8, to: 8, by: 1, shape: [2,2,4])
print(a)
/*
mfarray =
[[[ -8, -7, -6, -5],
[ -4, -3, -2, -1]],
[[ 0, 1, 2, 3],
[ 4, 5, 6, 7]]], type=Int, shape=[2, 2, 4]
*/
Repeated Numbers
You can create the repeated numbers array such like numpy.ones() * N
.
let a = Matft.nums(Float(1), shape: [2,2,4])
print(a)
/*
mfarray =
[[[ 1.0, 1.0, 1.0, 1.0],
[ 1.0, 1.0, 1.0, 1.0]],
[[ 1.0, 1.0, 1.0, 1.0],
[ 1.0, 1.0, 1.0, 1.0]]], type=Float, shape=[2, 2, 4]
*/
Etc.
The other functions are below;
Matft | Numpy |
---|---|
*#Matft.shallowcopy | *numpy.copy |
*#Matft.deepcopy | copy.deepcopy |
Matft.nums | numpy.ones * N |
Matft.nums_like | numpy.ones_like * N |
Matft.arange | numpy.arange |
Matft.eye | numpy.eye |
Matft.diag | numpy.diag |
Matft.vstack | numpy.vstack |
Matft.hstack | numpy.hstack |
Matft.concatenate | numpy.concatenate |
*Matft.append | numpy.append |
*Matft.insert | numpy.insert |
*Matft.take | numpy.take |
MfData
and MfStructure
Conversion
MfArray
consists of two main parts, which are MfData
and MfStructure
.
MfData
stores the pointer of raw data, the current type (=MfType
), and the "stored" raw type (=StoredType
, only Float
or Double
). On the other hand, MfStructure
stores the shape
and strides
property, which represents the current array's structure and are same as Numpy
.
So, when we exploits the MfData
and MfStructure
, changing the array's structure (=MfStructure
) is very efficient. For example, when you have the a
array initialized below;
let a = Matft.arange(start: 0, to: 27, by: 1, shape: [3,3,3])
print(a)
/*
mfarray =
[[[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8]],
[[ 9, 10, 11],
[ 12, 13, 14],
[ 15, 16, 17]],
[[ 18, 19, 20],
[ 21, 22, 23],
[ 24, 25, 26]]], type=Int, shape=[3, 3, 3]
*/
Type conversion
You can convert the current type into many types by astype
method or function.
let a = Matft.arange(start: 0, to: 27, by: 1, shape: [3,3,3], mftype: .Int)
print(a)
/*
mfarray =
[[[ -6, -5, -4],
[ -3, -2, -1],
[ 0, 1, 2]],
[[ 3, 4, 5],
[ 6, 7, 8],
[ 9, 10, 11]],
[[ 12, 13, 14],
[ 15, 16, 17],
[ 18, 19, 20]]], type=Int, shape=[3, 3, 3]
*/
print(a.astype(.UInt8))
/*
mfarray =
[[[ 250, 251, 252],
[ 253, 254, 255],
[ 0, 1, 2]],
[[ 3, 4, 5],
[ 6, 7, 8],
[ 9, 10, 11]],
[[ 12, 13, 14],
[ 15, 16, 17],
[ 18, 19, 20]]], type=UInt8, shape=[3, 3, 3]
*/
print(a.astype(.Float))
/*
mfarray =
[[[ -6.0, -5.0, -4.0],
[ -3.0, -2.0, -1.0],
[ 0.0, 1.0, 2.0]],
[[ 3.0, 4.0, 5.0],
[ 6.0, 7.0, 8.0],
[ 9.0, 10.0, 11.0]],
[[ 12.0, 13.0, 14.0],
[ 15.0, 16.0, 17.0],
[ 18.0, 19.0, 20.0]]], type=Float, shape=[3, 3, 3]
*/
Positive and Negative Indexing
You can extract the sliced MfArrray
by ~<
efficiently.
Note that use a[0~<]
instead of a[:]
to get all elements along axis (or a[Matft.all]
).
print(a[~<1]) //same as a[:1] for numpy
/*
mfarray =
[[[ 9, 10, 11],
[ 12, 13, 14],
[ 15, 16, 17]]], type=Int, shape=[1, 3, 3]
*/
print(a[1~<3]) //same as a[1:3] for numpy
/*
mfarray =
[[[ 9, 10, 11],
[ 12, 13, 14],
[ 15, 16, 17]],
[[ 18, 19, 20],
[ 21, 22, 23],
[ 24, 25, 26]]], type=Int, shape=[2, 3, 3]
*/
print(a[~<~<2]) //same as a[::2] for numpy
//print(a[~<<2]) //alias
/*
mfarray =
[[[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8]],
[[ 18, 19, 20],
[ 21, 22, 23],
[ 24, 25, 26]]], type=Int, shape=[2, 3, 3]
*/
print(a[Matft.all, 0]) //same as a[:, 0] for numpy
/*
mfarray =
[[ 0, 1, 2],
[ 9, 10, 11],
[18, 19, 20]], type=Int, shape=[3, 3]
*/
print(a[~<-1])
/*
mfarray =
[[[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8]],
[[ 9, 10, 11],
[ 12, 13, 14],
[ 15, 16, 17]]], type=Int, shape=[2, 3, 3]
*/
print(a[-1~<-3])
/*
mfarray =
[], type=Int, shape=[0, 3, 3]
*/
print(a[Matft.reverse])
//print(a[~<~<-1]) //alias
//print(a[~<<-1]) //alias
/*
mfarray =
[[[ 18, 19, 20],
[ 21, 22, 23],
[ 24, 25, 26]],
[[ 9, 10, 11],
[ 12, 13, 14],
[ 15, 16, 17]],
[[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8]]], type=Int, shape=[3, 3, 3]*/
Of course, you can set the value into the sliced array.
a[~<1] = Matft.arange(start: 0, to: 81, by: 9, shape: [3, 3])
print(a)
/*
mfarray =
[[[ 0, 9, 18],
[ 27, 36, 45],
[ 54, 63, 72]],
[[ 9, 10, 11],
[ 12, 13, 14],
[ 15, 16, 17]],
[[ 18, 19, 20],
[ 21, 22, 23],
[ 24, 25, 26]]], type=Int, shape=[3, 3, 3]
*/
Boolean Indexing
Matft
supports also boolean indexing! So, you can extract the array with a specific condition you want like this. This feature will be convenient for an image processing. See the numpy docs for more details.
let img = Matft.arange(start: -5, to: 70, by: 1, shape: [5, 5, 3], mftype: .Float)
print(img)
/*
mfarray =
[[[ -5.0, -4.0, -3.0],
[ -2.0, -1.0, 0.0],
[ 1.0, 2.0, 3.0],
[ 4.0, 5.0, 6.0],
[ 7.0, 8.0, 9.0]],
[[ 10.0, 11.0, 12.0],
[ 13.0, 14.0, 15.0],
[ 16.0, 17.0, 18.0],
[ 19.0, 20.0, 21.0],
[ 22.0, 23.0, 24.0]],
[[ 25.0, 26.0, 27.0],
[ 28.0, 29.0, 30.0],
[ 31.0, 32.0, 33.0],
[ 34.0, 35.0, 36.0],
[ 37.0, 38.0, 39.0]],
[[ 40.0, 41.0, 42.0],
[ 43.0, 44.0, 45.0],
[ 46.0, 47.0, 48.0],
[ 49.0, 50.0, 51.0],
[ 52.0, 53.0, 54.0]],
[[ 55.0, 56.0, 57.0],
[ 58.0, 59.0, 60.0],
[ 61.0, 62.0, 63.0],
[ 64.0, 65.0, 66.0],
[ 67.0, 68.0, 69.0]]], type=Float, shape=[5, 5, 3]
*/
print(img[img < 0])
/*
mfarray =
[ -5.0, -4.0, -3.0, -2.0, -1.0], type=Float, shape=[5]
*/
img[img < 0] = MfArray([0])
print(img)
/*
mfarray =
[[[ 0.0, 0.0, 0.0],
[ 0.0, 0.0, 0.0],
[ 1.0, 2.0, 3.0],
[ 4.0, 5.0, 6.0],
[ 7.0, 8.0, 9.0]],
[[ 10.0, 11.0, 12.0],
[ 13.0, 14.0, 15.0],
[ 16.0, 17.0, 18.0],
[ 19.0, 20.0, 21.0],
[ 22.0, 23.0, 24.0]],
[[ 25.0, 26.0, 27.0],
[ 28.0, 29.0, 30.0],
[ 31.0, 32.0, 33.0],
[ 34.0, 35.0, 36.0],
[ 37.0, 38.0, 39.0]],
[[ 40.0, 41.0, 42.0],
[ 43.0, 44.0, 45.0],
[ 46.0, 47.0, 48.0],
[ 49.0, 50.0, 51.0],
[ 52.0, 53.0, 54.0]],
[[ 55.0, 56.0, 57.0],
[ 58.0, 59.0, 60.0],
[ 61.0, 62.0, 63.0],
[ 64.0, 65.0, 66.0],
[ 67.0, 68.0, 69.0]]], type=Float, shape=[5, 5, 3]
Fancy Indexing
Furthermore, Matft
supports Fancy Indexing too! See the numpy docs for more details.
let a = MfArray([[1, 2], [3, 4], [5, 6]])
a[MfArray([0, 1, 2]), MfArray([0, -1, 0])] = MfArray([999,888,777])
print(a)
/*
mfarray =
[[ 999, 2],
[ 3, 888],
[ 777, 6]], type=Int, shape=[3, 2]
*/
a.T[MfArray([0, 1, -1]), MfArray([0, 1, 0])] = MfArray([-999,-888,-777])
print(a)
/*
mfarray =
[[ -999, -777],
[ 3, -888],
[ 777, 6]], type=Int, shape=[3, 2]
*/
Synchronizing extracted MfArray
As you can see the above examples, MfArray
has base
property (is similar to view in Numpy). Therefore, a MfArray
extracted (Sliced) by positive and negative indexing is synchronized. See numpy doc for more details.
Note that a MfArray
extracted by Boolean Indexing and Fancy Indexing is COPY of MfArray
of the original one.
let a = Matft.arange(start: 0, to: 4*4*2, by: 1, shape: [4,4,2])
let b = a[0~<, 1]
b[~<<-1] = MfArray([9999])
print(a)
/*
mfarray =
[[[ 0, 1],
[ 9999, 9999],
[ 4, 5],
[ 6, 7]],
[[ 8, 9],
[ 9999, 9999],
[ 12, 13],
[ 14, 15]],
[[ 16, 17],
[ 9999, 9999],
[ 20, 21],
[ 22, 23]],
[[ 24, 25],
[ 9999, 9999],
[ 28, 29],
[ 30, 31]]], type=Int, shape=[4, 4, 2]
*/
Etc.
The other conversion functions are below;
Matft | Numpy |
---|---|
*#Matft.astype | *numpy.astype |
*#Matft.transpose | *numpy.transpose |
*#Matft.expand_dims | *numpy.expand_dims |
*#Matft.squeeze | *numpy.squeeze |
*#Matft.broadcast_to | *numpy.broadcast_to |
*#Matft.to_contiguous | *numpy.ascontiguousarray |
*#Matft.flatten | *numpy.flatten |
*#Matft.flip | *numpy.flip |
*#Matft.clip | *numpy.clip |
*#Matft.swapaxes | *numpy.swapaxes |
*#Matft.moveaxis | *numpy.moveaxis |
*Matft.roll | numpy.roll |
*Matft.sort | *numpy.sort |
*Matft.argsort | *numpy.argsort |
^MfArray.toArray | ^numpy.ndarray.tolist |
^MfArray.toFlattenArray | n/a |
*Matft.orderedUnique | numpy.unique |
Math operation
Basic mathematic operations are supported in Matft
.
Arithmetic operation
print(a+a)
/*
mfarray =
[[ 0, 2, 4],
[ 6, 8, 10],
[ 12, 14, 16]], type=Int, shape=[3, 3]
*/
print(a-a)
/*
mfarray =
[[ 0, 0, 0],
[ 0, 0, 0],
[ 0, 0, 0]], type=Int, shape=[3, 3]
*/
print(a*a)
/*
mfarray =
[[ 0, 1, 4],
[ 9, 16, 25],
[ 36, 49, 64]], type=Int, shape=[3, 3]
*/
print(a/a)
/*
mfarray =
[[ -nan, 0.99999994, 0.99999994],
[ 0.99999994, 0.99999994, 0.99999994],
[ 0.99999994, 0.99999994, 0.99999994]], type=Float, shape=[3, 3]
*/
Broadcasting
The one of the main features in Numpy
is broadcasting operation. See numpy doc for more details.
let a = Matft.arange(start: 0, to: 3*3, by: 1, shape: [3,3])
let b = MfArray([-4, 10, 2]).reshape([3, 1])
print(a+b)
/*
mfarray =
[[ -4, -3, -2],
[ 13, 14, 15],
[ 8, 9, 10]], type=Int, shape=[3, 3]
*/
Etc.
Matft
has many mathematic functions.
- Math function
Matft | Numpy |
---|---|
#Matft.math.sin | numpy.sin |
Matft.math.asin | numpy.asin |
Matft.math.sinh | numpy.sinh |
Matft.math.asinh | numpy.asinh |
#Matft.math.cos | numpy.cos |
Matft.math.acos | numpy.acos |
Matft.math.cosh | numpy.cosh |
Matft.math.acosh | numpy.acosh |
#Matft.math.tan | numpy.tan |
Matft.math.atan | numpy.atan |
Matft.math.tanh | numpy.tanh |
Matft.math.atanh | numpy.atanh |
Matft.math.sqrt | numpy.sqrt |
Matft.math.rsqrt | numpy.rsqrt |
#Matft.math.exp | numpy.exp |
#Matft.math.log | numpy.log |
Matft.math.log2 | numpy.log2 |
Matft.math.log10 | numpy.log10 |
*Matft.math.ceil | numpy.ceil |
*Matft.math.floor | numpy.floor |
*Matft.math.trunc | numpy.trunc |
*Matft.math.nearest | numpy.nearest |
*Matft.math.round | numpy.round |
#Matft.math.abs | numpy.abs |
Matft.math.reciprocal | numpy.reciprocal |
#Matft.math.power | numpy.power |
Matft.math.arctan2 | numpy.arctan2 |
Matft.math.square | numpy.square |
Matft.math.sign | numpy.sign |
- Statistics function
Matft | Numpy |
---|---|
*Matft.stats.mean | *numpy.mean |
*Matft.stats.max | *numpy.max |
*Matft.stats.argmax | *numpy.argmax |
*Matft.stats.min | *numpy.min |
*Matft.stats.argmin | *numpy.argmin |
*Matft.stats.sum | *numpy.sum |
Matft.stats.maximum | numpy.maximum |
Matft.stats.minimum | numpy.minimum |
*Matft.stats.sumsqrt | n/a |
*Matft.stats.squaresum | n/a |
*Matft.stats.cumsum | *numpy.cumsum |
- Random function
Matft | Numpy |
---|---|
Matft.random.rand | numpy.random.rand |
Matft.random.randint | numpy.random.randint |
- Linear algebra
Matft | Numpy |
---|---|
Matft.linalg.solve | numpy.linalg.solve |
Matft.linalg.inv | numpy.linalg.inv |
Matft.linalg.det | numpy.linalg.det |
Matft.linalg.eigen | numpy.linalg.eig |
Matft.linalg.svd | numpy.linalg.svd |
Matft.linalg.pinv | numpy.linalg.pinv |
Matft.linalg.polar_left | scipy.linalg.polar |
Matft.linalg.polar_right | scipy.linalg.polar |
Matft.linalg.normlp_vec | scipy.linalg.norm |
Matft.linalg.normfro_mat | scipy.linalg.norm |
Matft.linalg.normnuc_mat | scipy.linalg.norm |
Advanced operation
Matft
has complex and image processing functions too!!!!
So, Matft
will be very useful for the signal and image processing!
But these functions are beta versions currently (on 22/08/08).
Complex operation
You can operate the complex values like the real ones.
let real = Matft.arange(start: 0, to: 16, by: 1).reshape([2,2,4])
let imag = Matft.arange(start: 0, to: -16, by: -1).reshape([2,2,4])
let a = MfArray(real: real, imag: imag)
print(a)
/*
mfarray =
[[[ 0 +0j, 1 -1j, 2 -2j, 3 -3j],
[ 4 -4j, 5 -5j, 6 -6j, 7 -7j]],
[[ 8 -8j, 9 -9j, 10 -10j, 11 -11j],
[ 12 -12j, 13 -13j, 14 -14j, 15 -15j]]], type=Int, shape=[2, 2, 4]
*/
print(a+a)
/*
mfarray =
[[[ 0 +0j, 2 -2j, 4 -4j, 6 -6j],
[ 8 -8j, 10 -10j, 12 -12j, 14 -14j]],
[[ 16 -16j, 18 -18j, 20 -20j, 22 -22j],
[ 24 -24j, 26 -26j, 28 -28j, 30 -30j]]], type=Int, shape=[2, 2, 4]
*/
print(Matft.complex.angle(a))
/*
mfarray =
[[[ -0.0, -0.7853982, -0.7853982, -0.7853982],
[ -0.7853982, -0.7853982, -0.7853982, -0.7853982]],
[[ -0.7853982, -0.7853982, -0.7853982, -0.7853982],
[ -0.7853982, -0.7853982, -0.7853982, -0.7853982]]], type=Float, shape=[2, 2, 4]
*/
print(Matft.complex.conjugate(a))
/*
mfarray =
[[[ 0 +0j, 1 +1j, 2 +2j, 3 +3j],
[ 4 +4j, 5 +5j, 6 +6j, 7 +7j]],
[[ 8 +8j, 9 +9j, 10 +10j, 11 +11j],
[ 12 +12j, 13 +13j, 14 +14j, 15 +15j]]], type=Int, shape=[2, 2, 4]
*/
Matft | Numpy |
---|---|
Matft.complex.angle | numpy.angle |
Matft.complex.conjugate | numpy.conj / numpy.conjugate |
Matft.complex.abs | numpy.abs / numpy.absolute |
Image operation
Conversion from/to CGImage
The conversion functions between MfArray
and CGImage
are ready. So, you can implement the complex image operation code
very easily! For example, if you use the Matft
's indexing feature,...
import UIKit
import Matft
import Accelerate
import CoreGraphics
import CoreGraphics.CGBitmapContext
class ViewController: UIViewController {
@IBOutlet weak var originalImageView: UIImageView!
@IBOutlet weak var reverseImageView: UIImageView!
@IBOutlet weak var grayreverseImageView: UIImageView!
@IBOutlet weak var swapImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
self.originalImageView.image = UIImage(named: "rena.jpeg")
self.reverseImageView.image = UIImage(named: "rena.jpeg")
self.grayreverseImageView.image = self.convertToGrayScale(image: UIImage(named: "rena.jpeg")!)
self.swapImageView.image = UIImage(named: "rena.jpeg")
self.reverse()
self.grayreverse()
self.swapchannel()
self.resize()
}
func reverse(){
var image = Matft.image.cgimage2mfarray(self.reverseImageView.image!.cgImage!)
// reverse
image = image[Matft.reverse] // same as image[~<<-1]
self.reverseImageView.image = UIImage(cgImage: Matft.image.mfarray2cgimage(image))
}
func swapchannel(){
var image = Matft.image.cgimage2mfarray(self.swapImageView.image!.cgImage!)
// swap channel
image = image[Matft.all, Matft.all, MfArray([1,0,2,3])] // same as image[0~<, 0~<, MfArray([1,0,2,3])]
self.swapImageView.image = UIImage(cgImage: Matft.image.mfarray2cgimage(image))
}
func grayreverse(){
var image = Matft.image.cgimage2mfarray(self.grayreverseImageView.image!.cgImage!, mftype: .UInt8)
// reverse
image = image[Matft.reverse] // same as image[~<<-1]
self.grayreverseImageView.image = UIImage(cgImage: Matft.image.mfarray2cgimage(image))
}
func convertToGrayScale(image: UIImage) -> UIImage{
//let gray_mfarray = (Matft.image.color(Matft.image.cgimage2mfarray(image.cgImage!)) * Float(255)).astype(.UInt8)
let gray_mfarray = Matft.image.color(Matft.image.cgimage2mfarray(image.cgImage!))
return UIImage(cgImage: Matft.image.mfarray2cgimage(gray_mfarray))
}
func resize(){
var image = Matft.image.cgimage2mfarray(self.swapImageView.image!.cgImage!)
// resize
image = Matft.image.resize(image, width: 300, height: 300)
self.swapImageView.image = UIImage(cgImage: Matft.image.mfarray2cgimage(image))
//self.swapImageView.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
}
}
Awesome!
Affine
// CGImage to MfArray
let rgb = Matft.image.cgimage2mfarray(uiimage.cgImage!)
// Convert RGBA into Gray Image
let gray = Matft.image.color(rgb)
// Resize
let rgb_resize = Matft.image.resize(rgb, width: 150, height: 150)
let gray_resize = Matft.image.resize(gray, width: 300, height: 300)
let rbg_rotated = Matft.image.warpAffine(rgb_resize, matrix: MfArray([[0, 1, 1],
[1, 0, 0]] as [[Float]]), width: 150, height: 150)
let gray_rotated = Matft.image.warpAffine(gray_resize, matrix: MfArray([[0, 1, 1],
[1, 0, 0]] as [[Float]]), width: 150, height: 150)
Matft.image.mfarray2cgimage(rbg_rotated)
Matft | Numpy |
---|---|
Matft.image.cgimage2mfarray | N/A |
Matft.image.mfarray2cgimage | N/A |
Matft | OpenCV |
---|---|
Matft.image.color | cv2.cvtColor |
Matft.image.resize | cv2.resize |
Matft.image.warpAffine | cv2.warpAffine |
Conclusion
I introduced the Matft
in this post. Matft
has many features from the basic mathematic array operations to the complicated operations such like boolean indexing, fancy indexing, complex data, image data and so on.
I hope Matft
will be useful for your project and help your project more efficient!
Top comments (0)