2023/9/7

install and setup snpe 2.14 in ubuntu 20.04

目前最新的 snpe (2.14) 要用 qpm 來安裝。
所以download 完 snpe sdk後,要先安裝qpm.
qpm 裝完,login 後,會是一個 daemon(service),就可以用 qpm-cli 命令。

用qpm-cli 解開/安裝 snpe..不管在哪裡解開,都會 install 到:
qpm-cli --extract XXOO.qik
SUCCESS: Installed qualcomm_neural_processing_sdk.Core at /opt/qcom/aistack/snpe/2.14.0.230828
裝完可以用:
qpm-cli --info qualcomm_neural_processing_sdk

Product Name           : qualcomm_neural_processing_sdk
Product Classification : Binary
Installed version      : None
Available version(s)   : 2.14.0.230828
                         2.13.4.230831
                         2.13.2.230822
                         2.13.0.230730
                         2.12.0.230626
                         2.11.0.230603
                         2.10.40.4
安裝完後的docs下,有 html 的文件。

follow 這個附的文件,setup,這版 2023/9 月的版本,是用 ubuntu 20.04
提供兩個 script 來檢查/安裝 需要個 package:
  • check-python-dependency
  • check-linux-dependency.sh
其中 check-python-denpency 需要在 virtual env (VENV 或 conda) 下,所以要為 snpe create 一個 python 環境。
文件說明 default 是 3.8,但是如果是要 run tensorflow 1.15.0,就要用 3.6。
先用 3.8 來做..用 conda create snpe3.8

然後 run envsetup.sh
$ source /opt/qcom/aistack/snpe/2.14.0.230828/bin/envsetup.sh 
[INFO] AISW SDK environment set
[INFO] SNPE_ROOT: /opt/qcom/aistack/snpe/2.14.0.230828
check python.. 結果一堆 error:
$ /opt/qcom/aistack/snpe/2.14.0.230828/bin/check-python-dependency 
/opt/qcom/aistack/snpe/2.14.0.230828/bin/check-python-dependency:55: DeprecationWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html
  import pkg_resources
WARNING: attrs installed version: 21.4.0 does not match tested version: 22.2.0
WARNING: decorator installed version: 4.4.2 does not match tested version: 5.1.1
WARNING: joblib installed version: 1.1.0 does not match tested version: 1.0.1
WARNING: packaging installed version: 21.3 does not match tested version: 21.0
WARNING: pillow installed version: 9.4.0 does not match tested version: 6.2.1
WARNING: scipy installed version: 1.8.1 does not match tested version: 1.9.1
Python Modules missing: absl-py, invoke, lxml, mako, matplotlib, numpy, opencv-python, pandas, pathlib2, protobuf, pytest, pyyaml, six, tabulate
Installing missing modules using pip3
Installing absl-py version: 0.13.0
ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.
nbconvert 6.5.0 requires entrypoints>=0.2.2, which is not installed.
Installing invoke version: 2.0.0
Installing lxml version: 4.6.2
Installing mako version: 1.1.0
Installing matplotlib version: 3.3.4
ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.
moviepy 1.0.3 requires requests<3.0,>=2.8.1, which is not installed.
Installing numpy version: 1.23.5
ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.
moviepy 1.0.3 requires requests<3.0,>=2.8.1, which is not installed.
Installing opencv-python version: 4.5.2.52
Installing pandas version: 1.1.5
Installing pathlib2 version: 2.3.6
Installing protobuf version: 3.19.6
Installing pytest version: 7.0.1
Installing pyyaml version: 3.10
ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.
naked 0.1.31 requires requests, which is not installed.
Installing six version: 1.16.0
Installing tabulate version: 0.8.5
用 pip 分別安裝 error 的 package,應該過了,但是 version 不一定一樣。

文件說明 android ndk 經過 snpe 認證的是 r19c.
用 android-studio 的 sdk-manager 來看,19版 只有 19.2
裝完看 CHANGELOG.md,剛好是 r19c,所以 path 就是 Android/Sdk/ndk/19.2.5345600

所以設完變數,run check...
$export ANDROID_NDK_ROOT=/home/charles-chang/Android/Sdk/ndk/19.2.5345600
$export PATH=${ANDROID_NDK_ROOT}:${PATH}
$/opt/qcom/aistack/snpe/2.14.0.230828/bin/envcheck -n
Checking Android NDK Environment
--------------------------------------------------------------
[INFO] Found ndk-build at /home/charles-chang/Android/Sdk/ndk/19.2.5345600/ndk-build and ANDROID_NDK_ROOT is also set.
--------------------------------------------------------------
$ /opt/qcom/aistack/snpe/2.14.0.230828/bin/envcheck -c
Checking Clang-9 Environment
--------------------------------------------------------------
[INFO] Found clang++-9 at /usr/bin/clang++-9
--------------------------------------------------------------
另外,pip install torch==1.8.1 onnx==1.11.0 tensorflow==2.10.1 tflite==2.3.0

