├── .drone.yml ├── .gitignore ├── Dockerfile ├── Dockerfile.tflite ├── LICENSE ├── README.md ├── detector.go ├── go.mod ├── go.sum ├── listener.go ├── mfcc.go ├── model.go ├── model_onnx.go ├── model_tflite.go ├── params.go ├── runner.go ├── runner_test.go ├── threshold.go └── threshold_test.go /.drone.yml: -------------------------------------------------------------------------------- 1 | kind: pipeline 2 | type: docker 3 | name: default 4 | 5 | steps: 6 | - name: tflite 7 | image: plugins/docker 8 | settings: 9 | username: 10 | from_secret: docker_username 11 | password: 12 | from_secret: docker_password 13 | repo: ghcr.io/tystuyfzand/precise-go 14 | registry: ghcr.io 15 | dockerfile: Dockerfile.tflite 16 | tags: 17 | - tflite 18 | - name: docker 19 | image: plugins/docker 20 | settings: 21 | username: 22 | from_secret: docker_username 23 | password: 24 | from_secret: docker_password 25 | repo: ghcr.io/tystuyfzand/precise-go 26 | registry: ghcr.io 27 | tags: 28 | - latest 29 | - ubuntu-jammy 30 | depends_on: 31 | - tflite 32 | - name: docker-gpu 33 | image: plugins/docker 34 | settings: 35 | username: 36 | from_secret: docker_username 37 | password: 38 | from_secret: docker_password 39 | build_args: 40 | BASE_IMAGE: nvidia/cuda:11.8.0-cudnn8-runtime-ubuntu22.04 41 | repo: ghcr.io/tystuyfzand/precise-go 42 | registry: ghcr.io 43 | tags: 44 | - ubuntu-jammy-gpu 45 | - latest-gpu 46 | depends_on: 47 | - tflite 48 | - name: docker-debian 49 | image: plugins/docker 50 | settings: 51 | username: 52 | from_secret: docker_username 53 | password: 54 | from_secret: docker_password 55 | build_args: 56 | BASE_IMAGE: debian:bullseye 57 | repo: ghcr.io/tystuyfzand/precise-go 58 | registry: ghcr.io 59 | tags: 60 | - debian 61 | depends_on: 62 | - tflite 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.iml -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG BASE_IMAGE=ubuntu:jammy 2 | FROM $BASE_IMAGE AS builder 3 | 4 | LABEL org.opencontainers.image.description="Base docker image with tensorflow lite, onnxruntime, and Go for precise-go" 5 | 6 | ARG GO_VERSION=1.20.3 7 | 8 | RUN apt-get update && \ 9 | apt-get -y install git build-essential curl ca-certificates && \ 10 | rm -rf /var/lib/apt/lists/* 11 | 12 | # Even though we have a Golang image, we can just use our own. 13 | # This lets us unify our GPU and Standard images. 14 | RUN curl -L -o /usr/local/go${GO_VERSION}.tar.gz https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz && \ 15 | tar -C /usr/local -xzf /usr/local/go${GO_VERSION}.tar.gz && \ 16 | rm -f /usr/local/go${GO_VERSION}.tar.gz 17 | 18 | ENV PATH=$PATH:/usr/local/go/bin 19 | 20 | # Re-use our prebuilt tflite (for all builds) 21 | COPY --from=ghcr.io/tystuyfzand/precise-go:tflite /usr/include/tf-include.tar.gz /usr/include 22 | COPY --from=ghcr.io/tystuyfzand/precise-go:tflite /usr/lib/libtensorflowlite.so /usr/lib/libtensorflowlite_c.so /usr/lib/ 23 | 24 | # Extract the archive (done to ensure we have all that we need in one spot - bazel puts it into a "cache" folder that changes) 25 | RUN tar -C /usr/include -xvf /usr/include/tf-include.tar.gz 26 | 27 | ENV CGO_CFLAGS="-I/usr/src/tensorflow" 28 | ENV CGO_LDFLAGS="-L/usr/src/tensorflow/bazel-bin/tensorflow/lite" 29 | 30 | ARG ONNX_RUNTIME_VERSION=1.12.0 31 | 32 | RUN FILE="onnxruntime-linux-x64-gpu-$ONNX_RUNTIME_VERSION.tgz" && \ 33 | URL="https://github.com/microsoft/onnxruntime/releases/download/v$ONNX_RUNTIME_VERSION/onnxruntime-linux-x64-$ONNX_RUNTIME_VERSION.tgz" && \ 34 | curl -L -o $FILE $URL && \ 35 | tar xvf $FILE -C /usr/local/ --strip-components=1 && \ 36 | rm -f $FILE 37 | 38 | ENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib -------------------------------------------------------------------------------- /Dockerfile.tflite: -------------------------------------------------------------------------------- 1 | FROM debian:bullseye AS builder 2 | 3 | LABEL org.opencontainers.image.description="Base docker image with Tensorflow lite for precise-go builds" 4 | 5 | RUN apt-get update && \ 6 | apt-get -y install git build-essential curl ca-certificates && \ 7 | rm -rf /var/lib/apt/lists/* 8 | 9 | ARG TENSORFLOW_VERSION=2.11.0 10 | 11 | RUN git clone https://github.com/tensorflow/tensorflow.git /usr/src/tensorflow && \ 12 | cd /usr/src/tensorflow && \ 13 | git checkout v$TENSORFLOW_VERSION 14 | 15 | RUN \ 16 | # Install bazel (https://docs.bazel.build/versions/master/install-ubuntu.html) \ 17 | mkdir -p /etc/apt/keyrings || true && \ 18 | echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/bazel.gpg] http://storage.googleapis.com/bazel-apt stable jdk1.8" | tee /etc/apt/sources.list.d/bazel.list && \ 19 | curl -fsSL https://bazel.build/bazel-release.pub.gpg | tee | gpg --dearmor | tee /etc/apt/keyrings/bazel.gpg > /dev/null && \ 20 | apt-get update && \ 21 | apt-get -y install bazel-5.4.0 && \ 22 | apt-get -y upgrade bazel-5.4.0 && \ 23 | ln -s /usr/bin/bazel-5.4.0 /usr/bin/bazel && \ 24 | # Unpack bazel for future use. 25 | bazel version 26 | 27 | RUN apt-get install -y python3 python3-pip && \ 28 | pip3 install numpy 29 | 30 | RUN cd /usr/src/tensorflow && \ 31 | ./configure && \ 32 | bazel build --config opt --config monolithic //tensorflow:install_headers //tensorflow/lite:libtensorflowlite.so //tensorflow/lite/c:libtensorflowlite_c.so && \ 33 | cp /usr/src/tensorflow/bazel-bin/tensorflow/lite/libtensorflowlite.so /usr/lib && \ 34 | cp /usr/src/tensorflow/bazel-bin/tensorflow/lite/c/libtensorflowlite_c.so /usr/lib && \ 35 | tar -C /usr/src/tensorflow/bazel-bin/tensorflow/include -zcvf /usr/include/tf-include.tar.gz . && \ 36 | cp -R /usr/src/tensorflow/bazel-bin/tensorflow/include/* /usr/include/ 37 | 38 | FROM debian:bullseye 39 | 40 | # Multi-stage build because bazel creates a lot of excess items. We only need the include files and libraries. 41 | 42 | COPY --from=builder /usr/include/tf-include.tar.gz /usr/include 43 | 44 | RUN tar -C /usr/include -xvf /usr/include/tf-include.tar.gz 45 | 46 | COPY --from=builder /usr/lib/libtensorflowlite.so /usr/lib/libtensorflowlite_c.so /usr/lib/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2023 Tyler Stuyfzand / Aurora Development LLC 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 4 | 5 | THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Precise-Lite for Go 2 | =================== 3 | 4 | This is a Go port of the [MycroftAI Precise](https://github.com/MycroftAI/mycroft-precise) engine, using tflite and onnxruntime 5 | (based on the [OpenVoiceOS fork](https://github.com/OpenVoiceOS/precise-lite)). 6 | 7 | I needed this for my Discord bot [Astra](https://astra.bot) - which I'm currently training a model for. Help us out [here](https://train.astra.bot/) and submit a few voice clips! 8 | 9 | 10 | What is working? 11 | ---------------- 12 | 13 | Everything should work, though I'm not sure if my port is off in any way. I'm not an AI/Machine Learning expert, I just read the code! 14 | 15 | Supported Backends 16 | ------------------ 17 | 18 | Currently, I've tested it with tflite and onnxruntime (cpu and gpu) - onnxruntime works and would be fine on CPU, but on 19 | GPUs it is slower than CPU due to the simplicity of the model. 20 | 21 | Example 22 | ------- 23 | 24 | See `runner_test.go` - this contains an example usage of both tflite and onnxruntime - though onnxruntime is slower, 25 | it was more of a test. 26 | 27 | Docker 28 | ------ 29 | 30 | The Dockerfiles contain a pre-built Go image with all dependencies required to use this library. If you want to use it 31 | yourself, you can use a FROM statement for `ghcr.io/tystuyfzand/precise-go` public image, use it to build, and then copy the tflite 32 | runtime. These images are based off Ubuntu 22.04 (due to simplicity and unifying the GPU and non-GPU, but debian exists too) 33 | 34 | The following tags are available, and all include Tensorflow 2.11.0 and ONNX 1.12.0 35 | 36 | - latest, ubuntu-jammy (Ubuntu 22.04) 37 | - debian (Debian 11) 38 | - latest-gpu, ubuntu-jammy-gpu (Ubuntu 22.04, CUDA 11.8.0, CUDNN 8) 39 | 40 | To use this in Docker, tensorflow doesn't exactly have a precompiled lite version that I've found. The easiest way is to 41 | use the base docker image `ghcr.io/tystuyfzand/precise-go` (which contains the latest version of Go as of this) 42 | and use it to build your application with a multi-stage build. Then, add the tensorflow lite libraries. 43 | 44 | Example: 45 | 46 | ```dockerfile 47 | FROM ghcr.io/tystuyfzand/precise-go:latest AS builder 48 | 49 | ADD . /app 50 | WORKDIR /app 51 | 52 | RUN go build -o main 53 | 54 | FROM ubuntu:jammy 55 | 56 | COPY --from=builder /app/main /main 57 | COPY --from=builder /usr/lib/libtensorflowlite.so /usr/lib/libtensorflowlite_c.so /usr/lib/ 58 | 59 | CMD ["/main"] 60 | ``` 61 | 62 | There is also support for onnxruntime (using `go build -tags onnx`) - but it's not necessary. 63 | 64 | Note: GPU/CUDA support exists, but is actually SLOWER than CPU support due to how simple the model is. When using this, you're required to use single stage builds OR use the same base CUDA image. 65 | 66 | Contributions, Bug Reports, etc. 67 | -------------------------------- 68 | 69 | All of these are welcome! If I made an error in the logic or there's something wrong, please let me know! -------------------------------------------------------------------------------- /detector.go: -------------------------------------------------------------------------------- 1 | package precise 2 | 3 | import "math" 4 | 5 | type TriggerOption func(*TriggerDetector) 6 | 7 | // WithSensitivity sets the detector sensitivity 8 | func WithSensitivity(sensitivity float32) TriggerOption { 9 | return func(t *TriggerDetector) { 10 | t.sensitivity = sensitivity 11 | } 12 | } 13 | 14 | // WithTriggerLevel sets the number of triggers required to return a 15 | // valid trigger 16 | func WithTriggerLevel(triggerLevel int) TriggerOption { 17 | return func(t *TriggerDetector) { 18 | t.triggerLevel = triggerLevel 19 | } 20 | } 21 | 22 | // NewTriggerDetector creates a new TriggerDetector 23 | func NewTriggerDetector(chunkSize int, opts ...TriggerOption) *TriggerDetector { 24 | t := &TriggerDetector{ 25 | chunkSize: chunkSize, 26 | sensitivity: 0.5, 27 | triggerLevel: 3, 28 | } 29 | 30 | for _, opt := range opts { 31 | opt(t) 32 | } 33 | 34 | return t 35 | } 36 | 37 | type TriggerDetector struct { 38 | chunkSize int 39 | sensitivity float32 40 | triggerLevel int 41 | activation int 42 | } 43 | 44 | // Update adds the current probability to the detection history 45 | func (t *TriggerDetector) Update(prob float32) bool { 46 | chunkActivated := prob > 1.0-t.sensitivity 47 | 48 | if chunkActivated || t.activation < 0 { 49 | t.activation += 1 50 | 51 | hasActivated := t.activation > t.triggerLevel 52 | 53 | if hasActivated || chunkActivated && t.activation < 0 { 54 | t.activation = int(math.Floor(float64(-(8 * 2048) / t.chunkSize))) 55 | } 56 | 57 | if hasActivated { 58 | return true 59 | } 60 | } else if t.activation > 0 { 61 | t.activation -= 1 62 | } 63 | 64 | return false 65 | } 66 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tystuyfzand/precise-go 2 | 3 | go 1.19 4 | 5 | replace github.com/ivansuteja96/go-onnxruntime => github.com/tystuyfzand/go-onnxruntime v0.0.0-20230107221602-893efd995fc3 6 | 7 | require ( 8 | github.com/cryptix/wav v0.0.0-20180415113528-8bdace674401 9 | github.com/ivansuteja96/go-onnxruntime v0.0.0-20220819143618-84b1a0db69d3 10 | github.com/mattn/go-tflite v1.0.4 11 | github.com/yut-kt/gomfcc v0.0.0-20220503093809-d81a01b6bf53 12 | gorgonia.org/tensor v0.9.24 13 | ) 14 | 15 | require ( 16 | github.com/apache/arrow/go/arrow v0.0.0-20201229220542-30ce2eb5d4dc // indirect 17 | github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 // indirect 18 | github.com/chewxy/hm v1.0.0 // indirect 19 | github.com/chewxy/math32 v1.0.8 // indirect 20 | github.com/davecgh/go-spew v1.1.1 // indirect 21 | github.com/gogo/protobuf v1.3.2 // indirect 22 | github.com/golang/protobuf v1.5.2 // indirect 23 | github.com/google/flatbuffers v1.12.0 // indirect 24 | github.com/google/go-cmp v0.5.6 // indirect 25 | github.com/mattn/go-pointer v0.0.1 // indirect 26 | github.com/pkg/errors v0.9.1 // indirect 27 | github.com/stretchr/testify v1.7.0 // indirect 28 | github.com/xtgo/set v1.0.0 // indirect 29 | github.com/yut-kt/goft v0.1.0 // indirect 30 | github.com/yut-kt/gowindow v0.1.7 // indirect 31 | go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 // indirect 32 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 // indirect 33 | golang.org/x/net v0.0.0-20210917221730-978cfadd31cf // indirect 34 | golang.org/x/sys v0.0.0-20210917161153-d61c044b1678 // indirect 35 | golang.org/x/text v0.3.7 // indirect 36 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect 37 | gonum.org/v1/gonum v0.9.0 // indirect 38 | google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4 // indirect 39 | google.golang.org/grpc v1.40.0 // indirect 40 | google.golang.org/protobuf v1.27.1 // indirect 41 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect 42 | gorgonia.org/vecf32 v0.9.0 // indirect 43 | gorgonia.org/vecf64 v0.9.0 // indirect 44 | ) 45 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 4 | gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= 5 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 6 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 7 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 8 | github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= 9 | github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= 10 | github.com/apache/arrow/go/arrow v0.0.0-20201229220542-30ce2eb5d4dc h1:zvQ6w7KwtQWgMQiewOF9tFtundRMVZFSAksNV6ogzuY= 11 | github.com/apache/arrow/go/arrow v0.0.0-20201229220542-30ce2eb5d4dc/go.mod h1:c9sxoIT3YgLxH4UhLOCKaBlEojuMhVYpk4Ntv3opUTQ= 12 | github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= 13 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 14 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 15 | github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 h1:SKI1/fuSdodxmNNyVBR8d7X/HuLnRpvvFO0AgyQk764= 16 | github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U= 17 | github.com/chewxy/hm v1.0.0 h1:zy/TSv3LV2nD3dwUEQL2VhXeoXbb9QkpmdRAVUFiA6k= 18 | github.com/chewxy/hm v1.0.0/go.mod h1:qg9YI4q6Fkj/whwHR1D+bOGeF7SniIP40VweVepLjg0= 19 | github.com/chewxy/math32 v1.0.0/go.mod h1:Miac6hA1ohdDUTagnvJy/q+aNnEk16qWUdb8ZVhvCN0= 20 | github.com/chewxy/math32 v1.0.8 h1:fU5E4Ec4Z+5RtRAi3TovSxUjQPkgRh+HbP7tKB2OFbM= 21 | github.com/chewxy/math32 v1.0.8/go.mod h1:dOB2rcuFrCn6UHrze36WSLVPKtzPMRAQvBvUwkSsLqs= 22 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 23 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 24 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 25 | github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 26 | github.com/cryptix/wav v0.0.0-20180415113528-8bdace674401 h1:rZ+OHHkwlkYALTEd6AYXSL92K/SEc4fkz+TfweIwu6A= 27 | github.com/cryptix/wav v0.0.0-20180415113528-8bdace674401/go.mod h1:knK8fd+KPlGGqSUWogv1DQzGTwnfUvAi0cIoWyOG7+U= 28 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 29 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 30 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 31 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 32 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 33 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 34 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 35 | github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= 36 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 37 | github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= 38 | github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= 39 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 40 | github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= 41 | github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= 42 | github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= 43 | github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= 44 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 45 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 46 | github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= 47 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 48 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 49 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 50 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 51 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 52 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 53 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 54 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 55 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 56 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 57 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 58 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 59 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 60 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 61 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 62 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 63 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 64 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 65 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 66 | github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= 67 | github.com/google/flatbuffers v1.12.0 h1:/PtAHvnBY4Kqnx/xCQ3OIV9uYcSFGScBsWI3Oogeh6w= 68 | github.com/google/flatbuffers v1.12.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= 69 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 70 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 71 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 72 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 73 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 74 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 75 | github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= 76 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 77 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 78 | github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= 79 | github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= 80 | github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= 81 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 82 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 83 | github.com/mattn/go-pointer v0.0.1 h1:n+XhsuGeVO6MEAp7xyEukFINEa+Quek5psIR/ylA6o0= 84 | github.com/mattn/go-pointer v0.0.1/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc= 85 | github.com/mattn/go-tflite v1.0.4 h1:wpfNKjMr3IJz4xI+oUeHE70RU6Q5dZc0FK/X8vCWLAo= 86 | github.com/mattn/go-tflite v1.0.4/go.mod h1:j7bVlVHgKURK0p7AQOw3OqlGE2SVXqck7JsJo4wI+bc= 87 | github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= 88 | github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= 89 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 90 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 91 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 92 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 93 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 94 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 95 | github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= 96 | github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= 97 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 98 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 99 | github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 100 | github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 101 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 102 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 103 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 104 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 105 | github.com/tystuyfzand/go-onnxruntime v0.0.0-20230107221602-893efd995fc3 h1:rmAs0um3Y3XvXaDmc5lWIk+tJyHPPjw57J6rvnGtP0M= 106 | github.com/tystuyfzand/go-onnxruntime v0.0.0-20230107221602-893efd995fc3/go.mod h1:HaFez3aT4xi9WpOUQRunEK05+OITnaskSuyaCVWsPxk= 107 | github.com/xtgo/set v1.0.0 h1:6BCNBRv3ORNDQ7fyoJXRv+tstJz3m1JVFQErfeZz2pY= 108 | github.com/xtgo/set v1.0.0/go.mod h1:d3NHzGzSa0NmB2NhFyECA+QdRp29oEn2xbT+TpeFoM8= 109 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 110 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 111 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 112 | github.com/yut-kt/goft v0.1.0 h1:vCAeHyADiXDmgGnr8STckid83I5Agvl4oZd2G/4ljqY= 113 | github.com/yut-kt/goft v0.1.0/go.mod h1:Vg9d313p8lyMQt7hBs7VSWKxeiVEyLPTgfpJl8EC0/A= 114 | github.com/yut-kt/gomfcc v0.0.0-20220503093809-d81a01b6bf53 h1:st7vJE2WK0LI2bs+X1fOdSPtBzBHFwnJSXOCw6tKeJA= 115 | github.com/yut-kt/gomfcc v0.0.0-20220503093809-d81a01b6bf53/go.mod h1:lA8GiHQrKJ65c5TTJmY4iS4t5rv3p0WmNcpQDLyHTWc= 116 | github.com/yut-kt/gowindow v0.1.7 h1:cHch1I8E2tBbC9t6vyY8h8mrKEiZUrtXdRSJY2rMl6s= 117 | github.com/yut-kt/gowindow v0.1.7/go.mod h1:cVaGb9r0J2+NgVBb1UEJ3rYQ4CkzXXoHfX1JqWAciBk= 118 | go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= 119 | go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 h1:FyBZqvoA/jbNzuAWLQE2kG820zMAkcilx6BMjGbL/E4= 120 | go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= 121 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 122 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 123 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 124 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 125 | golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 126 | golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 127 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 128 | golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 129 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 130 | golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= 131 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y= 132 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 133 | golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= 134 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 135 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 136 | golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 137 | golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 138 | golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 139 | golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 140 | golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 141 | golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 142 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 143 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 144 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 145 | golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 146 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 147 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 148 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 149 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 150 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 151 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 152 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 153 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 154 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 155 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 156 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 157 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 158 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 159 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 160 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 161 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 162 | golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 163 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 164 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 165 | golang.org/x/net v0.0.0-20210917221730-978cfadd31cf h1:R150MpwJIv1MpS0N/pc+NhTM8ajzvlmxlY5OYsrevXQ= 166 | golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 167 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 168 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 169 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 170 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 171 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 172 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 173 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 174 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 175 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 176 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 177 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 178 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 179 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 180 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 181 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 182 | golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 183 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 184 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 185 | golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 186 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 187 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 188 | golang.org/x/sys v0.0.0-20210917161153-d61c044b1678 h1:J27LZFQBFoihqXoegpscI10HpjZ7B5WQLLKL2FZXQKw= 189 | golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 190 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 191 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 192 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 193 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 194 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 195 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 196 | golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 197 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 198 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 199 | golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 200 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 201 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 202 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 203 | golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 204 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 205 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 206 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 207 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 208 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 209 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 210 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 211 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 212 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 213 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 214 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 215 | gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= 216 | gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= 217 | gonum.org/v1/gonum v0.9.0 h1:KSrriwTGNTqNqyR6ZWWvwtInD/kl89sTrKsDfmbk2HU= 218 | gonum.org/v1/gonum v0.9.0/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= 219 | gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc= 220 | gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= 221 | gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= 222 | gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= 223 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 224 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 225 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 226 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 227 | google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 228 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 229 | google.golang.org/genproto v0.0.0-20200911024640-645f7a48b24f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 230 | google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4 h1:ysnBoUyeL/H6RCvNRhWHjKoDEmguI+mPU+qHgK8qv/w= 231 | google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= 232 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 233 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 234 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 235 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 236 | google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 237 | google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= 238 | google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 239 | google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q= 240 | google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= 241 | google.golang.org/grpc/cmd/protoc-gen-go-grpc v0.0.0-20200910201057-6591123024b3/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= 242 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 243 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 244 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 245 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 246 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 247 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 248 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 249 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 250 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 251 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 252 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 253 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 254 | google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= 255 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 256 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 257 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 258 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 259 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 260 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 261 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 262 | gorgonia.org/tensor v0.9.24 h1:8ahrfwO4iby+1ILObIqfjJa+wyA2RoCfJSS3LVERSRE= 263 | gorgonia.org/tensor v0.9.24/go.mod h1:1dsOegMm2n1obs69YnVJdp2oPSKx9Q9Tco5i7GEaXRg= 264 | gorgonia.org/vecf32 v0.9.0 h1:PClazic1r+JVJ1dEzRXgeiVl4g1/Hf/w+wUSqnco1Xg= 265 | gorgonia.org/vecf32 v0.9.0/go.mod h1:NCc+5D2oxddRL11hd+pCB1PEyXWOyiQxfZ/1wwhOXCA= 266 | gorgonia.org/vecf64 v0.9.0 h1:bgZDP5x0OzBF64PjMGC3EvTdOoMEcmfAh1VCUnZFm1A= 267 | gorgonia.org/vecf64 v0.9.0/go.mod h1:hp7IOWCnRiVQKON73kkC/AUMtEXyf9kGlVrtPQ9ccVA= 268 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 269 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 270 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 271 | -------------------------------------------------------------------------------- /listener.go: -------------------------------------------------------------------------------- 1 | package precise 2 | 3 | import ( 4 | "errors" 5 | "gorgonia.org/tensor" 6 | ) 7 | 8 | var ( 9 | ErrModelClosed = errors.New("model closed") 10 | ) 11 | 12 | func NewListener(model Model, p Params) (*Listener, error) { 13 | l := &Listener{ 14 | params: p, 15 | model: model, 16 | windowAudio: make([]int16, 0), 17 | mfccs: tensor.New(tensor.Of(tensor.Float32), tensor.WithShape(p.NFeatures(), p.NMFCC)), 18 | } 19 | 20 | config := DefaultThreshold 21 | config.Center = p.ThresholdCenter 22 | l.decoder = NewThresholdDecoder(p.ThresholdConfig, config) 23 | 24 | return l, nil 25 | } 26 | 27 | type Listener struct { 28 | params Params 29 | model Model 30 | windowAudio []int16 31 | mfccs *tensor.Dense 32 | decoder *ThresholdDecoder 33 | } 34 | 35 | func (p *Listener) updateVectors(audio []int16) tensor.Tensor { 36 | p.windowAudio = append(p.windowAudio, audio...) 37 | 38 | if len(p.windowAudio) >= p.params.WindowSamples() { 39 | var newFeatures tensor.Tensor 40 | newFeatures = mfccSpec(p.windowAudio, p.params) 41 | 42 | p.windowAudio = p.windowAudio[newFeatures.Shape()[0]*p.params.HopSamples():] 43 | 44 | if newFeatures.Shape()[0] > p.mfccs.Shape()[0] { 45 | // TODO: Slice only necessary features, this is done in Rust as: 46 | // nrows = len, dim = mfccs.Dim()? 47 | // new_features = new_features.slice(s![new_features.nrows() - self.mfccs.dim().0..,..]).to_owned(); 48 | newFeatures = tensorMust(newFeatures.Slice(tensor.S(newFeatures.Shape()[0]-p.mfccs.Shape()[0], newFeatures.Shape()[0]))) 49 | } 50 | 51 | // TODO: Concatenate mfccs 52 | // Rust example: 53 | // Axis = dimension? (0, top level) 54 | // nrows = len? 55 | // self.mfccs = concatenate![Axis(0), self.mfccs.slice(s![new_features.nrows()..,..]).to_owned(), new_features]; 56 | // self.mfccs = np.concatenate((self.mfccs[len(new_features):], new_features)) 57 | 58 | if newFeatures.Shape()[0] == p.mfccs.Shape()[0] { 59 | p.mfccs = newFeatures.(*tensor.Dense) 60 | } else { 61 | slicedMfccs := tensorMust(p.mfccs.Slice(tensor.S(newFeatures.Shape()[0], p.mfccs.Shape()[0]))) 62 | 63 | p.mfccs = tensorMust(tensor.Concat(0, slicedMfccs, newFeatures.(*tensor.Dense))) 64 | } 65 | } 66 | 67 | return p.mfccs 68 | } 69 | 70 | func (p *Listener) Update(audio []int16) (float32, error) { 71 | if p.model == nil { 72 | return -1, ErrModelClosed 73 | } 74 | 75 | mfccs := p.updateVectors(audio) 76 | 77 | rawOutput, err := p.model.Predict(mfccs) 78 | 79 | if err != nil { 80 | return -1, err 81 | } 82 | 83 | return p.decoder.Decode(rawOutput), nil 84 | } 85 | 86 | func (p *Listener) Close() error { 87 | err := p.model.Close() 88 | 89 | if err != nil { 90 | return err 91 | } 92 | 93 | p.model = nil 94 | 95 | return nil 96 | } 97 | -------------------------------------------------------------------------------- /mfcc.go: -------------------------------------------------------------------------------- 1 | package precise 2 | 3 | import ( 4 | "github.com/yut-kt/gomfcc" 5 | "gorgonia.org/tensor" 6 | ) 7 | 8 | var ( 9 | int16Divider = 1.0 / 32768.0 10 | ) 11 | 12 | // int16ToFloatSlice Converts an int16 slice to float64 audio 13 | func int16ToFloatSlice(input []int16) []float64 { 14 | output := make([]float64, len(input)) 15 | 16 | for i, s := range input { 17 | output[i] = float64(s) * int16Divider 18 | } 19 | 20 | return output 21 | } 22 | 23 | func mfccSpec(audio []int16, params Params) *tensor.Dense { 24 | mfcc := gomfcc.NewGoMFCC(int16ToFloatSlice(audio), params.SampleRate, gomfcc.LowFrequency(0)) 25 | 26 | // window_t and hop_t are in fractions of a second, while we need milliseconds 27 | features := mfcc.GetFeatureByMS(params.NMFCC, params.NFilt, float64(params.WindowT)*1000, float64(params.HopT)*1000) 28 | 29 | backing := make([]float32, len(features)*len(features[0])) 30 | 31 | // Create a backing slice of float32s for the Tensor 32 | for i, featureList := range features { 33 | for index := 0; index < len(featureList); index++ { 34 | backing[i*len(featureList)+index] = float32(featureList[index]) 35 | } 36 | } 37 | 38 | return tensor.New(tensor.Of(tensor.Float32), tensor.WithShape(len(features), len(features[0])), tensor.WithBacking(backing)) 39 | } 40 | -------------------------------------------------------------------------------- /model.go: -------------------------------------------------------------------------------- 1 | package precise 2 | 3 | import ( 4 | "errors" 5 | "gorgonia.org/tensor" 6 | ) 7 | 8 | var ( 9 | ErrUnexpectedType = errors.New("unexpected tensor type") 10 | ) 11 | 12 | type Model interface { 13 | Predict(inputData tensor.Tensor) (float32, error) 14 | Close() error 15 | } 16 | -------------------------------------------------------------------------------- /model_onnx.go: -------------------------------------------------------------------------------- 1 | //go:build onnx 2 | 3 | package precise 4 | 5 | import ( 6 | "context" 7 | "errors" 8 | "github.com/ivansuteja96/go-onnxruntime" 9 | "gorgonia.org/tensor" 10 | ) 11 | 12 | type DeviceType int 13 | 14 | const ( 15 | OnnxCPU DeviceType = 1 16 | OnnxCUDA DeviceType = 2 17 | OnnxTensorRT DeviceType = 3 18 | ) 19 | 20 | func (t DeviceType) String() string { 21 | switch t { 22 | case OnnxCPU: 23 | return "CPU" 24 | case OnnxCUDA: 25 | return "CUDA" 26 | case OnnxTensorRT: 27 | return "TensorRT" 28 | } 29 | return "" 30 | } 31 | 32 | // NewONNXModel creates a new onnx model 33 | func NewONNXModel(modelPath string, deviceType DeviceType) (Model, error) { 34 | ortEnvDet := onnxruntime.NewORTEnv(onnxruntime.ORT_LOGGING_LEVEL_ERROR, "development") 35 | ortDetSO := onnxruntime.NewORTSessionOptions() 36 | 37 | switch deviceType { 38 | case OnnxTensorRT: 39 | ortDetSO.AppendExecutionProviderTensorRT(onnxruntime.TensorRTOptions{ 40 | DeviceID: 0, 41 | MaxWorkspaceSize: 1 * 1024 * 1024 * 1024, 42 | MaxPartitionIterations: 1000, 43 | MinSubgraphSize: 5, 44 | }) 45 | fallthrough 46 | case OnnxCUDA: 47 | ortDetSO.AppendExecutionProviderCUDA(onnxruntime.CudaOptions{ 48 | DeviceID: 0, 49 | GPUMemorylimit: 2 * 1024 * 1024 * 1024, 50 | DoCopyInDefaultStream: true, 51 | }) 52 | } 53 | 54 | model, err := onnxruntime.NewORTSession(ortEnvDet, modelPath, ortDetSO) 55 | 56 | if err != nil { 57 | return nil, err 58 | } 59 | 60 | return &ONNXModel{ 61 | model: model, 62 | }, nil 63 | } 64 | 65 | // ONNXModel represents a tensorflow lite model 66 | type ONNXModel struct { 67 | model *onnxruntime.ORTSession 68 | ctx context.Context 69 | } 70 | 71 | // Predict sends the input data into the input tensor, then invokes the model 72 | func (m *ONNXModel) Predict(inputData tensor.Tensor) (float32, error) { 73 | if m.model == nil { 74 | return -1, ErrModelClosed 75 | } 76 | 77 | data := inputData.Data().([]float32) 78 | 79 | shape := inputData.Shape() 80 | 81 | res, err := m.model.Predict([]onnxruntime.TensorValue{ 82 | { 83 | Value: data, 84 | // Shape is an interesting one 85 | // We're using 1 input tensor, with shape 29, 13 86 | Shape: []int64{1, int64(shape[0]), int64(shape[1])}, 87 | }, 88 | }) 89 | 90 | if err != nil { 91 | return -1, err 92 | } 93 | 94 | outputVals := res[0].Value 95 | 96 | if v, ok := outputVals.([]float32); ok { 97 | return v[0], nil 98 | } 99 | 100 | return -1, errors.New("unexpected output value type") 101 | } 102 | 103 | // Close cleans up the model after use 104 | func (m *ONNXModel) Close() error { 105 | m.model = nil 106 | return nil 107 | } 108 | -------------------------------------------------------------------------------- /model_tflite.go: -------------------------------------------------------------------------------- 1 | package precise 2 | 3 | import ( 4 | "errors" 5 | "github.com/mattn/go-tflite" 6 | "gorgonia.org/tensor" 7 | "sync" 8 | ) 9 | 10 | // NewTFLiteModel creates a new tensorflow lite model 11 | func NewTFLiteModel(modelPath string) (Model, error) { 12 | model := tflite.NewModelFromFile(modelPath) 13 | 14 | if model == nil { 15 | return nil, errors.New("cannot load model") 16 | } 17 | 18 | options := tflite.NewInterpreterOptions() 19 | 20 | interpreter := tflite.NewInterpreter(model, options) 21 | 22 | interpreter.AllocateTensors() 23 | 24 | return &TFLiteModel{ 25 | model: model, 26 | interpreter: interpreter, 27 | options: options, 28 | lock: new(sync.Mutex), 29 | }, nil 30 | } 31 | 32 | // TFLiteModel represents a tensorflow lite model 33 | type TFLiteModel struct { 34 | model *tflite.Model 35 | options *tflite.InterpreterOptions 36 | interpreter *tflite.Interpreter 37 | lock *sync.Mutex 38 | } 39 | 40 | // Predict sends the input data into the input tensor, then invokes the model 41 | func (m *TFLiteModel) Predict(inputData tensor.Tensor) (float32, error) { 42 | if m.model == nil { 43 | return -1, ErrModelClosed 44 | } 45 | 46 | input := m.interpreter.GetInputTensor(0) 47 | 48 | copy(input.Float32s(), inputData.Data().([]float32)) 49 | 50 | m.interpreter.Invoke() 51 | 52 | output := m.interpreter.GetOutputTensor(0) 53 | 54 | if output.Type() != tflite.Float32 { 55 | return -1, ErrUnexpectedType 56 | } 57 | 58 | return output.Float32s()[0], nil 59 | } 60 | 61 | // Close cleans up the model after use 62 | func (m *TFLiteModel) Close() error { 63 | m.lock.Lock() 64 | defer m.lock.Unlock() 65 | 66 | if m.model == nil { 67 | return nil 68 | } 69 | 70 | m.model.Delete() 71 | m.options.Delete() 72 | m.interpreter.Delete() 73 | 74 | m.model = nil 75 | return nil 76 | } 77 | -------------------------------------------------------------------------------- /params.go: -------------------------------------------------------------------------------- 1 | package precise 2 | 3 | import "math" 4 | 5 | // Params is the Precise model parameter list. 6 | // This can be loaded from JSON, however for tflite models these are hardcoded. 7 | // Defaults are set for these elsewhere via NewParams, not using the tags, 8 | // but they are there for reference 9 | type Params struct { 10 | WindowT float32 `json:"window_t" default:"0.1"` 11 | HopT float32 `json:"hop_t" default:"0.05"` 12 | BufferT float32 `json:"buffer_t" default:"1.5"` 13 | SampleRate int `json:"sample_rate" default:"16000"` 14 | SampleDepth int `json:"sample_depth" default:"2"` 15 | NMFCC int `json:"n_mfcc" default:"13"` 16 | NFilt int `json:"n_filt" default:"20"` 17 | NFft int `json:"n_fft" default:"512"` 18 | UseDelta bool `json:"use_delta" default:"false"` 19 | ThresholdConfig MuStd `json:"threshold_config" default:"[[6,4]]"` 20 | ThresholdCenter float32 `json:"threshold_center" default:"0.2"` 21 | } 22 | 23 | func NewParams() Params { 24 | return Params{ 25 | WindowT: 0.1, 26 | HopT: 0.05, 27 | BufferT: 1.5, 28 | SampleRate: 16000, 29 | SampleDepth: 2, 30 | NMFCC: 13, 31 | NFilt: 20, 32 | NFft: 512, 33 | ThresholdConfig: MuStd{{6, 4}}, 34 | ThresholdCenter: 0.2, 35 | } 36 | } 37 | 38 | func (p Params) BufferSamples() int { 39 | samples := int(float32(p.SampleRate)*p.BufferT + 0.5) 40 | return p.HopSamples() * int(math.Floor(float64(samples/p.HopSamples()))) 41 | } 42 | 43 | func (p Params) NFeatures() int { 44 | return 1 + int(math.Floor(float64(p.BufferSamples()-p.WindowSamples())/float64(p.HopSamples()))) 45 | } 46 | 47 | func (p Params) WindowSamples() int { 48 | return int(float32(p.SampleRate)*p.WindowT + 0.5) 49 | } 50 | 51 | func (p Params) HopSamples() int { 52 | return int(float32(p.SampleRate)*p.HopT + 0.5) 53 | } 54 | -------------------------------------------------------------------------------- /runner.go: -------------------------------------------------------------------------------- 1 | package precise 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | type ActivationFunc func() 8 | 9 | type PredictionFunc func(prob float32) 10 | 11 | type ExitFunc func(err error) 12 | 13 | type Option func(*Runner) 14 | 15 | // WithDetectorOpts sets detector options 16 | func WithDetectorOpts(opts ...TriggerOption) Option { 17 | return func(r *Runner) { 18 | r.detectorOpts = opts 19 | } 20 | } 21 | 22 | // WithActivationFunc sets the func called when activated 23 | func WithActivationFunc(f ActivationFunc) Option { 24 | return func(r *Runner) { 25 | r.OnActivation = f 26 | } 27 | } 28 | 29 | // WithPredictionFunc sets the func called after prediction 30 | func WithPredictionFunc(f PredictionFunc) Option { 31 | return func(r *Runner) { 32 | r.OnPrediction = f 33 | } 34 | } 35 | 36 | // WithPredictionFunc sets the func called after prediction 37 | func WithExitFunc(f ExitFunc) Option { 38 | return func(r *Runner) { 39 | r.OnExit = f 40 | } 41 | } 42 | 43 | // NewRunner creates a new network runner 44 | func NewRunner(listener *Listener, chunkSize int, opts ...Option) *Runner { 45 | r := &Runner{ 46 | listener: listener, 47 | chunkSize: chunkSize, 48 | sampleCh: make(chan []int16), 49 | closeCh: make(chan bool), 50 | } 51 | 52 | for _, opt := range opts { 53 | opt(r) 54 | } 55 | 56 | r.detector = NewTriggerDetector(chunkSize, r.detectorOpts...) 57 | 58 | r.Start() 59 | 60 | return r 61 | } 62 | 63 | type Runner struct { 64 | listener *Listener 65 | detector *TriggerDetector 66 | detectorOpts []TriggerOption 67 | chunkSize int 68 | running bool 69 | sampleCh chan []int16 70 | closeCh chan bool 71 | 72 | OnPrediction PredictionFunc 73 | OnActivation ActivationFunc 74 | OnExit ExitFunc 75 | } 76 | 77 | // Start will start the runner and the goroutine 78 | func (r *Runner) Start() { 79 | if r.running { 80 | return 81 | } 82 | 83 | r.running = true 84 | 85 | go r.handlePredictions() 86 | } 87 | 88 | // Stop will stop the runner without closing it. 89 | func (r *Runner) Stop() { 90 | if !r.running { 91 | return 92 | } 93 | 94 | r.running = false 95 | } 96 | 97 | // Close stops the neural network runner 98 | func (r *Runner) Close() error { 99 | r.Stop() 100 | 101 | close(r.closeCh) 102 | 103 | if r.listener != nil { 104 | return r.listener.Close() 105 | } 106 | 107 | return nil 108 | } 109 | 110 | // Write allows a Runner to act as an io.Writer 111 | func (r *Runner) Write(b []byte) (int, error) { 112 | samples := bytesToSamples(b) 113 | 114 | r.sampleCh <- samples 115 | 116 | return len(b) % 2, nil 117 | } 118 | 119 | // Queue passes in samples directly to the channel 120 | func (r *Runner) Queue(samples []int16) { 121 | r.sampleCh <- samples 122 | } 123 | 124 | // ReadFrom allows the Runner to simply read from a reader 125 | func (r *Runner) ReadFrom(reader io.Reader) (int64, error) { 126 | chunkSize := r.chunkSize 127 | 128 | if chunkSize == 0 || chunkSize == -1 { 129 | chunkSize = 2048 130 | } 131 | 132 | buf := make([]byte, chunkSize) 133 | 134 | var total int64 135 | 136 | for { 137 | read, err := reader.Read(buf) 138 | 139 | if err == io.EOF { 140 | break 141 | } else if err != nil { 142 | return -1, err 143 | } 144 | 145 | total += int64(read) 146 | 147 | r.sampleCh <- bytesToSamples(buf[:read]) 148 | } 149 | 150 | return total, nil 151 | } 152 | 153 | // handlePredictions is a constantly running goroutine to read samples from our chan 154 | func (r *Runner) handlePredictions() { 155 | var prob float32 156 | var err error 157 | 158 | loop: 159 | for r.running { 160 | select { 161 | case samples, ok := <-r.sampleCh: 162 | if !ok { 163 | break loop 164 | } 165 | prob, err = r.listener.Update(samples) 166 | 167 | if err != nil { 168 | break 169 | } 170 | 171 | if r.OnPrediction != nil { 172 | r.OnPrediction(prob) 173 | } 174 | 175 | if r.detector.Update(prob) { 176 | r.OnActivation() 177 | } 178 | case <-r.closeCh: 179 | break loop 180 | } 181 | } 182 | 183 | r.running = false 184 | 185 | if r.OnExit != nil { 186 | r.OnExit(err) 187 | } 188 | } 189 | 190 | // bytesToSamples converts bytes to 16-bit samples 191 | func bytesToSamples(b []byte) []int16 { 192 | readable := len(b) / 2 193 | 194 | samples := make([]int16, readable) 195 | 196 | for i := 0; i < readable; i++ { 197 | samples[i] = int16(b[i*2]) + int16(b[i*2+1])<<8 198 | } 199 | 200 | return samples 201 | } 202 | -------------------------------------------------------------------------------- /runner_test.go: -------------------------------------------------------------------------------- 1 | package precise 2 | 3 | import ( 4 | "fmt" 5 | "github.com/cryptix/wav" 6 | "os" 7 | "testing" 8 | ) 9 | 10 | func TestNewRunner(t *testing.T) { 11 | testRunModel(t, "out.wav") 12 | } 13 | 14 | func testRunModel(t *testing.T, inputFile string) { 15 | model, err := NewONNXModel("astra.onnx", OnnxCUDA) 16 | 17 | if err != nil { 18 | t.Fatal("Unable to load model") 19 | } 20 | 21 | p := NewParams() 22 | 23 | l, err := NewListener(model, p) 24 | 25 | t.Log("Testing file", inputFile) 26 | 27 | f, err := os.Open(inputFile) 28 | 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | 33 | stat, err := f.Stat() 34 | 35 | if err != nil { 36 | t.Fatal(err) 37 | } 38 | 39 | wr, err := wav.NewReader(f, stat.Size()) 40 | 41 | if err != nil { 42 | t.Fatal(err) 43 | } 44 | 45 | wdr, err := wr.GetDumbReader() 46 | 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | 51 | activated := false 52 | 53 | ch := make(chan struct{}) 54 | 55 | t.Log("Setting up runner") 56 | 57 | var runner *Runner 58 | 59 | opts := []Option{ 60 | WithActivationFunc(func() { 61 | activated = true 62 | }), 63 | WithExitFunc(func(err error) { 64 | close(ch) 65 | 66 | runner.Close() 67 | }), 68 | WithDetectorOpts(WithSensitivity(0.8)), 69 | } 70 | 71 | runner = NewRunner(l, -1, opts...) 72 | 73 | t.Log("Reading data") 74 | 75 | read, err := runner.ReadFrom(wdr) 76 | 77 | if err != nil { 78 | t.Fatal("Unable to read wav data", err) 79 | } else { 80 | t.Log("Successfully read", read, "bytes") 81 | } 82 | 83 | runner.Stop() 84 | 85 | <-ch 86 | 87 | if activated { 88 | t.Log("Sample activated") 89 | } else { 90 | t.Log("No activation found") 91 | } 92 | } 93 | 94 | var benchResult float32 95 | 96 | func BenchmarkTFLiteRunner(b *testing.B) { 97 | model, err := NewTFLiteModel("astra.tflite") 98 | 99 | if err != nil { 100 | b.Fatal("Unable to load model") 101 | } 102 | 103 | p := NewParams() 104 | 105 | l, err := NewListener(model, p) 106 | 107 | defer l.Close() 108 | 109 | samples, err := loadSamples("out.wav") 110 | 111 | if err != nil { 112 | b.Fatal(err) 113 | } 114 | 115 | mfccs := l.updateVectors(samples) 116 | 117 | var val float32 118 | 119 | for i := 0; i < b.N; i++ { 120 | val, err = l.model.Predict(mfccs) 121 | 122 | if err != nil { 123 | b.Fatal(err) 124 | } 125 | } 126 | 127 | benchResult = val 128 | } 129 | 130 | func BenchmarkOnnxRunner(b *testing.B) { 131 | for t := OnnxCPU; t <= OnnxCUDA; t++ { 132 | b.Run(fmt.Sprintf("onnx_%s", t.String()), func(b *testing.B) { 133 | b.StopTimer() 134 | model, err := NewONNXModel("astra.onnx", t) 135 | 136 | if err != nil { 137 | b.Fatal("Unable to load model") 138 | } 139 | 140 | p := NewParams() 141 | 142 | l, err := NewListener(model, p) 143 | 144 | defer l.Close() 145 | 146 | samples, err := loadSamples("out.wav") 147 | 148 | if err != nil { 149 | b.Fatal(err) 150 | } 151 | 152 | mfccs := l.updateVectors(samples) 153 | 154 | var val float32 155 | 156 | b.StartTimer() 157 | 158 | for i := 0; i < b.N; i++ { 159 | val, err = l.model.Predict(mfccs) 160 | 161 | if err != nil { 162 | b.Fatal(err) 163 | } 164 | } 165 | 166 | benchResult = val 167 | }) 168 | } 169 | } 170 | 171 | func loadSamples(inputFile string) ([]int16, error) { 172 | f, err := os.Open(inputFile) 173 | 174 | if err != nil { 175 | return nil, err 176 | } 177 | 178 | defer f.Close() 179 | 180 | stat, err := f.Stat() 181 | 182 | if err != nil { 183 | return nil, err 184 | } 185 | 186 | wr, err := wav.NewReader(f, stat.Size()) 187 | 188 | if err != nil { 189 | return nil, err 190 | } 191 | 192 | samples := make([]int16, wr.GetSampleCount()) 193 | 194 | for i := 0; i < len(samples); i++ { 195 | sample, err := wr.ReadSample() 196 | 197 | if err != nil { 198 | return nil, err 199 | } 200 | 201 | samples[i] = int16(sample) 202 | } 203 | 204 | return samples, nil 205 | } 206 | -------------------------------------------------------------------------------- /threshold.go: -------------------------------------------------------------------------------- 1 | package precise 2 | 3 | import ( 4 | "gorgonia.org/tensor" 5 | "math" 6 | "sort" 7 | ) 8 | 9 | // MuStd represents threshold configs with easy to use functions. 10 | type MuStd [][]float32 11 | 12 | // Min calculates the minimum with minZ, then sorts and returns the lowest value 13 | func (m MuStd) Min(minZ float32) float32 { 14 | vals := m.Calculate(minZ) 15 | 16 | sort.Slice(vals, func(i, j int) bool { 17 | return vals[i] < vals[j] 18 | }) 19 | 20 | return vals[0] 21 | } 22 | 23 | // Max calculates the maximum with maxZ, then sorts and returns the highest value 24 | func (m MuStd) Max(maxZ float32) float32 { 25 | vals := m.Calculate(maxZ) 26 | 27 | sort.Slice(vals, func(i, j int) bool { 28 | return vals[i] > vals[j] 29 | }) 30 | 31 | return vals[0] 32 | } 33 | 34 | // Calculate applies the value to the configuration 35 | func (m MuStd) Calculate(value float32) []float32 { 36 | out := make([]float32, len(m)) 37 | 38 | for i := 0; i < len(m); i++ { 39 | out[i] = m[i][0] + value*m[i][1] 40 | } 41 | 42 | return out 43 | } 44 | 45 | type ThresholdOptions struct { 46 | MinZ float32 47 | MaxZ float32 48 | Center float32 49 | Resolution int 50 | } 51 | 52 | // DefaultThreshold is the default set of options for the decoder. 53 | var DefaultThreshold = ThresholdOptions{ 54 | MinZ: -4.0, 55 | MaxZ: 4.0, 56 | Center: 0.5, 57 | Resolution: 200, 58 | } 59 | 60 | // NewThresholdDecoder creates a new ThresholdDecoder with calculated distributions. 61 | func NewThresholdDecoder(muStds MuStd, options ThresholdOptions) *ThresholdDecoder { 62 | min := int(muStds.Min(options.MinZ)) 63 | max := int(muStds.Max(options.MaxZ)) 64 | 65 | td := &ThresholdDecoder{ 66 | options: options, 67 | MinOut: min, 68 | MaxOut: max, 69 | OutRange: max - min, 70 | } 71 | 72 | var cumulative float32 73 | 74 | td.cd = tensorMust(td.calcPd(muStds).Apply(func(v float32) float32 { 75 | cumulative += v 76 | return cumulative 77 | })) 78 | 79 | return td 80 | } 81 | 82 | // ThresholdDecoder decodes raw neural network output 83 | type ThresholdDecoder struct { 84 | options ThresholdOptions 85 | minZ, maxZ float32 86 | MinOut int 87 | MaxOut int 88 | OutRange int 89 | cd *tensor.Dense 90 | } 91 | 92 | // Decode takes raw neural network output and normalizes it using 93 | // probability distribution 94 | func (t *ThresholdDecoder) Decode(rawOutput float32) float32 { 95 | if rawOutput == 1 || rawOutput == 0 { 96 | return rawOutput 97 | } 98 | 99 | var cp float32 100 | 101 | if t.OutRange == 0 { 102 | if rawOutput > float32(t.MinOut) { 103 | cp = 1 104 | } else { 105 | cp = 0 106 | } 107 | } else { 108 | ratio := (asigmoid(rawOutput) - float32(t.MinOut)) / float32(t.OutRange) 109 | ratio = float32(math.Min(math.Max(float64(ratio), 0.0), 1.0)) 110 | 111 | cp = t.cd.GetF32(int(ratio*float32(t.cd.Size()-1) + 0.5)) 112 | } 113 | 114 | if cp < t.options.Center { 115 | return 0.5 * cp / t.options.Center 116 | } 117 | 118 | return 0.5 + 0.5*(cp-t.options.Center)/(1.0-t.options.Center) 119 | } 120 | 121 | // calcPd Fills a list with probability distributions 122 | func (t *ThresholdDecoder) calcPd(muStds MuStd) *tensor.Dense { 123 | points := LinSpace(float32(t.MinOut), float32(t.MaxOut), t.options.Resolution*t.OutRange) 124 | 125 | pointsSize := points.Shape()[0] 126 | 127 | // Why this you ask? Well, it's hard to create a tensor from multiple tensors. 128 | items := make([]float32, len(muStds)*pointsSize) 129 | 130 | for i, muStd := range muStds { 131 | d := pdf(points, muStd[0], muStd[1]) 132 | 133 | // Copy into a pre-allocated slice 134 | copy(items[i*pointsSize:], d.Data().([]float32)) 135 | } 136 | 137 | data := tensor.New(tensor.Of(tensor.Float32), tensor.WithShape(len(muStds), pointsSize), tensor.WithBacking(items)) 138 | 139 | // Dimensions should be x, len(points) 140 | 141 | summed := tensorMust(data.Sum(0)) 142 | 143 | // After sum, we should have a single len(points) tensor 144 | 145 | return tensorMust(summed.DivScalar(float32(t.options.Resolution*len(muStds)), true)) 146 | } 147 | 148 | // asigmoid Inverse sigmoid (logit) for scalars 149 | func asigmoid(x float32) float32 { 150 | return float32(-math.Log(float64(1.0/x - 1.0))) 151 | } 152 | 153 | // pdf is a Probability density function (normal distribution) 154 | func pdf(x *tensor.Dense, mu, std float32) *tensor.Dense { 155 | if std == 0 { 156 | return tensorMust(x.MulScalar(0.0, true)) 157 | } 158 | 159 | // return (1.0 / (std * sqrt(2 * pi))) * np.exp(-(x - mu) ** 2 / (2 * std ** 2)) 160 | 161 | // (1.0 / (std * sqrt(2 * pi))) 162 | a1 := 1.0 / (std * float32(math.Sqrt(2.0*math.Pi))) 163 | 164 | //let b1 = -(x - mu).mapv(|v|v.powf(2.0)); 165 | muSub := tensorMust(x.SubScalar(mu, true)) 166 | muSub = tensorMust(muSub.PowScalar(float32(2.0), true)) 167 | 168 | b1 := tensorMust(muSub.Apply(func(v float32) float32 { 169 | return -v 170 | })) 171 | 172 | //let b2 = b1 / (2.0 * std.powf(2.0)); 173 | 174 | b2 := tensorMust(b1.DivScalar(float32(2.0*math.Pow(float64(std), 2.0)), true)) 175 | //a1 * (b2.mapv(|v|v.exp())) 176 | 177 | // a1 * np.exp(...) 178 | return tensorMust(tensor.Mul(a1, tensorMust(tensor.Exp(b2)))) 179 | } 180 | 181 | // tensorMust is a simple utility that takes the output of a tensor operation and 182 | // requires err to be nil 183 | // This bypasses some unnecessary error checking on tensor operations 184 | func tensorMust(t tensor.Tensor, err error) *tensor.Dense { 185 | if err != nil { 186 | panic(err) 187 | } 188 | 189 | return t.(*tensor.Dense) 190 | } 191 | 192 | // LinSpace returns evenly spaced numbers over a specified closed interval. 193 | func LinSpace(start, stop float32, num int) (res *tensor.Dense) { 194 | if num <= 0 { 195 | return tensor.New() 196 | } 197 | if num == 1 { 198 | return tensor.New(tensor.WithBacking([]float32{start})) 199 | } 200 | 201 | step := (stop - start) / float32(num-1) 202 | resFloats := make([]float32, num) 203 | resFloats[0] = start 204 | for i := 1; i < num; i++ { 205 | resFloats[i] = start + float32(i)*step 206 | } 207 | resFloats[num-1] = stop 208 | 209 | return tensor.New(tensor.WithShape(len(resFloats)), tensor.WithBacking(resFloats)) 210 | } 211 | -------------------------------------------------------------------------------- /threshold_test.go: -------------------------------------------------------------------------------- 1 | package precise 2 | 3 | import "testing" 4 | 5 | func TestNewThresholdDecoder(t *testing.T) { 6 | td := NewThresholdDecoder(MuStd{{6, 2}}, DefaultThreshold) 7 | 8 | t.Log("Decoded value:", td.Decode(0.9585)) 9 | } 10 | --------------------------------------------------------------------------------