DEV Community

jjjkkkjjj
jjjkkkjjj

Posted on

The matrix operation library in Swift

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
}
Enter fullscreen mode Exit fullscreen mode

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]
*/
Enter fullscreen mode Exit fullscreen mode

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]
*/
Enter fullscreen mode Exit fullscreen mode

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]
*/
Enter fullscreen mode Exit fullscreen mode

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]
*/
Enter fullscreen mode Exit fullscreen mode

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]
*/
Enter fullscreen mode Exit fullscreen mode

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]
*/
Enter fullscreen mode Exit fullscreen mode
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]*/
Enter fullscreen mode Exit fullscreen mode

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]
*/
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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]
*/
Enter fullscreen mode Exit fullscreen mode

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]
*/
Enter fullscreen mode Exit fullscreen mode

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]
*/
Enter fullscreen mode Exit fullscreen mode

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]
*/
Enter fullscreen mode Exit fullscreen mode

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]
*/
Enter fullscreen mode Exit fullscreen mode
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)
    }

}
Enter fullscreen mode Exit fullscreen mode

Screen Shot 2022-07-19 at 21 09 02

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)
Enter fullscreen mode Exit fullscreen mode

Image description

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)