2023/9/6

/proc/meminfo MemAvaileble

在 /proc/meminfo.c:
    available = si_mem_available();
    ...

    show_val_kb(m, "MemTotal:       ", i.totalram);
    show_val_kb(m, "MemFree:        ", i.freeram);
    show_val_kb(m, "MemAvailable:   ", available);
    ...
在 mm/page_alloc.c
long si_mem_available(void)
{
    long available;
    unsigned long pagecache;
    unsigned long wmark_low = 0;
    unsigned long pages[NR_LRU_LISTS];
    unsigned long reclaimable;
    struct zone *zone;
    int lru;

    for (lru = LRU_BASE; lru < NR_LRU_LISTS; lru++)
        pages[lru] = global_node_page_state(NR_LRU_BASE + lru);

    for_each_zone(zone)
        wmark_low += low_wmark_pages(zone);

    /*
     * Estimate the amount of memory available for userspace allocations,
     * without causing swapping.
     */
    available = global_zone_page_state(NR_FREE_PAGES) - totalreserve_pages;

    /*
     * Not all the page cache can be freed, otherwise the system will
     * start swapping. Assume at least half of the page cache, or the
     * low watermark worth of cache, needs to stay.
     */
    pagecache = pages[LRU_ACTIVE_FILE] + pages[LRU_INACTIVE_FILE];
    pagecache -= min(pagecache / 2, wmark_low);
    available += pagecache;

    /*
     * Part of the reclaimable slab and other kernel memory consists of
     * items that are in use, and cannot be freed. Cap this estimate at the
     * low watermark.
     */
    reclaimable = global_node_page_state(NR_SLAB_RECLAIMABLE) +
            global_node_page_state(NR_KERNEL_MISC_RECLAIMABLE);
    available += reclaimable - min(reclaimable / 2, wmark_low);

    if (available < 0)
        available = 0;
    return available;
}
所以 MemAvailable 是 free + cache + reclaimable slab (buffer?)

2023/9/4

windows10 : pyopengl , glutInit( ) error : OpenGL.error.NullFunctionError

ref: windows7 下run 的好好的,升級到windows10 之後就fail 了,
用 example code 來測試:
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *

def Draw():
        glClear(GL_COLOR_BUFFER_BIT)
        glutWireTeapot(0.5)
        glFlush()

glutInit()
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA)
glutInitWindowSize(300,300)
glutCreateWindow(b"teapot")
glutDisplayFunc(Draw)
glutIdleFunc(Draw)
glutMainLoop()

if __name__ == '__main__':
        Draw()
