Improve visualisation system #12
@ -4,9 +4,7 @@ version = "0.1.0"
|
||||
description = "Use Wi-Fi Channel State Information to locate human presence"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
"flask",
|
||||
"numpy>=2.0.0",
|
||||
"flask-sock",
|
||||
"pytest",
|
||||
"matplotlib",
|
||||
"scipy",
|
||||
@ -15,9 +13,15 @@ dependencies = [
|
||||
"h5py>=3.12.1",
|
||||
"pyyaml>=6.0.2",
|
||||
"pydantic>=2.10.6",
|
||||
"torch"
|
||||
"torch",
|
||||
"grpcio>=1.70.0",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools", "wheel", "grpcio-tools"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
|
||||
[project.scripts]
|
||||
where-fi = "where_fi.cli:app"
|
||||
|
||||
@ -43,3 +47,9 @@ package = true
|
||||
|
||||
[tool.setuptools]
|
||||
packages = ["where_fi"]
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"grpcio-tools>=1.70.0",
|
||||
"protoletariat>=3.3.9",
|
||||
]
|
||||
|
||||
199
uv.lock
199
uv.lock
@ -1,4 +1,5 @@
|
||||
version = 1
|
||||
revision = 1
|
||||
requires-python = ">=3.11"
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.12'",
|
||||
@ -14,15 +15,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blinker"
|
||||
version = "1.9.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.1.8"
|
||||
@ -113,35 +105,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/89/ec/00d68c4ddfedfe64159999e5f8a98fb8442729a63e2077eb9dcd89623d27/filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338", size = 16164 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flask"
|
||||
version = "3.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "blinker" },
|
||||
{ name = "click" },
|
||||
{ name = "itsdangerous" },
|
||||
{ name = "jinja2" },
|
||||
{ name = "werkzeug" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/89/50/dff6380f1c7f84135484e176e0cac8690af72fa90e932ad2a0a60e28c69b/flask-3.1.0.tar.gz", hash = "sha256:5f873c5184c897c8d9d1b05df1e3d01b14910ce69607a117bd3277098a5836ac", size = 680824 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/af/47/93213ee66ef8fae3b93b3e29206f6b251e65c97bd91d8e1c5596ef15af0a/flask-3.1.0-py3-none-any.whl", hash = "sha256:d667207822eb83f1c4b50949b1623c8fc8d51f2341d65f72e1a1815397551136", size = 102979 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flask-sock"
|
||||
version = "0.7.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "flask" },
|
||||
{ name = "simple-websocket" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8d/8f/c6ab717dc90f4e46d1430335cd4ab13e3629410bb760c0ead6de476760fb/flask-sock-0.7.0.tar.gz", hash = "sha256:e023b578284195a443b8d8bdb4469e6a6acf694b89aeb51315b1a34fcf427b7d", size = 4334 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/98/107728ce3f430b5481eb426ccc5e1f7c8ab0bd01eaf231c62a8d528ff721/flask_sock-0.7.0-py3-none-any.whl", hash = "sha256:caac4d679392aaf010d02fabcf73d52019f5bdaf1c9c131ec5a428cb3491204a", size = 3982 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fonttools"
|
||||
version = "4.55.6"
|
||||
@ -185,12 +148,78 @@ wheels = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.14.0"
|
||||
name = "grpcio"
|
||||
version = "1.70.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/69/e1/4b21b5017c33f3600dcc32b802bb48fe44a4d36d6c066f52650c7c2690fa/grpcio-1.70.0.tar.gz", hash = "sha256:8d1584a68d5922330025881e63a6c1b54cc8117291d382e4fa69339b6d914c56", size = 12788932 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/c4/1f67d23d6bcadd2fd61fb460e5969c52b3390b4a4e254b5e04a6d1009e5e/grpcio-1.70.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:17325b0be0c068f35770f944124e8839ea3185d6d54862800fc28cc2ffad205a", size = 5229017 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/bd/cc36811c582d663a740fb45edf9f99ddbd99a10b6ba38267dc925e1e193a/grpcio-1.70.0-cp311-cp311-macosx_10_14_universal2.whl", hash = "sha256:dbe41ad140df911e796d4463168e33ef80a24f5d21ef4d1e310553fcd2c4a386", size = 11472027 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/32/8538bb2ace5cd72da7126d1c9804bf80b4fe3be70e53e2d55675c24961a8/grpcio-1.70.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:5ea67c72101d687d44d9c56068328da39c9ccba634cabb336075fae2eab0d04b", size = 5707785 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/5c/a45f85f2a0dfe4a6429dee98717e0e8bd7bd3f604315493c39d9679ca065/grpcio-1.70.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb5277db254ab7586769e490b7b22f4ddab3876c490da0a1a9d7c695ccf0bf77", size = 6331599 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/e5/5316b239380b8b2ad30373eb5bb25d9fd36c0375e94a98a0a60ea357d254/grpcio-1.70.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7831a0fc1beeeb7759f737f5acd9fdcda520e955049512d68fda03d91186eea", size = 5940834 },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/33/dbf035bc6d167068b4a9f2929dfe0b03fb763f0f861ecb3bb1709a14cb65/grpcio-1.70.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:27cc75e22c5dba1fbaf5a66c778e36ca9b8ce850bf58a9db887754593080d839", size = 6641191 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/c4/684d877517e5bfd6232d79107e5a1151b835e9f99051faef51fed3359ec4/grpcio-1.70.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d63764963412e22f0491d0d32833d71087288f4e24cbcddbae82476bfa1d81fd", size = 6198744 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/43/92fe5eeaf340650a7020cfb037402c7b9209e7a0f3011ea1626402219034/grpcio-1.70.0-cp311-cp311-win32.whl", hash = "sha256:bb491125103c800ec209d84c9b51f1c60ea456038e4734688004f377cfacc113", size = 3617111 },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/15/b6cf2c9515c028aff9da6984761a3ab484a472b0dc6435fcd07ced42127d/grpcio-1.70.0-cp311-cp311-win_amd64.whl", hash = "sha256:d24035d49e026353eb042bf7b058fb831db3e06d52bee75c5f2f3ab453e71aca", size = 4304604 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/a4/ddbda79dd176211b518f0f3795af78b38727a31ad32bc149d6a7b910a731/grpcio-1.70.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:ef4c14508299b1406c32bdbb9fb7b47612ab979b04cf2b27686ea31882387cff", size = 5198135 },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/5c/60eb8a063ea4cb8d7670af8fac3f2033230fc4b75f62669d67c66ac4e4b0/grpcio-1.70.0-cp312-cp312-macosx_10_14_universal2.whl", hash = "sha256:aa47688a65643afd8b166928a1da6247d3f46a2784d301e48ca1cc394d2ffb40", size = 11447529 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/b9/1bf8ab66729f13b44e8f42c9de56417d3ee6ab2929591cfee78dce749b57/grpcio-1.70.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:880bfb43b1bb8905701b926274eafce5c70a105bc6b99e25f62e98ad59cb278e", size = 5664484 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/06/2f377d6906289bee066d96e9bdb91e5e96d605d173df9bb9856095cccb57/grpcio-1.70.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e654c4b17d07eab259d392e12b149c3a134ec52b11ecdc6a515b39aceeec898", size = 6303739 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/50/64c94cfc4db8d9ed07da71427a936b5a2bd2b27c66269b42fbda82c7c7a4/grpcio-1.70.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2394e3381071045a706ee2eeb6e08962dd87e8999b90ac15c55f56fa5a8c9597", size = 5910417 },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/89/8795dfc3db4389c15554eb1765e14cba8b4c88cc80ff828d02f5572965af/grpcio-1.70.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:b3c76701428d2df01964bc6479422f20e62fcbc0a37d82ebd58050b86926ef8c", size = 6626797 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/b2/6a97ac91042a2c59d18244c479ee3894e7fb6f8c3a90619bb5a7757fa30c/grpcio-1.70.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ac073fe1c4cd856ebcf49e9ed6240f4f84d7a4e6ee95baa5d66ea05d3dd0df7f", size = 6190055 },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/2b/28db55c8c4d156053a8c6f4683e559cd0a6636f55a860f87afba1ac49a51/grpcio-1.70.0-cp312-cp312-win32.whl", hash = "sha256:cd24d2d9d380fbbee7a5ac86afe9787813f285e684b0271599f95a51bce33528", size = 3600214 },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/c3/a7a225645a965029ed432e5b5e9ed959a574e62100afab553eef58be0e37/grpcio-1.70.0-cp312-cp312-win_amd64.whl", hash = "sha256:0495c86a55a04a874c7627fd33e5beaee771917d92c0e6d9d797628ac40e7655", size = 4292538 },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/38/66d0f32f88feaf7d83f8559cd87d899c970f91b1b8a8819b58226de0a496/grpcio-1.70.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:aa573896aeb7d7ce10b1fa425ba263e8dddd83d71530d1322fd3a16f31257b4a", size = 5199218 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/96/947df763a0b18efb5cc6c2ae348e56d97ca520dc5300c01617b234410173/grpcio-1.70.0-cp313-cp313-macosx_10_14_universal2.whl", hash = "sha256:d405b005018fd516c9ac529f4b4122342f60ec1cee181788249372524e6db429", size = 11445983 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/5b/f3d4b063e51b2454bedb828e41f3485800889a3609c49e60f2296cc8b8e5/grpcio-1.70.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:f32090238b720eb585248654db8e3afc87b48d26ac423c8dde8334a232ff53c9", size = 5663954 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/0b/dab54365fcedf63e9f358c1431885478e77d6f190d65668936b12dd38057/grpcio-1.70.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfa089a734f24ee5f6880c83d043e4f46bf812fcea5181dcb3a572db1e79e01c", size = 6304323 },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/a8/8f965a7171ddd336ce32946e22954aa1bbc6f23f095e15dadaa70604ba20/grpcio-1.70.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f19375f0300b96c0117aca118d400e76fede6db6e91f3c34b7b035822e06c35f", size = 5910939 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/05/0bbf68be8b17d1ed6f178435a3c0c12e665a1e6054470a64ce3cb7896596/grpcio-1.70.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:7c73c42102e4a5ec76608d9b60227d917cea46dff4d11d372f64cbeb56d259d0", size = 6631405 },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/6a/5df64b6df405a1ed1482cb6c10044b06ec47fd28e87c2232dbcf435ecb33/grpcio-1.70.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:0a5c78d5198a1f0aa60006cd6eb1c912b4a1520b6a3968e677dbcba215fabb40", size = 6190982 },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/aa/aeaac87737e6d25d1048c53b8ec408c056d3ed0c922e7c5efad65384250c/grpcio-1.70.0-cp313-cp313-win32.whl", hash = "sha256:fe9dbd916df3b60e865258a8c72ac98f3ac9e2a9542dcb72b7a34d236242a5ce", size = 3598359 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/79/8edd2442d2de1431b4a3de84ef91c37002f12de0f9b577fb07b452989dbc/grpcio-1.70.0-cp313-cp313-win_amd64.whl", hash = "sha256:4119fed8abb7ff6c32e3d2255301e59c316c22d31ab812b3fbcbaf3d0d87cc68", size = 4293938 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "grpcio-tools"
|
||||
version = "1.70.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "grpcio" },
|
||||
{ name = "protobuf" },
|
||||
{ name = "setuptools" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c1/fe/3adf1035c1f9e9243516530beae67e197f2acc17562ec75f03a0ba77fc55/grpcio_tools-1.70.0.tar.gz", hash = "sha256:e578fee7c1c213c8e471750d92631d00f178a15479fb2cb3b939a07fc125ccd3", size = 5323149 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/2b/446a63000acab303bbc1b84fa7dbfa4857d96e95ab53e85083ba16c60d4a/grpcio_tools-1.70.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:5f5aba12d98d25c7ab2dd983939e2c21556a7d15f903b286f24d88d2c6e30c0a", size = 2380860 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/d2/48e82de83bf34f9a5207ea808a1c6e074bf657720664eb6c9f0bab38dbf2/grpcio_tools-1.70.0-cp311-cp311-macosx_10_14_universal2.whl", hash = "sha256:d47a6c6cfc526b290b7b53a37dd7e6932983f7a168b56aab760b4b597c47f30f", size = 5957716 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/f7/a735faa8fc96778aa54e321ac6820bab03ee4eea305cc1209b095dfdffee/grpcio_tools-1.70.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:b5a9beadd1e24772ffa2c70f07d72f73330d356b78b246e424f4f2ed6c6713f3", size = 2336501 },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/ed/4bed599c061b65149b32569347a857098819d75c2419c4202f9de1e06250/grpcio_tools-1.70.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb8135eef160a62505f074bf7a3d62f3b13911c3c14037c5392bf877114213b5", size = 2729638 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/43/d8850889a2041cf94e882712df0e323cd6bbf24f8f4c50e2f0d80c68da7d/grpcio_tools-1.70.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7ac9b3e13ace8467a586c53580ee22f9732c355583f3c344ef8c6c0666219cc", size = 2463251 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/2e/2407641c70ca0afe03a04c3c29f0b51e1582759e3d5c995217b4ed0ce2bd/grpcio_tools-1.70.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:63f367363a4a1489a0046b19f9d561216ea0d206c40a6f1bf07a58ccfb7be480", size = 3340968 },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/bb/591799e6b0445028d74552964e47d7b0b23ff5ce9c377688b318de331f12/grpcio_tools-1.70.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:54ceffef59a059d2c7304554a8bbb20eedb05a3f937159ab1c332c1b28e12c9f", size = 2944466 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/90/b73293fff616574cbdf70437efb3b2ee6af3705c6b2cc19dd02dfb01708f/grpcio_tools-1.70.0-cp311-cp311-win32.whl", hash = "sha256:7a90a66a46821140a2a2b0be787dfabe42e22e9a5ba9cc70726b3e5c71a3b785", size = 947335 },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/cc/12ad066dc722285ee3f7d398d4272dc43857de6b7e6fa509a385ca4a857f/grpcio_tools-1.70.0-cp311-cp311-win_amd64.whl", hash = "sha256:4ebf09733545a69c166b02caa14c34451e38855544820dab7fdde5c28e2dbffe", size = 1119053 },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/8d/21f3f0c6e8ddc7ffd82873a6ff767a568a3384043adc034c49fd72020884/grpcio_tools-1.70.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:ec5d6932c3173d7618267b3b3fd77b9243949c5ec04302b7338386d4f8544e0b", size = 2380552 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/10/def56ecb8e139a96aae9d408d891f32f24a066c57179ce5f78e7edf70a35/grpcio_tools-1.70.0-cp312-cp312-macosx_10_14_universal2.whl", hash = "sha256:f22852da12f53b02a3bdb29d0c32fcabab9c7c8f901389acffec8461083f110d", size = 5956826 },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/5e/f10375b90b7dc14d1b5095797d4f79b34e584fbc9bda06e093ad316a96dd/grpcio_tools-1.70.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:7d45067e6efd20881e98a0e1d7edd7f207b1625ad7113321becbfe0a6ebee46c", size = 2335835 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/33/d770fbdf824edfc0f9297be046d4d48fbc81b2dbf802827ade65110f0a47/grpcio_tools-1.70.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3020c97f03b30eee3c26aa2a55fbe003f1729c6f879a378507c2c78524db7c12", size = 2729501 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/fb/8442f386fa71056abe7ebbc153eaac8cbe32875ed659a641ca526ab9f341/grpcio_tools-1.70.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7fd472fce3b33bdf7fbc24d40da7ab10d7a088bcaf59c37433c2c57330fbcb6", size = 2462824 },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/4e/1703d2586663078613baed553de052e029b3d7fe311e90d3f023c85e612a/grpcio_tools-1.70.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3875543d74ce1a698a11f498f83795216ce929cb29afa5fac15672c7ba1d6dd2", size = 3340759 },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/d9/f61e427b0e1d7305396dacea65d1e0612eb2bc66b02328ef6bde117624fb/grpcio_tools-1.70.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a130c24d617a3a57369da784080dfa8848444d41b7ae1250abc06e72e706a8d9", size = 2944463 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/8f/8f6f511ad90e12d7c2f396ad9efe46019c0a77a5f5f69e46998c834405e4/grpcio_tools-1.70.0-cp312-cp312-win32.whl", hash = "sha256:8eae17c920d14e2e451dbb18f5d8148f884e10228061941b33faa8fceee86e73", size = 946776 },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/0f/aff5d01ce9ae94ed02b79e033b0c469e560221340c09120270109de4986a/grpcio_tools-1.70.0-cp312-cp312-win_amd64.whl", hash = "sha256:99caa530242a0a832d8b6a6ab94b190c9b449d3e237f953911b4d56207569436", size = 1118594 },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/2a/bf442acb748b2a53281e5e7cc3fa36c25ae99436cd2f2cfe684096d4c39f/grpcio_tools-1.70.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:f024688d04e7a9429489ed695b85628075c3c6d655198ba3c6ccbd1d8b7c333b", size = 2380142 },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/a2/984dabaf1cdc41e267acdd37232026ede28f55bc6f9e932907bcbbb46773/grpcio_tools-1.70.0-cp313-cp313-macosx_10_14_universal2.whl", hash = "sha256:1fa9a81621d7178498dedcf94eb8f276a7594327faf3dd5fd1935ce2819a2bdb", size = 5955907 },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/78/ebefc32418be93828b46eca5952ef1cb0400b33883bc20c22b1fc2a51f61/grpcio_tools-1.70.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:c6da2585c0950cdb650df1ff6d85b3fe31e22f8370b9ee11f8fe641d5b4bf096", size = 2335428 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/f8/5d4b58dc846bf28b8b9abf07f5d091eb078fc4f01184adb3b374cf5119a4/grpcio_tools-1.70.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70234b592af17050ec30cf35894790cef52aeae87639efe6db854a7fa783cc8c", size = 2728481 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/28/46833d415b2c2e3e0f36763c528da48785c94580240684e56410abd08aa0/grpcio_tools-1.70.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c021b040d0a9f5bb96a725c4d2b95008aad127d6bed124a7bbe854973014f5b", size = 2462401 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/8a/c771a09aea58275106e08e7dd37470c6e8555dfcea9a7b44d1c5adc80370/grpcio_tools-1.70.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:114a42e566e5b16a47e98f7910a6c0074b37e2d1faacaae13222e463d0d0d43c", size = 3340068 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/be/e3dfa73435c633859c4a045c299105e99a6c6a41cda524148bf9c8d4dc99/grpcio_tools-1.70.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:4cae365d7e3ba297256216a9a256458b286f75c64603f017972b3ad1ee374437", size = 2944317 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/bd/e30fb2b0ce2c0c48caf994b1ebedb56fc7103e26062dd31a41ad1e528eb7/grpcio_tools-1.70.0-cp313-cp313-win32.whl", hash = "sha256:ae139a8d3ddd8353f62af3af018e99ebcd2f4a237bd319cb4b6f58dd608aaa54", size = 946136 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/8a/92aba852bbe2ddf3e44c354b4162b3cf350b810523ffb2d0e5937bd3f249/grpcio_tools-1.70.0-cp313-cp313-win_amd64.whl", hash = "sha256:04bf30c0eb2741defe3ab6e0a6102b022d69cfd39d68fab9b954993ceca8d346", size = 1118147 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -228,15 +257,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itsdangerous"
|
||||
version = "2.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.1.5"
|
||||
@ -682,6 +702,33 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "protobuf"
|
||||
version = "5.29.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f7/d1/e0a911544ca9993e0f17ce6d3cc0932752356c1b0a834397f28e63479344/protobuf-5.29.3.tar.gz", hash = "sha256:5da0f41edaf117bde316404bad1a486cb4ededf8e4a54891296f648e8e076620", size = 424945 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/7a/1e38f3cafa022f477ca0f57a1f49962f21ad25850c3ca0acd3b9d0091518/protobuf-5.29.3-cp310-abi3-win32.whl", hash = "sha256:3ea51771449e1035f26069c4c7fd51fba990d07bc55ba80701c78f886bf9c888", size = 422708 },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/fa/aae8e10512b83de633f2646506a6d835b151edf4b30d18d73afd01447253/protobuf-5.29.3-cp310-abi3-win_amd64.whl", hash = "sha256:a4fa6f80816a9a0678429e84973f2f98cbc218cca434abe8db2ad0bffc98503a", size = 434508 },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/04/3eaedc2ba17a088961d0e3bd396eac764450f431621b58a04ce898acd126/protobuf-5.29.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a8434404bbf139aa9e1300dbf989667a83d42ddda9153d8ab76e0d5dcaca484e", size = 417825 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/06/7c467744d23c3979ce250397e26d8ad8eeb2bea7b18ca12ad58313c1b8d5/protobuf-5.29.3-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:daaf63f70f25e8689c072cfad4334ca0ac1d1e05a92fc15c54eb9cf23c3efd84", size = 319573 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/45/2ebbde52ad2be18d3675b6bee50e68cd73c9e0654de77d595540b5129df8/protobuf-5.29.3-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:c027e08a08be10b67c06bf2370b99c811c466398c357e615ca88c91c07f0910f", size = 319672 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/b2/ab07b09e0f6d143dfb839693aa05765257bceaa13d03bf1a696b78323e7a/protobuf-5.29.3-py3-none-any.whl", hash = "sha256:0a18ed4a24198528f2333802eb075e59dea9d679ab7a6c5efb017a59004d849f", size = 172550 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "protoletariat"
|
||||
version = "3.3.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
{ name = "protobuf" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/09/cd/f8c0b9d113a1d551b7a014339c4da85c71c2ba9ac889855eb45744d89459/protoletariat-3.3.9.tar.gz", hash = "sha256:db209b7058a0b700c5855b79c4151216205a93298b052c227774cba59f213831", size = 20678 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/59/bf79f403c358d1dd4a9cfa2bbfea8734bfa1a1181aecf3a8945ab6f6e7fc/protoletariat-3.3.9-py3-none-any.whl", hash = "sha256:a876a600691005c7d229a149f9639c994a087c4a66bc054717f243f0f353ae66", size = 20718 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.10.6"
|
||||
@ -914,18 +961,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simple-websocket"
|
||||
version = "1.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "wsproto" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b0/d4/bfa032f961103eba93de583b161f0e6a5b63cebb8f2c7d0c6e6efe1e3d2e/simple_websocket-1.1.0.tar.gz", hash = "sha256:7939234e7aa067c534abdab3a9ed933ec9ce4691b0713c78acb195560aa52ae4", size = 17300 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl", hash = "sha256:4af6069630a38ed6c561010f0e11a5bc0d4ca569b36306eb257cd9a192497c8c", size = 13842 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.17.0"
|
||||
@ -1021,25 +1056,12 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "werkzeug"
|
||||
version = "3.1.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "markupsafe" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9f/69/83029f1f6300c5fb2471d621ab06f6ec6b3324685a2ce0f9777fd4a8b71e/werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746", size = 806925 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "where-fi"
|
||||
version = "0.1.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "flask" },
|
||||
{ name = "flask-sock" },
|
||||
{ name = "grpcio" },
|
||||
{ name = "h5py" },
|
||||
{ name = "matplotlib" },
|
||||
{ name = "numpy" },
|
||||
@ -1052,10 +1074,15 @@ dependencies = [
|
||||
{ name = "typer" },
|
||||
]
|
||||
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "grpcio-tools" },
|
||||
{ name = "protoletariat" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "flask" },
|
||||
{ name = "flask-sock" },
|
||||
{ name = "grpcio", specifier = ">=1.70.0" },
|
||||
{ name = "h5py", specifier = ">=3.12.1" },
|
||||
{ name = "matplotlib" },
|
||||
{ name = "numpy", specifier = ">=2.0.0" },
|
||||
@ -1068,14 +1095,8 @@ requires-dist = [
|
||||
{ name = "typer" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wsproto"
|
||||
version = "1.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "h11" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c9/4a/44d3c295350d776427904d73c189e10aeae66d7f555bb2feee16d1e4ba5a/wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065", size = 53425 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736", size = 24226 },
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
{ name = "grpcio-tools", specifier = ">=1.70.0" },
|
||||
{ name = "protoletariat", specifier = ">=3.3.9" },
|
||||
]
|
||||
|
||||
@ -1,15 +1,16 @@
|
||||
import logging
|
||||
import multiprocessing as mp
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
import torch
|
||||
import typer
|
||||
|
||||
from .. import visualise
|
||||
from ..config import config
|
||||
from ..processing.aoa import AoA
|
||||
from ..processing.preprocess import Preprocessor
|
||||
from ..visualise import server as visualise
|
||||
from . import file, globals
|
||||
|
||||
app = typer.Typer(callback=globals.main)
|
||||
@ -40,23 +41,24 @@ def heatmap() -> None:
|
||||
aoa = AoA()
|
||||
|
||||
# Start webapp in background process
|
||||
manager = mp.Manager()
|
||||
webapp_queue: "mp.Queue[npt.NDArray[np.float32]]" = manager.Queue(
|
||||
config.sample_rate
|
||||
)
|
||||
webapp_queue: "mp.Queue[visualise.VisualiserData]" = mp.Queue(config.sample_rate)
|
||||
webapp = mp.Process(target=visualise.start, args=(webapp_queue,))
|
||||
webapp.start()
|
||||
|
||||
def visualise_data(data: npt.NDArray[Any], dtype: visualise.DataType) -> None:
|
||||
if not webapp_queue.full():
|
||||
webapp_queue.put(visualise.VisualiserData(data, dtype))
|
||||
|
||||
def callback(antenna_data: npt.NDArray[np.complex64]) -> None:
|
||||
logger.info(f"Got final CSI data with shape {antenna_data.shape}")
|
||||
visualise_data(antenna_data, visualise.DataType.RAW_CSI)
|
||||
processed = preprocessor.preprocess(antenna_data)
|
||||
visualise_data(processed, visualise.DataType.PROCESSED_CSI)
|
||||
logger.info(f"Processed CSI data with shape {processed.shape}")
|
||||
processed_tensor = torch.tensor(processed, device=device)
|
||||
aoa.update(processed_tensor)
|
||||
|
||||
if not webapp_queue.full():
|
||||
heatmap = aoa.heatmap()
|
||||
webapp_queue.put(heatmap)
|
||||
visualise_data(heatmap, visualise.DataType.HEATMAP)
|
||||
|
||||
globals.csi_producer(csi_callback=callback)
|
||||
logger.info("Finished processing CSI data")
|
||||
|
||||
2
where_fi/visualise/.gitignore
vendored
Normal file
2
where_fi/visualise/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
server/generated
|
||||
frontend/src/grpc
|
||||
9
where_fi/visualise/Makefile
Normal file
9
where_fi/visualise/Makefile
Normal file
@ -0,0 +1,9 @@
|
||||
all: server/generated frontend/src/grpc
|
||||
|
||||
server/generated: protos/*.proto
|
||||
mkdir -p server/generated
|
||||
find protos/ -type f -name "*.proto" | xargs uv run python -m grpc_tools.protoc -Iprotos --python_out=server/generated --pyi_out=server/generated --grpc_python_out=server/generated
|
||||
find protos/ -type f -name "*.proto" | xargs uv run protol --create-package --in-place --python-out server/generated/ protoc --proto-path=protos/
|
||||
|
||||
frontend/src/grpc: protos/*.proto
|
||||
find protos/ -name "*.proto" | xargs npm exec --prefix frontend/ protoc --ts_out frontend/src/grpc -I protos/
|
||||
@ -1,136 +0,0 @@
|
||||
import io
|
||||
import logging
|
||||
import multiprocessing as mp
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import Generator
|
||||
|
||||
import matplotlib
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
from flask import Flask, Response, render_template, request
|
||||
from flask_sock import Sock
|
||||
from simple_websocket import Server
|
||||
|
||||
from ..config import config
|
||||
from ..processing.aoa import AoA
|
||||
|
||||
matplotlib.use("agg")
|
||||
|
||||
app = Flask(__name__)
|
||||
sock = Sock(app)
|
||||
aoa_queue = None
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
data: npt.NDArray[np.complex128] = np.array([], dtype=complex)
|
||||
aoa: AoA = AoA()
|
||||
|
||||
|
||||
@app.route("/preprocessed")
|
||||
def preprocessed() -> str:
|
||||
return render_template("preprocessed.html")
|
||||
|
||||
|
||||
Subscriber = Server
|
||||
subscriber_settings: dict[Subscriber, tuple[int, int, int]] = {}
|
||||
|
||||
|
||||
@sock.route("/data")
|
||||
def get_data(sock: Subscriber) -> None:
|
||||
while True:
|
||||
msg = sock.receive()
|
||||
if len(msg.split()) != 3:
|
||||
break
|
||||
subcarrier, rx, tx = map(int, msg.split())
|
||||
subscriber_settings[sock] = (subcarrier, rx, tx)
|
||||
|
||||
|
||||
def add_data(
|
||||
raw_data: npt.NDArray[np.complex128], new_data: npt.NDArray[np.complex128]
|
||||
) -> None:
|
||||
if config.VISUALISE_RAW:
|
||||
magn = np.abs(raw_data)
|
||||
phase = np.angle(raw_data)
|
||||
|
||||
new_mag = np.abs(new_data)
|
||||
new_phase = np.angle(new_data)
|
||||
|
||||
fig, axs = plt.subplots(2, 2)
|
||||
axs[0, 0].plot(magn[:, 0, 0], c="b")
|
||||
axs[0, 0].plot(magn[:, 1, 0], c="orange")
|
||||
axs[1, 0].plot(new_mag[:, 0, 0], c="b")
|
||||
axs[1, 0].plot(new_mag[:, 1, 0], c="orange")
|
||||
axs[0, 1].plot(phase[:, 0, 0], c="b")
|
||||
axs[0, 1].plot(phase[:, 1, 0], c="orange")
|
||||
axs[1, 1].plot(new_phase[:, 0, 0], c="b")
|
||||
axs[1, 1].plot(new_phase[:, 1, 0], c="orange")
|
||||
fig.savefig("/tmp/plot.png")
|
||||
plt.close(fig)
|
||||
|
||||
global data
|
||||
if data.size == 0:
|
||||
data = np.expand_dims(new_data, axis=0)
|
||||
else:
|
||||
data = np.concat([data, np.expand_dims(new_data, axis=0)], axis=0)
|
||||
|
||||
# Only keep latest 100 entries
|
||||
if data.shape[0] > 100:
|
||||
data = data[-100:]
|
||||
to_remove: list[Subscriber] = []
|
||||
for subscriber in subscriber_settings:
|
||||
try:
|
||||
subcarrier, rx, tx = subscriber_settings[subscriber]
|
||||
subscriber.send(new_data.real[subcarrier, rx, tx])
|
||||
except Exception as e:
|
||||
to_remove.append(subscriber)
|
||||
print(e)
|
||||
|
||||
for subscriber in to_remove:
|
||||
del subscriber_settings[subscriber]
|
||||
|
||||
|
||||
def plot_heatmap(heatmap: npt.NDArray[np.float32]) -> io.BytesIO:
|
||||
logger.info(f"Making heatmap with aoa of {aoa.timestamp}")
|
||||
fig = plt.figure()
|
||||
ax = fig.add_axes([0.1, 0.1, 0.9, 0.9]) # , polar=True)
|
||||
r = np.linspace(
|
||||
0, config.music.heatmap.tof_max, config.music.heatmap.tof_resolution
|
||||
)
|
||||
theta = np.linspace(0, np.pi, config.music.heatmap.theta_resolution) # Angle values
|
||||
|
||||
mesh = ax.pcolormesh(theta, r, heatmap, edgecolors="face", vmin=0, vmax=50)
|
||||
|
||||
fig.colorbar(mesh, ax=ax)
|
||||
buf = io.BytesIO()
|
||||
fig.savefig(buf, format="jpeg")
|
||||
plt.close(fig)
|
||||
|
||||
buf.seek(0)
|
||||
return buf
|
||||
|
||||
|
||||
def gather_aoa() -> Generator[bytes, None, None]:
|
||||
assert aoa_queue is not None
|
||||
|
||||
while True:
|
||||
logger.debug("Receiving from aoa pipe")
|
||||
heatmap = aoa_queue.get()
|
||||
buf = plot_heatmap(heatmap)
|
||||
yield (b"--frame\r\nContent-Type: image/jpeg\r\n\r\n" + buf.read() + b"\r\n")
|
||||
buf.close()
|
||||
|
||||
|
||||
@app.route("/aoa_tof")
|
||||
def aoa_tof() -> Response:
|
||||
return Response(gather_aoa(), mimetype="multipart/x-mixed-replace; boundary=frame")
|
||||
|
||||
|
||||
def start(conn: "mp.Queue[npt.NDArray[np.float32]]") -> None:
|
||||
global app, aoa_queue
|
||||
|
||||
aoa_queue = conn
|
||||
app.run(debug=True, use_reloader=False, host="0.0.0.0")
|
||||
logger.info("Visualisation server shut down")
|
||||
66
where_fi/visualise/envoy.yaml
Normal file
66
where_fi/visualise/envoy.yaml
Normal file
@ -0,0 +1,66 @@
|
||||
admin:
|
||||
access_log_path: /tmp/admin_access.log
|
||||
address:
|
||||
socket_address: { address: 0.0.0.0, port_value: 9901 }
|
||||
|
||||
static_resources:
|
||||
listeners:
|
||||
- name: listener_0
|
||||
address:
|
||||
socket_address: { address: 0.0.0.0, port_value: 8080 }
|
||||
filter_chains:
|
||||
- filters:
|
||||
- name: envoy.filters.network.http_connection_manager
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
|
||||
codec_type: auto
|
||||
stat_prefix: ingress_http
|
||||
route_config:
|
||||
name: local_route
|
||||
virtual_hosts:
|
||||
- name: local_service
|
||||
domains: ["*"]
|
||||
routes:
|
||||
- match: { prefix: "/" }
|
||||
route:
|
||||
cluster: echo_service
|
||||
timeout: 0s
|
||||
max_stream_duration:
|
||||
grpc_timeout_header_max: 0s
|
||||
cors:
|
||||
allow_origin_string_match:
|
||||
- prefix: "*"
|
||||
allow_methods: GET, PUT, DELETE, POST, OPTIONS
|
||||
allow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout
|
||||
max_age: "1728000"
|
||||
expose_headers: custom-header-1,grpc-status,grpc-message
|
||||
http_filters:
|
||||
- name: envoy.filters.http.grpc_web
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/envoy.extensions.filters.http.grpc_web.v3.GrpcWeb
|
||||
- name: envoy.filters.http.cors
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/envoy.extensions.filters.http.cors.v3.Cors
|
||||
- name: envoy.filters.http.router
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
|
||||
clusters:
|
||||
- name: echo_service
|
||||
connect_timeout: 0.25s
|
||||
type: logical_dns
|
||||
# HTTP/2 support
|
||||
typed_extension_protocol_options:
|
||||
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
|
||||
"@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
|
||||
explicit_http_config:
|
||||
http2_protocol_options: {}
|
||||
lb_policy: round_robin
|
||||
load_assignment:
|
||||
cluster_name: cluster_0
|
||||
endpoints:
|
||||
- lb_endpoints:
|
||||
- endpoint:
|
||||
address:
|
||||
socket_address:
|
||||
address: 127.0.0.1
|
||||
port_value: 50051
|
||||
9
where_fi/visualise/frontend/.editorconfig
Normal file
9
where_fi/visualise/frontend/.editorconfig
Normal file
@ -0,0 +1,9 @@
|
||||
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue}]
|
||||
charset = utf-8
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
end_of_line = lf
|
||||
max_line_length = 100
|
||||
1
where_fi/visualise/frontend/.gitattributes
vendored
Normal file
1
where_fi/visualise/frontend/.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
||||
* text=auto eol=lf
|
||||
30
where_fi/visualise/frontend/.gitignore
vendored
Normal file
30
where_fi/visualise/frontend/.gitignore
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
*.tsbuildinfo
|
||||
7
where_fi/visualise/frontend/.prettierrc.json
Normal file
7
where_fi/visualise/frontend/.prettierrc.json
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/prettierrc",
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"printWidth": 100
|
||||
}
|
||||
1
where_fi/visualise/frontend/env.d.ts
vendored
Normal file
1
where_fi/visualise/frontend/env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
24
where_fi/visualise/frontend/eslint.config.ts
Normal file
24
where_fi/visualise/frontend/eslint.config.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import pluginVue from 'eslint-plugin-vue'
|
||||
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'
|
||||
import skipFormatting from '@vue/eslint-config-prettier/skip-formatting'
|
||||
|
||||
// To allow more languages other than `ts` in `.vue` files, uncomment the following lines:
|
||||
// import { configureVueProject } from '@vue/eslint-config-typescript'
|
||||
// configureVueProject({ scriptLangs: ['ts', 'tsx'] })
|
||||
// More info at https://github.com/vuejs/eslint-config-typescript/#advanced-setup
|
||||
|
||||
export default defineConfigWithVueTs(
|
||||
{
|
||||
name: 'app/files-to-lint',
|
||||
files: ['**/*.{ts,mts,tsx,vue}'],
|
||||
},
|
||||
|
||||
{
|
||||
name: 'app/files-to-ignore',
|
||||
ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**'],
|
||||
},
|
||||
|
||||
pluginVue.configs['flat/essential'],
|
||||
vueTsConfigs.recommended,
|
||||
skipFormatting,
|
||||
)
|
||||
12
where_fi/visualise/frontend/index.html
Normal file
12
where_fi/visualise/frontend/index.html
Normal file
@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Where-Fi Visualizer</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
5326
where_fi/visualise/frontend/package-lock.json
generated
Normal file
5326
where_fi/visualise/frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
46
where_fi/visualise/frontend/package.json
Normal file
46
where_fi/visualise/frontend/package.json
Normal file
@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "visualise",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "run-p type-check \"build-only {@}\" --",
|
||||
"preview": "vite preview",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --build",
|
||||
"lint": "eslint . --fix",
|
||||
"format": "prettier --write src/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mdi/font": "^7.4.47",
|
||||
"@protobuf-ts/grpcweb-transport": "^2.9.4",
|
||||
"buffer": "^6.0.3",
|
||||
"google-protobuf": "^3.21.4",
|
||||
"grpc-web": "^1.5.0",
|
||||
"plotly.js-dist": "^3.0.1",
|
||||
"plotly.js-dist-min": "^3.0.1",
|
||||
"vue": "^3.5.13",
|
||||
"vuetify": "^3.7.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@protobuf-ts/plugin": "^2.9.4",
|
||||
"@tsconfig/node22": "^22.0.0",
|
||||
"@types/node": "^22.13.1",
|
||||
"@types/plotly.js": "^2.35.2",
|
||||
"@types/plotly.js-dist-min": "^2.3.4",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"@vue/eslint-config-prettier": "^10.1.0",
|
||||
"@vue/eslint-config-typescript": "^14.3.0",
|
||||
"@vue/tsconfig": "^0.7.0",
|
||||
"eslint": "^9.18.0",
|
||||
"eslint-plugin-vue": "^9.32.0",
|
||||
"jiti": "^2.4.2",
|
||||
"npm-run-all2": "^7.0.2",
|
||||
"prettier": "^3.4.2",
|
||||
"typescript": "~5.7.3",
|
||||
"vite": "^6.0.11",
|
||||
"vite-plugin-vue-devtools": "^7.7.1",
|
||||
"vue-tsc": "^2.2.0"
|
||||
}
|
||||
}
|
||||
42
where_fi/visualise/frontend/src/App.vue
Normal file
42
where_fi/visualise/frontend/src/App.vue
Normal file
@ -0,0 +1,42 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import PlotList from './components/PlotList.vue'
|
||||
import Figure from './components/Figure.vue'
|
||||
import { Figure as FigureType } from './grpc/figure'
|
||||
|
||||
var plots = ref<FigureType[]>([])
|
||||
var paused = ref(false)
|
||||
|
||||
function addPlot(fig: FigureType) {
|
||||
console.log('Plot added', fig)
|
||||
plots.value.push(fig)
|
||||
console.log(plots)
|
||||
}
|
||||
function removePlot(fig: FigureType) {
|
||||
console.log('Plot removed', fig)
|
||||
plots.value = plots.value.filter((x) => x.uuid !== fig.uuid)
|
||||
console.log(plots)
|
||||
}
|
||||
declare module '@vue/runtime-core' {
|
||||
interface ComponentCustomProperties {
|
||||
$props: {
|
||||
onClick?: (e: MouseEvent) => void
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-app>
|
||||
<PlotList @figure-selected="addPlot" @figure-deselected="removePlot" />
|
||||
|
||||
<v-main>
|
||||
<v-container fluid>
|
||||
<Figure v-for="plot in plots" :key="plot.uuid" :figure="plot" :paused="paused" />
|
||||
</v-container>
|
||||
<v-fab location="right bottom" color="secondary" @click="paused = !paused" icon app>
|
||||
<v-icon :icon="paused ? 'mdi-play' : 'mdi-pause'"></v-icon>
|
||||
</v-fab>
|
||||
</v-main>
|
||||
</v-app>
|
||||
</template>
|
||||
86
where_fi/visualise/frontend/src/assets/main.css
Normal file
86
where_fi/visualise/frontend/src/assets/main.css
Normal file
@ -0,0 +1,86 @@
|
||||
/* color palette from <https://github.com/vuejs/theme> */
|
||||
:root {
|
||||
--vt-c-white: #ffffff;
|
||||
--vt-c-white-soft: #f8f8f8;
|
||||
--vt-c-white-mute: #f2f2f2;
|
||||
|
||||
--vt-c-black: #181818;
|
||||
--vt-c-black-soft: #222222;
|
||||
--vt-c-black-mute: #282828;
|
||||
|
||||
--vt-c-indigo: #2c3e50;
|
||||
|
||||
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
|
||||
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
|
||||
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
|
||||
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
|
||||
|
||||
--vt-c-text-light-1: var(--vt-c-indigo);
|
||||
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
|
||||
--vt-c-text-dark-1: var(--vt-c-white);
|
||||
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
|
||||
}
|
||||
|
||||
/* semantic color variables for this project */
|
||||
:root {
|
||||
--color-background: var(--vt-c-white);
|
||||
--color-background-soft: var(--vt-c-white-soft);
|
||||
--color-background-mute: var(--vt-c-white-mute);
|
||||
|
||||
--color-border: var(--vt-c-divider-light-2);
|
||||
--color-border-hover: var(--vt-c-divider-light-1);
|
||||
|
||||
--color-heading: var(--vt-c-text-light-1);
|
||||
--color-text: var(--vt-c-text-light-1);
|
||||
|
||||
--section-gap: 160px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-background: var(--vt-c-black);
|
||||
--color-background-soft: var(--vt-c-black-soft);
|
||||
--color-background-mute: var(--vt-c-black-mute);
|
||||
|
||||
--color-border: var(--vt-c-divider-dark-2);
|
||||
--color-border-hover: var(--vt-c-divider-dark-1);
|
||||
|
||||
--color-heading: var(--vt-c-text-dark-1);
|
||||
--color-text: var(--vt-c-text-dark-2);
|
||||
}
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
color: var(--color-text);
|
||||
background: var(--color-background);
|
||||
transition:
|
||||
color 0.5s,
|
||||
background-color 0.5s;
|
||||
line-height: 1.6;
|
||||
font-family:
|
||||
Inter,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
Roboto,
|
||||
Oxygen,
|
||||
Ubuntu,
|
||||
Cantarell,
|
||||
'Fira Sans',
|
||||
'Droid Sans',
|
||||
'Helvetica Neue',
|
||||
sans-serif;
|
||||
font-size: 15px;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
92
where_fi/visualise/frontend/src/components/Figure.vue
Normal file
92
where_fi/visualise/frontend/src/components/Figure.vue
Normal file
@ -0,0 +1,92 @@
|
||||
<script setup lang="ts">
|
||||
import * as Plotly from 'plotly.js-dist-min'
|
||||
import { Figure, FigureData } from '../grpc/figure'
|
||||
import { FigureServiceClient } from '../grpc/figure.client'
|
||||
import { GrpcWebFetchTransport } from '@protobuf-ts/grpcweb-transport'
|
||||
import { host } from '../connect'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { onBeforeUnmount } from 'vue'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const { figure, paused = false } = defineProps<{ figure: Figure; paused?: boolean }>()
|
||||
const cancel = ref<boolean>(false)
|
||||
|
||||
const data = ref<FigureData | null>(null)
|
||||
const plotData = computed(() => {
|
||||
if (data.value) {
|
||||
switch (data.value.figure.oneofKind) {
|
||||
case 'line':
|
||||
return data.value.figure.line.lines.map((line) => ({
|
||||
x: line.x.length == 0 ? undefined : line.x,
|
||||
y: line.y,
|
||||
type: 'scatter' as const,
|
||||
name: line.label,
|
||||
color: line.color ? line.color : undefined,
|
||||
}))
|
||||
case 'heatmap':
|
||||
return []
|
||||
/* data.value.figure.heatmap.lines.map((line) => {
|
||||
return {
|
||||
x: line.x,
|
||||
y: line.y,
|
||||
type: 'scatter',
|
||||
name: line.label,
|
||||
color: line.color,
|
||||
}
|
||||
})*/
|
||||
}
|
||||
}
|
||||
return []
|
||||
})
|
||||
|
||||
const layout = {
|
||||
title: { text: figure.title },
|
||||
xaxis: { title: { text: figure.xLabel } },
|
||||
yaxis: { title: { text: figure.yLabel } },
|
||||
}
|
||||
|
||||
function updateGraph() {
|
||||
console.log('Updating graph with', plotData.value)
|
||||
Plotly.newPlot(figure.uuid, plotData.value, layout, { responsive: true })
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
console.log('Mounted')
|
||||
|
||||
updateGraph()
|
||||
|
||||
const transport = new GrpcWebFetchTransport({ baseUrl: host })
|
||||
const figureService = new FigureServiceClient(transport)
|
||||
const stream = figureService.getFigureUpdate({ uuid: figure.uuid })
|
||||
for await (const response of stream.responses) {
|
||||
if (cancel.value) {
|
||||
console.log('Stopping stream...')
|
||||
break
|
||||
}
|
||||
console.log(`Got new value for ${figure.uuid}:`, response)
|
||||
|
||||
if (!paused) {
|
||||
data.value = response
|
||||
updateGraph()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
console.log('Component is about to unmount, stopping stream...')
|
||||
cancel.value = true
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-row class="w-100">
|
||||
<v-col cols="12">
|
||||
<v-card class="w-100">
|
||||
<v-card-title>{{ figure.title }}</v-card-title>
|
||||
<v-card-text>
|
||||
<div :id="figure.uuid" class="w-100" />
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
83
where_fi/visualise/frontend/src/components/PlotList.vue
Normal file
83
where_fi/visualise/frontend/src/components/PlotList.vue
Normal file
@ -0,0 +1,83 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue'
|
||||
import { Figure } from '../grpc/figure'
|
||||
import { FigureServiceClient } from '../grpc/figure.client'
|
||||
import { GrpcWebFetchTransport } from '@protobuf-ts/grpcweb-transport'
|
||||
import { host } from '../connect'
|
||||
|
||||
const plots = ref<Figure[]>([])
|
||||
const loading = ref(true)
|
||||
const error = ref<string | null>(null)
|
||||
var emit = (_: any, ...args: any[]) => {
|
||||
console.log('No emit function set', args)
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
emits: ['figure-selected', 'figure-deselected'],
|
||||
data() {
|
||||
return {
|
||||
selected: [],
|
||||
loading: loading,
|
||||
plots: plots,
|
||||
error,
|
||||
}
|
||||
},
|
||||
setup(_, ctx) {
|
||||
console.log('Setup')
|
||||
emit = ctx.emit
|
||||
},
|
||||
async mounted() {
|
||||
const transport = new GrpcWebFetchTransport({ baseUrl: host })
|
||||
const figureService = new FigureServiceClient(transport)
|
||||
const stream = figureService.getFigure({})
|
||||
for await (const response of stream.responses) {
|
||||
console.log('Got new figure', response)
|
||||
plots.value.push(response)
|
||||
}
|
||||
console.log('Done fetching figures')
|
||||
loading.value = false
|
||||
},
|
||||
watch: {
|
||||
selected(newVal: string[], oldVal: string[]) {
|
||||
const added = newVal.filter((x) => !oldVal.includes(x))
|
||||
const removed = oldVal.filter((x) => !newVal.includes(x))
|
||||
|
||||
for (const uuid of added) {
|
||||
console.log('Selected', uuid)
|
||||
const plot = plots.value.find((x) => x.uuid === uuid)
|
||||
emit('figure-selected', plot)
|
||||
}
|
||||
for (const uuid of removed) {
|
||||
console.log('Deselected', uuid)
|
||||
const plot = plots.value.find((x) => x.uuid === uuid)
|
||||
emit('figure-deselected', plot)
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-navigation-drawer permanent width="250">
|
||||
<v-list>
|
||||
<v-list-item title="Available figures" subtitle="Choose some of the figures below" />
|
||||
</v-list>
|
||||
<v-divider />
|
||||
<v-list v-model:selected="selected" select-strategy="leaf" nav>
|
||||
<v-list-item v-for="plot in plots" :key="plot.uuid" :value="plot.uuid">
|
||||
<v-list-item-title>{{ plot.title }}</v-list-item-title>
|
||||
<v-list-item-subtitle class="text-high-emphasis"
|
||||
>{{ plot.xLabel }} w/ {{ plot.yLabel }}</v-list-item-subtitle
|
||||
>
|
||||
</v-list-item>
|
||||
<v-skeleton-loader
|
||||
type="list-item-two-line"
|
||||
v-for="n in 3"
|
||||
:key="n"
|
||||
v-if="loading"
|
||||
></v-skeleton-loader>
|
||||
</v-list>
|
||||
</v-navigation-drawer>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
1
where_fi/visualise/frontend/src/connect.ts
Normal file
1
where_fi/visualise/frontend/src/connect.ts
Normal file
@ -0,0 +1 @@
|
||||
export const host = 'http://localhost:8080'
|
||||
24
where_fi/visualise/frontend/src/main.ts
Normal file
24
where_fi/visualise/frontend/src/main.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import App from './App.vue'
|
||||
|
||||
import { createApp } from 'vue'
|
||||
import 'vuetify/styles'
|
||||
import { createVuetify } from 'vuetify'
|
||||
import * as components from 'vuetify/components'
|
||||
import * as directives from 'vuetify/directives'
|
||||
import { aliases, mdi } from 'vuetify/iconsets/mdi'
|
||||
import '@mdi/font/css/materialdesignicons.css'
|
||||
|
||||
const vuetify = createVuetify({
|
||||
components,
|
||||
directives,
|
||||
theme: { defaultTheme: 'dark' },
|
||||
icons: {
|
||||
defaultSet: 'mdi',
|
||||
aliases,
|
||||
sets: {
|
||||
mdi,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
createApp(App).use(vuetify).mount('#app')
|
||||
16
where_fi/visualise/frontend/tsconfig.app.json
Normal file
16
where_fi/visualise/frontend/tsconfig.app.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||
"exclude": ["src/**/__tests__/*"],
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"vueCompilerOptions": {
|
||||
"fallthroughAttributes": true,
|
||||
"strictTemplates": true
|
||||
}
|
||||
}
|
||||
11
where_fi/visualise/frontend/tsconfig.json
Normal file
11
where_fi/visualise/frontend/tsconfig.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
19
where_fi/visualise/frontend/tsconfig.node.json
Normal file
19
where_fi/visualise/frontend/tsconfig.node.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "@tsconfig/node22/tsconfig.json",
|
||||
"include": [
|
||||
"vite.config.*",
|
||||
"vitest.config.*",
|
||||
"cypress.config.*",
|
||||
"nightwatch.conf.*",
|
||||
"playwright.config.*",
|
||||
"eslint.config.*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"types": ["node"]
|
||||
}
|
||||
}
|
||||
15
where_fi/visualise/frontend/vite.config.ts
Normal file
15
where_fi/visualise/frontend/vite.config.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue(), vueDevTools()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
},
|
||||
},
|
||||
})
|
||||
27
where_fi/visualise/protos/figure.proto
Normal file
27
where_fi/visualise/protos/figure.proto
Normal file
@ -0,0 +1,27 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "figure_type/line.proto";
|
||||
import "figure_type/heatmap.proto";
|
||||
|
||||
message Figure {
|
||||
string uuid = 1;
|
||||
string title = 2;
|
||||
string x_label = 3;
|
||||
string y_label = 4;
|
||||
}
|
||||
|
||||
message FigureData {
|
||||
string uuid = 1;
|
||||
oneof figure {
|
||||
LineChartData line = 2;
|
||||
HeatmapData heatmap = 3;
|
||||
}
|
||||
}
|
||||
|
||||
message FigureRequest {}
|
||||
message FigureDataRequest { string uuid = 1; }
|
||||
|
||||
service FigureService {
|
||||
rpc GetFigure(FigureRequest) returns (stream Figure) {}
|
||||
rpc GetFigureUpdate(FigureDataRequest) returns (stream FigureData) {}
|
||||
}
|
||||
10
where_fi/visualise/protos/figure_type/heatmap.proto
Normal file
10
where_fi/visualise/protos/figure_type/heatmap.proto
Normal file
@ -0,0 +1,10 @@
|
||||
syntax = "proto3";
|
||||
|
||||
message HeatmapData {
|
||||
string uuid = 1;
|
||||
repeated float x = 2;
|
||||
repeated float y = 3;
|
||||
uint32 width = 4;
|
||||
uint32 height = 5;
|
||||
string cmap = 6;
|
||||
}
|
||||
18
where_fi/visualise/protos/figure_type/line.proto
Normal file
18
where_fi/visualise/protos/figure_type/line.proto
Normal file
@ -0,0 +1,18 @@
|
||||
syntax = "proto3";
|
||||
|
||||
message LineChartData {
|
||||
message Line {
|
||||
string uuid = 1;
|
||||
repeated float x = 2;
|
||||
repeated float y = 3;
|
||||
string label = 4;
|
||||
string color = 5;
|
||||
}
|
||||
repeated Line lines = 1;
|
||||
}
|
||||
|
||||
message LineChartPoint {
|
||||
string uuid = 1;
|
||||
float x = 2;
|
||||
float y = 3;
|
||||
}
|
||||
127
where_fi/visualise/server/__init__.py
Normal file
127
where_fi/visualise/server/__init__.py
Normal file
@ -0,0 +1,127 @@
|
||||
import logging
|
||||
import multiprocessing as mp
|
||||
import queue
|
||||
import threading
|
||||
import uuid
|
||||
from concurrent import futures
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from typing import Any, Generator
|
||||
|
||||
import grpc
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
|
||||
from .generated import figure_pb2, figure_pb2_grpc
|
||||
from .generated.figure_type import heatmap_pb2, line_pb2
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
clients: dict[str, list[queue.Queue[figure_pb2.FigureData]]] = {}
|
||||
clients_lock = threading.Lock()
|
||||
|
||||
|
||||
class DataType(Enum):
|
||||
RAW_CSI = 1
|
||||
PROCESSED_CSI = 2
|
||||
HEATMAP = 3
|
||||
|
||||
|
||||
@dataclass
|
||||
class VisualiserData:
|
||||
data: npt.NDArray[Any]
|
||||
dtype: DataType
|
||||
|
||||
|
||||
figures = {
|
||||
"raw_phase": figure_pb2.Figure(
|
||||
uuid=str(uuid.uuid4()),
|
||||
title="Raw CSI Phase",
|
||||
x_label="Subcarrier",
|
||||
y_label="Phase",
|
||||
),
|
||||
"processed_phase": figure_pb2.Figure(
|
||||
uuid=str(uuid.uuid4()),
|
||||
title="Preprocessed CSI Phase",
|
||||
x_label="Subcarrier",
|
||||
y_label="Phase",
|
||||
),
|
||||
"aoa_heatmap": figure_pb2.Figure(
|
||||
uuid=str(uuid.uuid4()),
|
||||
title="Preprocessed CSI Phase",
|
||||
x_label="Subcarrier",
|
||||
y_label="Phase",
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
class FigureServer(figure_pb2_grpc.FigureServiceServicer):
|
||||
def GetFigure(
|
||||
self, request: figure_pb2.FigureRequest, context: grpc.ServicerContext
|
||||
) -> Generator[figure_pb2.Figure, None, None]:
|
||||
for figure in figures.values():
|
||||
yield figure
|
||||
|
||||
def GetFigureUpdate(
|
||||
self, request: figure_pb2.FigureDataRequest, context: grpc.ServicerContext
|
||||
) -> Generator[figure_pb2.FigureData, None, None]:
|
||||
logger.info(
|
||||
f"Received request for figure data stream for figure {request.uuid}"
|
||||
)
|
||||
|
||||
with clients_lock:
|
||||
q: queue.Queue[figure_pb2.FigureData] = queue.Queue()
|
||||
if request.uuid not in clients:
|
||||
clients[request.uuid] = []
|
||||
clients[request.uuid].append(q)
|
||||
|
||||
try:
|
||||
while context.is_active():
|
||||
try:
|
||||
yield q.get(timeout=1)
|
||||
except queue.Empty:
|
||||
pass
|
||||
finally:
|
||||
with clients_lock:
|
||||
clients[request.uuid].remove(q)
|
||||
|
||||
|
||||
def add_data(dtype: DataType, new_data: npt.NDArray[np.complex128]) -> None:
|
||||
match dtype:
|
||||
case DataType.RAW_CSI | DataType.PROCESSED_CSI:
|
||||
uuid = (
|
||||
figures["raw_phase"].uuid
|
||||
if dtype == DataType.RAW_CSI
|
||||
else figures["processed_phase"].uuid
|
||||
)
|
||||
lines = [
|
||||
line_pb2.LineChartData.Line(
|
||||
y=np.angle(new_data)[:, i, 0], label=f"Antenna {i}"
|
||||
)
|
||||
for i in range(new_data.shape[1])
|
||||
]
|
||||
linechart = line_pb2.LineChartData(lines=lines)
|
||||
for q in clients.get(uuid, []):
|
||||
q.put(figure_pb2.FigureData(uuid=uuid, line=linechart))
|
||||
case DataType.HEATMAP:
|
||||
pass
|
||||
|
||||
|
||||
def listen_for_data(data_queue: "mp.Queue[VisualiserData]") -> None:
|
||||
while True:
|
||||
data = data_queue.get()
|
||||
add_data(data.dtype, data.data)
|
||||
|
||||
|
||||
def start(data_queue: "mp.Queue[VisualiserData]") -> None:
|
||||
logger.info("Visualisation server shut down")
|
||||
|
||||
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
|
||||
figure_pb2_grpc.add_FigureServiceServicer_to_server(FigureServer(), server)
|
||||
server.add_insecure_port("[::]:50051")
|
||||
server.add_insecure_port("0.0.0.0:50051")
|
||||
logger.info("Starting server on port 50051")
|
||||
server.start()
|
||||
logger.info("Server started")
|
||||
threading.Thread(target=listen_for_data, args=(data_queue,), daemon=True).start()
|
||||
server.wait_for_termination()
|
||||
@ -1,42 +0,0 @@
|
||||
const plotElement = document.getElementById("plot");
|
||||
|
||||
const layout = {
|
||||
title: "Real-Time Complex Data Visualization",
|
||||
xaxis: { title: "Time (ms)" },
|
||||
yaxis: { title: "Value" },
|
||||
};
|
||||
|
||||
Plotly.newPlot(
|
||||
plotElement,
|
||||
[
|
||||
{
|
||||
x: [],
|
||||
y: [],
|
||||
type: "scatter",
|
||||
mode: "lines+markers", // Line + markers
|
||||
},
|
||||
],
|
||||
layout,
|
||||
);
|
||||
|
||||
const socket = new WebSocket("ws://" + location.host + "/data");
|
||||
socket.onopen = function () {
|
||||
console.log("Connected to the server");
|
||||
socket.send("0 0 0");
|
||||
};
|
||||
socket.addEventListener("message", function (msg) {
|
||||
Plotly.extendTraces(
|
||||
plotElement,
|
||||
{
|
||||
x: [[msg.timeStamp]],
|
||||
y: [[msg.data]],
|
||||
},
|
||||
[0],
|
||||
300,
|
||||
);
|
||||
});
|
||||
|
||||
function changeSubcarrier() {
|
||||
const subcarrier = document.getElementById("subcarrier").value;
|
||||
socket.send(subcarrier + " 0 0");
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||
<script src="https://cdn.plot.ly/plotly-2.35.2.min.js" charset="utf-8"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Preprocessing</h1>
|
||||
<div id="plot"></div>
|
||||
<script src="{{url_for('static', filename='plot.js')}}"></script>
|
||||
<input type="number" id="subcarrier" class="form-control" placeholder="Choose subcarrier" onchange="changeSubcarrier()" value="0">
|
||||
</div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
|
||||
</body>
|
||||
Loading…
Reference in New Issue
Block a user