Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 04e5d24cab |
6
ecgsyn-rs/.gitignore
vendored
Normal file
6
ecgsyn-rs/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
*.log
|
||||||
|
tmp/
|
||||||
|
|
||||||
|
node_modules/
|
||||||
|
pkg/
|
||||||
|
target/
|
||||||
8
ecgsyn-rs/.idea/.gitignore
generated
vendored
Normal file
8
ecgsyn-rs/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
11
ecgsyn-rs/.idea/ecgsyn-rs.iml
generated
Normal file
11
ecgsyn-rs/.idea/ecgsyn-rs.iml
generated
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="EMPTY_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
8
ecgsyn-rs/.idea/modules.xml
generated
Normal file
8
ecgsyn-rs/.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/ecgsyn-rs.iml" filepath="$PROJECT_DIR$/.idea/ecgsyn-rs.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
ecgsyn-rs/.idea/vcs.xml
generated
Normal file
6
ecgsyn-rs/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
466
ecgsyn-rs/Cargo.lock
generated
Normal file
466
ecgsyn-rs/Cargo.lock
generated
Normal file
@@ -0,0 +1,466 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "approx"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bumpalo"
|
||||||
|
version = "3.16.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytemuck"
|
||||||
|
version = "1.19.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "0.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ecgsyn"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"itertools",
|
||||||
|
"js-sys",
|
||||||
|
"nalgebra",
|
||||||
|
"num-traits",
|
||||||
|
"ode_solvers",
|
||||||
|
"rand_xoshiro",
|
||||||
|
"realfft",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wee_alloc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "js-sys"
|
||||||
|
version = "0.3.72"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9"
|
||||||
|
dependencies = [
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.162"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "matrixmultiply"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"rawpointer",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memory_units"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nalgebra"
|
||||||
|
version = "0.33.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "26aecdf64b707efd1310e3544d709c5c0ac61c13756046aaaba41be5c4f66a3b"
|
||||||
|
dependencies = [
|
||||||
|
"approx",
|
||||||
|
"matrixmultiply",
|
||||||
|
"nalgebra-macros",
|
||||||
|
"num-complex",
|
||||||
|
"num-rational",
|
||||||
|
"num-traits",
|
||||||
|
"simba",
|
||||||
|
"typenum",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nalgebra-macros"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "254a5372af8fc138e36684761d3c0cdb758a4410e938babcff1c860ce14ddbfc"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-bigint"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
|
||||||
|
dependencies = [
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-complex"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-integer"
|
||||||
|
version = "0.1.46"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-rational"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
|
||||||
|
dependencies = [
|
||||||
|
"num-bigint",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ode_solvers"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7f899b49b30ecbcfc2db3328b9517741764266e8b42a91cde85573da67790019"
|
||||||
|
dependencies = [
|
||||||
|
"nalgebra",
|
||||||
|
"num-traits",
|
||||||
|
"simba",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.20.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "paste"
|
||||||
|
version = "1.0.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "primal-check"
|
||||||
|
version = "0.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc0d895b311e3af9902528fbb8f928688abbd95872819320517cc24ca6b2bd08"
|
||||||
|
dependencies = [
|
||||||
|
"num-integer",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.89"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.37"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_xoshiro"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa"
|
||||||
|
dependencies = [
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rawpointer"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "realfft"
|
||||||
|
version = "3.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "390252372b7f2aac8360fc5e72eba10136b166d6faeed97e6d0c8324eb99b2b1"
|
||||||
|
dependencies = [
|
||||||
|
"rustfft",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustfft"
|
||||||
|
version = "6.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "43806561bc506d0c5d160643ad742e3161049ac01027b5e6d7524091fd401d86"
|
||||||
|
dependencies = [
|
||||||
|
"num-complex",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
"primal-check",
|
||||||
|
"strength_reduce",
|
||||||
|
"transpose",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "safe_arch"
|
||||||
|
version = "0.7.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3460605018fdc9612bce72735cba0d27efbcd9904780d44c7e3a9948f96148a"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simba"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b3a386a501cd104797982c15ae17aafe8b9261315b5d07e3ec803f2ea26be0fa"
|
||||||
|
dependencies = [
|
||||||
|
"approx",
|
||||||
|
"num-complex",
|
||||||
|
"num-traits",
|
||||||
|
"paste",
|
||||||
|
"wide",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strength_reduce"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.87"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "transpose"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1ad61aed86bc3faea4300c7aee358b4c6d0c8d6ccc36524c96e4c92ccf26e77e"
|
||||||
|
dependencies = [
|
||||||
|
"num-integer",
|
||||||
|
"strength_reduce",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typenum"
|
||||||
|
version = "1.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen"
|
||||||
|
version = "0.2.95"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
"once_cell",
|
||||||
|
"wasm-bindgen-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-backend"
|
||||||
|
version = "0.2.95"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro"
|
||||||
|
version = "0.2.95"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"wasm-bindgen-macro-support",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro-support"
|
||||||
|
version = "0.2.95"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-backend",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-shared"
|
||||||
|
version = "0.2.95"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wee_alloc"
|
||||||
|
version = "0.4.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 0.1.10",
|
||||||
|
"libc",
|
||||||
|
"memory_units",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wide"
|
||||||
|
version = "0.7.28"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b828f995bf1e9622031f8009f8481a85406ce1f4d4588ff746d872043e855690"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
"safe_arch",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-i686-pc-windows-gnu",
|
||||||
|
"winapi-x86_64-pc-windows-gnu",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-i686-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
24
ecgsyn-rs/Cargo.toml
Normal file
24
ecgsyn-rs/Cargo.toml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
[package]
|
||||||
|
name = "ecgsyn"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
itertools = "0.13.0"
|
||||||
|
js-sys = "0.3.72"
|
||||||
|
nalgebra = "0.33.2"
|
||||||
|
num-traits = "0.2.19"
|
||||||
|
ode_solvers = "0.5.0"
|
||||||
|
rand_xoshiro = "0.6.0"
|
||||||
|
realfft = "3.4.0"
|
||||||
|
wasm-bindgen = "0.2.95"
|
||||||
|
wee_alloc = "0.4.5"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
overflow-checks = false
|
||||||
|
opt-level = "s"
|
||||||
|
lto = true
|
||||||
26
ecgsyn-rs/site/package.json
Normal file
26
ecgsyn-rs/site/package.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"name": "ecgsyn-site",
|
||||||
|
"description": "description",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": "John Luebs <john@luebs.org>",
|
||||||
|
"license": "MIT",
|
||||||
|
"main": "app/main.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "webpack --config webpack.config.js",
|
||||||
|
"serve": "webpack serve --config webpack.config.js --open --mode development --devtool eval-source-map"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ecgsyn": "link:../pkg",
|
||||||
|
"picnic": "^7.1.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"copy-webpack-plugin": "^12.0.2",
|
||||||
|
"css-loader": "^7.1.2",
|
||||||
|
"style-loader": "^4.0.0",
|
||||||
|
"ts-loader": "^9.5.1",
|
||||||
|
"typescript": "^5.6.3",
|
||||||
|
"webpack": "^5.96.1",
|
||||||
|
"webpack-cli": "^5.1.4",
|
||||||
|
"webpack-dev-server": "^5.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
2844
ecgsyn-rs/site/pnpm-lock.yaml
generated
Normal file
2844
ecgsyn-rs/site/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
4
ecgsyn-rs/site/src/custom.d.ts
vendored
Normal file
4
ecgsyn-rs/site/src/custom.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
declare module "*.css" {
|
||||||
|
const content: any;
|
||||||
|
export default content;
|
||||||
|
}
|
||||||
80
ecgsyn-rs/site/src/index.html
Normal file
80
ecgsyn-rs/site/src/index.html
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html class="no-js" lang="">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
||||||
|
<title>ECGSYN</title>
|
||||||
|
<meta name="description" content="" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
|
||||||
|
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
||||||
|
<!-- Place favicon.ico in the root directory -->
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<script src="./bundle.js"></script>
|
||||||
|
<!--[if lt IE 8]>
|
||||||
|
<p class="browserupgrade">
|
||||||
|
You are using an <strong>outdated</strong> browser. Please
|
||||||
|
<a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.
|
||||||
|
</p>
|
||||||
|
<![endif]-->
|
||||||
|
|
||||||
|
<div class="flex two">
|
||||||
|
<div class="full">
|
||||||
|
<canvas height="200"></canvas>
|
||||||
|
</div>
|
||||||
|
<div style="width: 5%">
|
||||||
|
<label class="stack wlabel">
|
||||||
|
<input name="stack" type="radio" />
|
||||||
|
<span class="button toggle">P</span>
|
||||||
|
</label>
|
||||||
|
<label class="stack wlabel">
|
||||||
|
<input name="stack" type="radio" />
|
||||||
|
<span class="button toggle">Q</span>
|
||||||
|
</label>
|
||||||
|
<label class="stack wlabel">
|
||||||
|
<input name="stack" type="radio" />
|
||||||
|
<span class="button toggle">R</span>
|
||||||
|
</label>
|
||||||
|
<label class="stack wlabel">
|
||||||
|
<input name="stack" type="radio" />
|
||||||
|
<span class="button toggle">S</span>
|
||||||
|
</label>
|
||||||
|
<label class="stack wlabel">
|
||||||
|
<input name="stack" type="radio" />
|
||||||
|
<span class="button toggle">T</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div style="width: 95%">
|
||||||
|
<article class="parameter-group">
|
||||||
|
<div class="slider-group">
|
||||||
|
<div class="slider-container">
|
||||||
|
<label>θ (°)</label>
|
||||||
|
<input type="range" min="-180" max="180" value="0" step="1" />
|
||||||
|
<input class="value" type="number" min="-180" max="180" value="0" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="slider-container">
|
||||||
|
<label>a</label>
|
||||||
|
<input type="range" min="-30" max="30" value="0" step="0.1" />
|
||||||
|
<input class="value" type="number" min="-30" max="30" value="0" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="slider-container">
|
||||||
|
<label>b</label>
|
||||||
|
<input type="range" min="0" max="1" value="0.5" step="0.01" />
|
||||||
|
<input class="value" type="number" min="0" max="1" value="0.5" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="slider-container">
|
||||||
|
<label>θrf</label>
|
||||||
|
<input type="range" min="0" max="0.5" value="0" step="0.01" />
|
||||||
|
<input class="value" type="number" min="0" max="0.5" value="0" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
18
ecgsyn-rs/site/src/index.ts
Normal file
18
ecgsyn-rs/site/src/index.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import * as ecg from "ecgsyn";
|
||||||
|
import "./styles.css";
|
||||||
|
|
||||||
|
//ecg.greet("WebAssembly with Penis!");
|
||||||
|
let tp = new ecg.TimeParameters();
|
||||||
|
|
||||||
|
let rrprocess = new ecg.RrProcess(tp);
|
||||||
|
let rrp = new ecg.RrParameters();
|
||||||
|
|
||||||
|
let series = rrprocess.generate_series(rrp);
|
||||||
|
|
||||||
|
let params = new ecg.Parameters(-0.4, 1.2, 0.0);
|
||||||
|
params.push_extremum(new ecg.Extremum(0.0, 30, 0.1, 0.0));
|
||||||
|
|
||||||
|
let buffer = new ecg.Buffer();
|
||||||
|
ecg.ecgsyn(params, series, buffer);
|
||||||
|
(window as any).ecg = ecg;
|
||||||
|
(window as any).buffer = buffer;
|
||||||
68
ecgsyn-rs/site/src/styles.css
Normal file
68
ecgsyn-rs/site/src/styles.css
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
@import "picnic/picnic.css";
|
||||||
|
|
||||||
|
.wlabel {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-panel {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 20px auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wave-selector {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
padding: 15px;
|
||||||
|
background: #f8f8f8;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wave-selector label {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wave-buttons {
|
||||||
|
margin-left: auto;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.parameter-group {
|
||||||
|
margin: 0 0;
|
||||||
|
padding: 15px;
|
||||||
|
background: #f8f8f8;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-group {
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin: 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-container label {
|
||||||
|
width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-container input[type="range"] {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-container .value {
|
||||||
|
width: 8em;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.small {
|
||||||
|
padding: 0.3em 0.7em;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
22
ecgsyn-rs/site/tsconfig.json
Normal file
22
ecgsyn-rs/site/tsconfig.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES5",
|
||||||
|
"module": "ES6",
|
||||||
|
"jsx": "react",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"outDir": "./dist/",
|
||||||
|
"strict": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"declaration": true,
|
||||||
|
"declarationDir": "./dist/types/",
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"downlevelIteration": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"isolatedDeclarations": true
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["src/**/*.test.ts"]
|
||||||
|
}
|
||||||
34
ecgsyn-rs/site/webpack.config.js
Normal file
34
ecgsyn-rs/site/webpack.config.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
const CopyPlugin = require("copy-webpack-plugin");
|
||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
entry: "./src/index.ts",
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.css$/i,
|
||||||
|
use: ["style-loader", "css-loader"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.tsx?$/,
|
||||||
|
use: "ts-loader",
|
||||||
|
exclude: /node_modules/,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
extensions: [".tsx", ".ts", ".js"],
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
path: path.resolve(__dirname, "dist"),
|
||||||
|
filename: "bundle.js",
|
||||||
|
},
|
||||||
|
experiments: {
|
||||||
|
asyncWebAssembly: true,
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new CopyPlugin({
|
||||||
|
patterns: [{ from: "src/index.html" }],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
};
|
||||||
558
ecgsyn-rs/src/lib.rs
Normal file
558
ecgsyn-rs/src/lib.rs
Normal file
@@ -0,0 +1,558 @@
|
|||||||
|
#[global_allocator]
|
||||||
|
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
||||||
|
|
||||||
|
use itertools::Itertools;
|
||||||
|
use itertools::MinMaxResult::MinMax;
|
||||||
|
use js_sys::{Float64Array, WebAssembly};
|
||||||
|
use nalgebra::{Complex, Vector3};
|
||||||
|
use num_traits::float::Float;
|
||||||
|
use ode_solvers::Dopri5;
|
||||||
|
use rand_xoshiro::{
|
||||||
|
rand_core::{RngCore, SeedableRng},
|
||||||
|
Xoshiro256Plus,
|
||||||
|
};
|
||||||
|
use realfft::{ComplexToReal, RealFftPlanner};
|
||||||
|
use std::{f64::consts::TAU, iter::Sum, sync::Arc};
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
trait RoundTies {
|
||||||
|
fn round_ties_even(self) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RoundTies for f32 {
|
||||||
|
#[inline(always)]
|
||||||
|
fn round_ties_even(self) -> Self {
|
||||||
|
f32::round_ties_even(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RoundTies for f64 {
|
||||||
|
#[inline(always)]
|
||||||
|
fn round_ties_even(self) -> Self {
|
||||||
|
f64::round_ties_even(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ieee_rem<T: Float + RoundTies>(x: T, y: T) -> T {
|
||||||
|
let n = (x / y).round_ties_even();
|
||||||
|
x - n * y
|
||||||
|
}
|
||||||
|
|
||||||
|
trait Rand {
|
||||||
|
fn rand<R: RngCore>(rng: &mut R) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Rand for f32 {
|
||||||
|
fn rand<R: RngCore>(rng: &mut R) -> f32 {
|
||||||
|
f32::from_bits(rng.next_u32() >> 8) * 5.9604645e-8f32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Rand for f64 {
|
||||||
|
fn rand<R: RngCore>(rng: &mut R) -> f64 {
|
||||||
|
f64::from_bits(rng.next_u64() >> 11) * 1.1102230246251565e-16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mean<T>(v: &[T]) -> T
|
||||||
|
where
|
||||||
|
T: Float + Sum<T>,
|
||||||
|
{
|
||||||
|
v.iter().copied().sum::<T>() / T::from(v.len()).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sum_square_deviations<T>(v: &[T], c: Option<T>) -> T
|
||||||
|
where
|
||||||
|
T: Float + Sum<T>,
|
||||||
|
{
|
||||||
|
let c = c.unwrap_or_else(|| mean(v));
|
||||||
|
v.iter().map(|x| (*x - c).powi(2)).sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn variance<T>(v: &[T], xbar: Option<T>) -> T
|
||||||
|
where
|
||||||
|
T: Float + Sum<T>,
|
||||||
|
{
|
||||||
|
assert!(v.len() > 1);
|
||||||
|
let n = T::from(v.len()).unwrap();
|
||||||
|
let sum = sum_square_deviations(v, xbar);
|
||||||
|
sum / (n - T::one())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn std_deviation<T>(v: &[T], xbar: Option<T>) -> T
|
||||||
|
where
|
||||||
|
T: Float + Sum<T>,
|
||||||
|
{
|
||||||
|
variance(v, xbar).sqrt()
|
||||||
|
}
|
||||||
|
|
||||||
|
const FFT_MIN_SIZE: usize = 32;
|
||||||
|
|
||||||
|
fn fft_is_valid_size(n: usize) -> bool {
|
||||||
|
const MIN_SIZE: usize = FFT_MIN_SIZE;
|
||||||
|
|
||||||
|
// n must be greater than 32, and have prime factors only of 2, 3, and 5.
|
||||||
|
let mut n = n;
|
||||||
|
while n >= 5 * MIN_SIZE && (n % 5) == 0 {
|
||||||
|
n /= 5;
|
||||||
|
}
|
||||||
|
while n >= 3 * MIN_SIZE && (n % 3) == 0 {
|
||||||
|
n /= 3;
|
||||||
|
}
|
||||||
|
while n >= 2 * MIN_SIZE && (n % 2) == 0 {
|
||||||
|
n /= 2;
|
||||||
|
}
|
||||||
|
n == MIN_SIZE
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fft_nearest_valid_size(n: usize, round_up: bool) -> usize {
|
||||||
|
const MIN_SIZE: usize = FFT_MIN_SIZE;
|
||||||
|
if n < MIN_SIZE {
|
||||||
|
return MIN_SIZE;
|
||||||
|
}
|
||||||
|
let initial_size = if round_up {
|
||||||
|
MIN_SIZE * ((n + MIN_SIZE - 1) / MIN_SIZE) // round up
|
||||||
|
} else {
|
||||||
|
MIN_SIZE * (n / MIN_SIZE) // round down
|
||||||
|
};
|
||||||
|
let step = if round_up {
|
||||||
|
MIN_SIZE
|
||||||
|
} else {
|
||||||
|
-(MIN_SIZE as isize) as usize
|
||||||
|
};
|
||||||
|
std::iter::successors(Some(initial_size), |&size| Some(size.wrapping_add(step)))
|
||||||
|
.find(|&size| fft_is_valid_size(size))
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct TimeParameters {
|
||||||
|
/// The number of beats to simulate.
|
||||||
|
pub num_beats: u32,
|
||||||
|
/// The internal sampling frequency in Hz.
|
||||||
|
pub sr_internal: u32,
|
||||||
|
/// Output decimation factor.
|
||||||
|
pub decimate_factor: u32,
|
||||||
|
/// The mean heart rate in beats per minute.
|
||||||
|
pub hr_mean: f64,
|
||||||
|
/// The standard deviation of the heart rate.
|
||||||
|
pub hr_std: f64,
|
||||||
|
/// RNG seed value
|
||||||
|
pub seed: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TimeParameters {
|
||||||
|
fn default() -> Self {
|
||||||
|
TimeParameters {
|
||||||
|
num_beats: 12,
|
||||||
|
sr_internal: 512,
|
||||||
|
decimate_factor: 1,
|
||||||
|
hr_mean: 60.0,
|
||||||
|
hr_std: 1.0,
|
||||||
|
seed: 8,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
impl TimeParameters {
|
||||||
|
#[wasm_bindgen(constructor)]
|
||||||
|
pub fn new() -> TimeParameters {
|
||||||
|
TimeParameters::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct RrParameters {
|
||||||
|
/// Mayer wave frequency in Hz.
|
||||||
|
pub flo: f64,
|
||||||
|
/// flo standard deviation.
|
||||||
|
pub flostd: f64,
|
||||||
|
/// Respiratory frequency in Hz.
|
||||||
|
pub fhi: f64,
|
||||||
|
/// fhi standard deviation.
|
||||||
|
pub fhistd: f64,
|
||||||
|
/// The ratio of power between low and high frequency components.
|
||||||
|
pub lfhfratio: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for RrParameters {
|
||||||
|
fn default() -> Self {
|
||||||
|
RrParameters {
|
||||||
|
flo: 0.1,
|
||||||
|
flostd: 0.01,
|
||||||
|
fhi: 0.25,
|
||||||
|
fhistd: 0.01,
|
||||||
|
lfhfratio: 0.5,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
impl RrParameters {
|
||||||
|
#[wasm_bindgen(constructor)]
|
||||||
|
pub fn new() -> RrParameters {
|
||||||
|
RrParameters::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub struct RrProcess {
|
||||||
|
time_params: TimeParameters,
|
||||||
|
ifft: Arc<dyn ComplexToReal<f64>>,
|
||||||
|
spectrum: Vec<Complex<f64>>,
|
||||||
|
scratch: Vec<Complex<f64>>,
|
||||||
|
signal: Vec<f64>,
|
||||||
|
rng: Xoshiro256Plus,
|
||||||
|
rr_mean: f64,
|
||||||
|
rr_std: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
impl RrProcess {
|
||||||
|
#[wasm_bindgen(constructor)]
|
||||||
|
pub fn new(time_params: &TimeParameters) -> RrProcess {
|
||||||
|
let mut planner = RealFftPlanner::<f64>::new();
|
||||||
|
let rr_mean = 60.0 / time_params.hr_mean;
|
||||||
|
let rr_std = 60.0 * time_params.hr_std / time_params.hr_mean.powi(2);
|
||||||
|
|
||||||
|
let nrr = fft_nearest_valid_size(
|
||||||
|
(time_params.num_beats * time_params.sr_internal * (rr_mean.ceil() as u32)) as usize,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
let ifft = planner.plan_fft_inverse(nrr);
|
||||||
|
let spectrum = ifft.make_input_vec();
|
||||||
|
let signal = ifft.make_output_vec();
|
||||||
|
let scratch = ifft.make_scratch_vec();
|
||||||
|
let rng = Xoshiro256Plus::seed_from_u64(time_params.seed);
|
||||||
|
RrProcess {
|
||||||
|
time_params: time_params.clone(),
|
||||||
|
ifft,
|
||||||
|
spectrum,
|
||||||
|
scratch,
|
||||||
|
signal,
|
||||||
|
rng,
|
||||||
|
rr_mean,
|
||||||
|
rr_std,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn generate_signal(&mut self, params: &RrParameters) -> Box<[f64]> {
|
||||||
|
let w1 = TAU * params.flo;
|
||||||
|
let w2 = TAU * params.fhi;
|
||||||
|
let c1 = TAU * params.flostd;
|
||||||
|
let c2 = TAU * params.fhistd;
|
||||||
|
|
||||||
|
let sig2 = 1.0;
|
||||||
|
let sig1 = params.lfhfratio;
|
||||||
|
|
||||||
|
let sr = self.time_params.sr_internal as f64;
|
||||||
|
let nrr = self.signal.len() as f64;
|
||||||
|
|
||||||
|
let dw = sr / nrr * TAU;
|
||||||
|
|
||||||
|
self.spectrum.iter_mut().enumerate().for_each(|(i, x)| {
|
||||||
|
let w = i as f64 * dw;
|
||||||
|
|
||||||
|
let dw1 = w - w1;
|
||||||
|
let dw2 = w - w2;
|
||||||
|
let hw1 = sig1 * (-dw1.powi(2) / (2.0 * c1.powi(2))).exp() / (TAU * c1.powi(2)).sqrt();
|
||||||
|
let hw2 = sig2 * (-dw2.powi(2) / (2.0 * c2.powi(2))).exp() / (TAU * c2.powi(2)).sqrt();
|
||||||
|
let hw = hw1 + hw2;
|
||||||
|
let sw = (sr / 2.0) * hw.sqrt();
|
||||||
|
let ph = TAU * f64::rand(&mut self.rng);
|
||||||
|
x.re = sw * ph.cos();
|
||||||
|
x.im = sw * ph.sin();
|
||||||
|
});
|
||||||
|
self.spectrum[0].im = 0.0;
|
||||||
|
if let Some(last) = self.spectrum.last_mut() {
|
||||||
|
last.im = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ =
|
||||||
|
self.ifft
|
||||||
|
.process_with_scratch(&mut self.spectrum, &mut self.signal, &mut self.scratch);
|
||||||
|
|
||||||
|
self.signal.iter_mut().for_each(|x| *x *= 1.0 / nrr);
|
||||||
|
let xstd = std_deviation(&self.signal, None);
|
||||||
|
let ratio = self.rr_std / xstd;
|
||||||
|
|
||||||
|
self.signal
|
||||||
|
.iter()
|
||||||
|
.map(|x| x * ratio + self.rr_mean)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn generate_series(&mut self, params: &RrParameters) -> RrSeriesWasm {
|
||||||
|
let signal = self.generate_signal(params);
|
||||||
|
self.rng.jump();
|
||||||
|
RrSeriesWasm(RrSeries::new(
|
||||||
|
&self.time_params,
|
||||||
|
params,
|
||||||
|
self.rng.clone(),
|
||||||
|
&signal,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Segment<T: Float> {
|
||||||
|
end: T,
|
||||||
|
value: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Float> Segment<T> {
|
||||||
|
fn new(end: T, value: T) -> Segment<T> {
|
||||||
|
Segment { end, value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RrSeries<T: Float> {
|
||||||
|
time_params: TimeParameters,
|
||||||
|
rr_params: RrParameters,
|
||||||
|
rng: Xoshiro256Plus,
|
||||||
|
len: usize,
|
||||||
|
segments: Vec<Segment<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Float + RoundTies> RrSeries<T> {
|
||||||
|
fn new(
|
||||||
|
time_params: &TimeParameters,
|
||||||
|
rr_params: &RrParameters,
|
||||||
|
rng: Xoshiro256Plus,
|
||||||
|
signal: &[T],
|
||||||
|
) -> RrSeries<T> {
|
||||||
|
let sr = T::from(time_params.sr_internal).unwrap();
|
||||||
|
let mut tecg = T::zero();
|
||||||
|
let mut i = 0usize;
|
||||||
|
let mut segments = Vec::<Segment<T>>::new();
|
||||||
|
while i < signal.len() {
|
||||||
|
tecg = tecg + signal[i];
|
||||||
|
segments.push(Segment::new(tecg, signal[i]));
|
||||||
|
i = (T::one() + (tecg * sr).round_ties_even())
|
||||||
|
.to_usize()
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
RrSeries {
|
||||||
|
time_params: *time_params,
|
||||||
|
rr_params: *rr_params,
|
||||||
|
rng,
|
||||||
|
len: signal.len(),
|
||||||
|
segments,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn val(&self, t: T) -> T {
|
||||||
|
let len = self.segments.len();
|
||||||
|
let i = match self.segments.partition_point(|x| t.lt(&x.end)) {
|
||||||
|
i if i < len => i,
|
||||||
|
_ => len - 1,
|
||||||
|
};
|
||||||
|
self.segments[i].value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct Extremum {
|
||||||
|
pub theta: f64,
|
||||||
|
pub a: f64,
|
||||||
|
pub b: f64,
|
||||||
|
pub theta_rf: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
impl Extremum {
|
||||||
|
#[wasm_bindgen(constructor)]
|
||||||
|
pub fn new(deg: f64, a: f64, b: f64, theta_rf: f64) -> Extremum {
|
||||||
|
Extremum {
|
||||||
|
theta: deg.to_radians(),
|
||||||
|
a,
|
||||||
|
b,
|
||||||
|
theta_rf,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub struct Buffer(Vec<f64>);
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
impl Buffer {
|
||||||
|
#[wasm_bindgen(constructor)]
|
||||||
|
pub fn new() -> Buffer {
|
||||||
|
Buffer(Vec::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn array(&self) -> Float64Array {
|
||||||
|
let memory_buffer = wasm_bindgen::memory()
|
||||||
|
.dyn_into::<WebAssembly::Memory>()
|
||||||
|
.unwrap()
|
||||||
|
.buffer();
|
||||||
|
Float64Array::new_with_byte_offset_and_length(
|
||||||
|
&memory_buffer,
|
||||||
|
self.0.as_ptr() as u32,
|
||||||
|
self.0.len() as u32,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub struct Parameters {
|
||||||
|
pub range_min: f64,
|
||||||
|
pub range_max: f64,
|
||||||
|
pub noise_amplitude: f64,
|
||||||
|
extrema: Vec<Extremum>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
impl Parameters {
|
||||||
|
#[wasm_bindgen(constructor)]
|
||||||
|
pub fn new(range_min: f64, range_max: f64, noise_amplitude: f64) -> Parameters {
|
||||||
|
Parameters {
|
||||||
|
range_min,
|
||||||
|
range_max,
|
||||||
|
noise_amplitude,
|
||||||
|
extrema: Vec::with_capacity(5),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn push_extremum(&mut self, extremum: &Extremum) {
|
||||||
|
self.extrema.push(*extremum);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn remove_extremum(&mut self, index: usize) {
|
||||||
|
self.extrema.remove(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn set_extremum(&mut self, index: usize, extremum: &Extremum) {
|
||||||
|
self.extrema[index] = *extremum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Attractor {
|
||||||
|
ti: f64,
|
||||||
|
ai: f64,
|
||||||
|
bi: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct EcgSystem<'a> {
|
||||||
|
rr: &'a RrSeries<f64>,
|
||||||
|
fhi: f64,
|
||||||
|
attrs: Vec<Attractor>,
|
||||||
|
}
|
||||||
|
type State = Vector3<f64>;
|
||||||
|
impl ode_solvers::System<f64, State> for EcgSystem<'_> {
|
||||||
|
fn system(&self, t: f64, v: &State, dv: &mut State) {
|
||||||
|
let ta = v.y.atan2(v.x);
|
||||||
|
|
||||||
|
let r0 = 1.0;
|
||||||
|
let a0 = 1.0 - (v.x.powi(2) + v.y.powi(2)).sqrt() / r0;
|
||||||
|
|
||||||
|
let w0 = TAU / self.rr.val(t);
|
||||||
|
|
||||||
|
let zbase = 0.005 * (TAU * self.fhi * t).sin();
|
||||||
|
|
||||||
|
dv.x = a0 * v.x - w0 * v.y;
|
||||||
|
dv.y = a0 * v.y + w0 * v.x;
|
||||||
|
dv.z = 0.0;
|
||||||
|
|
||||||
|
for attr in self.attrs.iter() {
|
||||||
|
let dt = ieee_rem(ta - attr.ti, TAU);
|
||||||
|
|
||||||
|
dv.z += -attr.ai * dt * (-0.5 * dt.powi(2) / attr.bi.powi(2)).exp();
|
||||||
|
}
|
||||||
|
dv.z += -1.0 * (v.z - zbase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn ecgsyn(params: &Parameters, rr_series: &mut RrSeriesWasm, buffer: &mut Buffer) {
|
||||||
|
let time_params = &rr_series.0.time_params;
|
||||||
|
let rr_params = &rr_series.0.rr_params;
|
||||||
|
|
||||||
|
let hr_sec = time_params.hr_mean / 60.0;
|
||||||
|
let hr_fact = hr_sec.sqrt();
|
||||||
|
|
||||||
|
let system = EcgSystem {
|
||||||
|
rr: &rr_series.0,
|
||||||
|
fhi: rr_params.fhi,
|
||||||
|
attrs: params
|
||||||
|
.extrema
|
||||||
|
.iter()
|
||||||
|
.map(|ex| Attractor {
|
||||||
|
ti: ex.theta * hr_sec.powf(ex.theta_rf),
|
||||||
|
ai: ex.a,
|
||||||
|
bi: ex.b * hr_fact,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let dt = 1.0 / time_params.sr_internal as f64;
|
||||||
|
let t_end = rr_series.0.len as f64 * dt;
|
||||||
|
let y0 = State::new(1.0, 0.0, 0.04);
|
||||||
|
|
||||||
|
let mut stepper = Dopri5::new(system, 0.0, t_end, dt, y0, 1.0e-6, 1.0e-6);
|
||||||
|
|
||||||
|
let _ = stepper.integrate().unwrap();
|
||||||
|
|
||||||
|
let zresult = &mut buffer.0;
|
||||||
|
let stepper_out = stepper.y_out();
|
||||||
|
zresult.clear();
|
||||||
|
stepper_out
|
||||||
|
.iter()
|
||||||
|
.step_by(time_params.decimate_factor as usize)
|
||||||
|
.for_each(|y| {
|
||||||
|
zresult.push(y.z);
|
||||||
|
});
|
||||||
|
|
||||||
|
let (zmin, zmax) = if let MinMax(zmin, zmax) = zresult.iter().minmax() {
|
||||||
|
(*zmin, *zmax)
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let zrange = zmax - zmin;
|
||||||
|
zresult.iter_mut().for_each(|z| {
|
||||||
|
*z = params.range_min
|
||||||
|
+ (params.range_max - params.range_min) * (*z - zmin) / zrange
|
||||||
|
+ (2.0 * f64::rand(&mut rr_series.0.rng) - 1.0) * params.noise_amplitude;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = "RrSeries")]
|
||||||
|
pub struct RrSeriesWasm(RrSeries<f64>);
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
let time_params = TimeParameters::default();
|
||||||
|
let rr_params = RrParameters::default();
|
||||||
|
let mut rr_process = RrProcess::new(&time_params);
|
||||||
|
let mut rr_series = rr_process.generate_series(&rr_params);
|
||||||
|
let mut buffer = Buffer::new();
|
||||||
|
let mut params = Parameters::new(-0.4, 1.2, 0.0);
|
||||||
|
vec![
|
||||||
|
Extremum::new(-70.0, 1.2, 0.25, 0.25),
|
||||||
|
Extremum::new(-15.0, 0.0, 0.0, 0.5),
|
||||||
|
Extremum::new(0.0, 30.0, 0.1, 0.0),
|
||||||
|
Extremum::new(15.0, -7.5, 0.1, 0.5),
|
||||||
|
Extremum::new(100.0, 0.75, 0.04, 0.25),
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.for_each(|ex| {
|
||||||
|
params.push_extremum(ex);
|
||||||
|
});
|
||||||
|
ecgsyn(¶ms, &mut rr_series, &mut buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
22
index.html
22
index.html
@@ -1,22 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html class="no-js" lang="">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
|
||||||
<title>Untitled</title>
|
|
||||||
<meta name="description" content="" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
|
|
||||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
|
||||||
<!-- Place favicon.ico in the root directory -->
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<!--[if lt IE 8]>
|
|
||||||
<p class="browserupgrade">
|
|
||||||
You are using an <strong>outdated</strong> browser. Please
|
|
||||||
<a href="http://browsehappy.com/">upgrade your browser</a> to improve
|
|
||||||
your experience.
|
|
||||||
</p>
|
|
||||||
<![endif]-->
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Reference in New Issue
Block a user