出現 Error:
Traceback (most recent call last):
  File "D:\teapot.py", line 11, in &;t;module>
    glutInit()
  File "D:\Python311\Lib\site-packages\OpenGL\GLUT\special.py", line 333, in glutInit
    _base_glutInit( ctypes.byref(count), holder )
  File "D:\Python311\Lib\site-packages\OpenGL\platform\baseplatform.py", line 423, in __call__
     raise error.NullFunctionError(
OpenGL.error.NullFunctionError: Attempt to call an undefined function glutInit, check for bool(glutInit) before calling  
結果google 說是 pypl official 的 opengl package 沒有包到兩個 dlll...
pipewire 0.3.78-1 (https://pipewire-debian.github.io)
 

  Debian Package - 

	- enable modemmanager

  Pipewire -

    - For more : https://gitlab.freedesktop.org/pipewire/pipewire/-/releases
  .

Troubleshooting - 

  - Have any package regarding issue? report on github :
    https://github.com/pipewire-debian/pipewire-debian/issues/new/choose

  - Upstream recommends to use 'WirePlumber' instead 'pipewire-media-session'      
    as session manager, to get it add another PPA,      
    'sudo add-apt-repository ppa:pipewire-debian/wireplumber-upstream'      
    For more instruction read : https://pipewire-debian.github.io    

2023/8/9

add one rotation axis

mmwave 的 python tool 有 azimuth, elevation tilt,缺一個 (不知道名子)。
所以加上去。

code:
elevAziRotMatrix = np.matrix([  [  math.cos(aziTilt),  math.cos(elevTilt)*math.sin(aziTilt), math.sin(elevTilt)*math.sin(aziTilt)],
                                [ -math.sin(aziTilt),  math.cos(elevTilt)*math.cos(aziTilt), math.sin(elevTilt)*math.cos(aziTilt)],
                                [                  0,                   -math.sin(elevTilt),                   math.cos(elevTilt)],
                             ])
對照 [https://en.wikipedia.org/wiki/Rotation_matrix wiki]:


用 0 度來猜對應關係..
所以看起來缺的應該是 β

另外 eleTilt, aziTilt 的方向和座標定義方向相反 α β γ
  • β : rotation
  • α : elevation
  • γ : azimuth
所以code :
 cos(γ),  cos(α)sin(γ), sin(α)sin(γ)
 sin(γ),  cos(α)cos(γ), sin(α)cos(γ)
      0, -sin(α)      , cos(α)
和 公式比較, α γ 的角度方向大概是相反,所以 sin 都要 * -1。

加入 rotTilt : β

原來 sin(0) = 0 的部份,要加回去 sin(β),也就是 sin(rotTilt)

2023/8/7

筆記:3D people counting, point cloud detection algorithm

根據 [https://dev.ti.com/tirex/explore/node?a=1AslXXD__1.10.00.13&node=A__AIQPG9x7K34A8l4ZELgznA__radar_toolbox__1AslXXD__1.10.00.13&r=1AslXXD__1.20.00.11 3D people tracking detection layer tuning guide],
這個3D point cloud 的 processing 的順序是:
  • 每個chirp 都做 range FFT
  • 同一TX反射的,每個水平ANT 的 range FFT,做 capon beamformer,找出方位角
  • 這樣就類似 R-V map 一樣,變成2D 的 heat map 圖,不過現在是 Range-Azimuth map,因為原來的 doppler FFT 改成 capon beamformer
  • 在 2D heat map (R-A map) 上,用 2D CFar (2-pass CFar)找出 signal point (detection point)
  • 找出 detection point (2D heat map 的 bin),再用垂直向的 ANT 做 capon beamformer 找出垂直角(elevation)
  • 針對detection point,同一 ant,連續chirp 的 bin,做 doppler FFT,算出速度

範例有兩種 ANT 排列,一個是安裝在天花板,一個安裝在牆上。
天花板的ANT 排列,X-Z 向是對稱的。
牆壁的ANT排例,X向的天線數量比較多。

所以兩個的 processing algorithm 不一樣。

牆壁的ANT,X 向比較多,所以先做 azimuth capon beamformer,再把結果的 bin 做 elevation beam former,
天花板 X-Z 向一樣多,所以都做....

2023/8/4

go-lang small http file server with upload function

ChatGPT 寫的,還沒看能不能動...
package main

import (
	"html/template"
	"io"
	"net/http"
	"os"
	"path/filepath"
)

func main() {
	http.HandleFunc("/", handleFileServer)
	http.HandleFunc("/upload", handleUpload)

	// Start the server on port 8080
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		panic(err)
	}
}

func handleFileServer(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	dirPath := "." // Current directory
	filePath := filepath.Join(dirPath, r.URL.Path)
	file, err := os.Open(filePath)
	if err != nil {
		http.Error(w, "File not found", http.StatusNotFound)
		return
	}
	defer file.Close()

	// Check if the path is a directory
	fileInfo, err := file.Stat()
	if err != nil {
		http.Error(w, "Failed to get file info", http.StatusInternalServerError)
		return
	}
	if fileInfo.IsDir() {
		renderDirectory(w, filePath)
		return
	}

	// Serve the file if it's not a directory
	http.ServeFile(w, r, filePath)
}

func renderDirectory(w http.ResponseWriter, dirPath string) {
	tmpl, err := template.New("directory").Parse(directoryTemplate)
	if err != nil {
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
		return
	}

	// Read the directory content
	dir, err := os.ReadDir(dirPath)
	if err != nil {
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
		return
	}

	data := struct {
		DirectoryName string
		Files         []string
	}{
		DirectoryName: dirPath,
		Files:         make([]string, 0),
	}

	for _, entry := range dir {
		data.Files = append(data.Files, entry.Name())
	}

	// Render the template
	err = tmpl.Execute(w, data)
	if err != nil {
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
	}
}

func handleUpload(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	// Limit the maximum file size to 10 MB
	r.ParseMultipartForm(10 << 20)

	// Get the uploaded file
	file, handler, err := r.FormFile("file")
	if err != nil {
		http.Error(w, "Failed to get file from request", http.StatusBadRequest)
		return
	}
	defer file.Close()

	// Create the file in the current directory
	f, err := os.Create(handler.Filename)
	if err != nil {
		http.Error(w, "Failed to create file on server", http.StatusInternalServerError)
		return
	}
	defer f.Close()

	// Copy the uploaded file to the server
	_, err = io.Copy(f, file)
	if err != nil {
		http.Error(w, "Failed to save file on server", http.StatusInternalServerError)
		return
	}

	// Redirect back to the directory listing
	http.Redirect(w, r, "/", http.StatusFound)
}

const directoryTemplate = `
<!DOCTYPE html>
<html>
<head>
	<title>Directory Listing: {{ .DirectoryName }}</title>
</head>
<body>
	<h1>Directory Listing: {{ .DirectoryName }}</h1>
	<ul>
		{{ range .Files }}
		<li><a href="{{ . }}">{{ . }}</a></li>
		{{ end }}
	</ul>
	<form action="/upload" method="post" enctype="multipart/form-data">
		<input type="file" name="file">
		<input type="submit" value="Upload">
	</form>
</body>
</html>
`