The matrix operation library in Swift
Tags: swift, math, ndarray, matrix
Published: 2022-08-08 05:56:56

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](https://github.com/jjjkkkjjj/Matft) 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](https://developer.apple.com/documentation/accelerate)), `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;

```swift
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.

```swift
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.

```swift
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`.

```swift
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;

```swift
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.

```swift
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]`).
  
```swift
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]
*/
```

```swift
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. 

```swift
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](https://numpy.org/doc/stable/user/basics.indexing.html#boolean-array-indexing) for more details.

```swift
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](https://numpy.org/doc/stable/user/basics.indexing.html#integer-array-indexing) for more details.

```swift
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](#Positive-and-Negative-Indexing) is synchronized. See [numpy doc](https://numpy.org/doc/stable/user/basics.indexing.html) for more details.

Note that a `MfArray` extracted by [Boolean Indexing](#Boolean-Indexing) and [Fancy Indexing](#Fancy-Indexing) is **COPY** of `MfArray` of the original one.

```swift
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

```swift
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](https://numpy.org/doc/stable/user/basics.broadcasting.html) for more details.

```swift
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.

```swift
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,...

```swift
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)
    }
    
}
```

<img width="513" alt="Screen Shot 2022-07-19 at 21 09 02" src="https://user-images.githubusercontent.com/16914891/179746856-c4e8048d-3e7c-4835-b39c-ddf6af5b5fd7.png">

Awesome!

#### Affine 

```swift
// 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)
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ghp5ngab26xf1rdt01wl.png)
  

| 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!