effusive 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- effusive-0.1.1/PKG-INFO +68 -0
- effusive-0.1.1/README.md +48 -0
- effusive-0.1.1/pyproject.toml +62 -0
- effusive-0.1.1/pyproject.toml.orig +56 -0
- effusive-0.1.1/src/effusive/__init__.py +1 -0
- effusive-0.1.1/src/effusive/assets/__init__.py +28 -0
- effusive-0.1.1/src/effusive/assets/aperture.svg +1 -0
- effusive-0.1.1/src/effusive/assets/audio-waveform.svg +1 -0
- effusive-0.1.1/src/effusive/assets/circle-check.svg +4 -0
- effusive-0.1.1/src/effusive/assets/circle-x.svg +5 -0
- effusive-0.1.1/src/effusive/assets/cpu.svg +1 -0
- effusive-0.1.1/src/effusive/assets/hard-drive.svg +1 -0
- effusive-0.1.1/src/effusive/assets/layers.svg +1 -0
- effusive-0.1.1/src/effusive/assets/pause.svg +1 -0
- effusive-0.1.1/src/effusive/assets/play.svg +1 -0
- effusive-0.1.1/src/effusive/assets/sliders-horizontal.svg +21 -0
- effusive-0.1.1/src/effusive/assets/square.svg +1 -0
- effusive-0.1.1/src/effusive/assets/triangle-alert.svg +5 -0
- effusive-0.1.1/src/effusive/bids.py +390 -0
- effusive-0.1.1/src/effusive/cli.py +42 -0
- effusive-0.1.1/src/effusive/config.py +534 -0
- effusive-0.1.1/src/effusive/default_config.toml +63 -0
- effusive-0.1.1/src/effusive/matlab_worker.py +424 -0
- effusive-0.1.1/src/effusive/motor_controller.py +356 -0
- effusive-0.1.1/src/effusive/napari/__init__.py +1 -0
- effusive-0.1.1/src/effusive/napari/commands.py +592 -0
- effusive-0.1.1/src/effusive/napari/components.py +94 -0
- effusive-0.1.1/src/effusive/napari/console.py +191 -0
- effusive-0.1.1/src/effusive/napari/controls.py +545 -0
- effusive-0.1.1/src/effusive/napari/crop.py +379 -0
- effusive-0.1.1/src/effusive/napari/debug.py +365 -0
- effusive-0.1.1/src/effusive/napari/panels/__init__.py +31 -0
- effusive-0.1.1/src/effusive/napari/panels/common.py +158 -0
- effusive-0.1.1/src/effusive/napari/panels/data.py +153 -0
- effusive-0.1.1/src/effusive/napari/panels/metadata.py +582 -0
- effusive-0.1.1/src/effusive/napari/panels/processing.py +356 -0
- effusive-0.1.1/src/effusive/napari/panels/sequence.py +337 -0
- effusive-0.1.1/src/effusive/napari/panels/stack.py +261 -0
- effusive-0.1.1/src/effusive/napari/panels/system.py +497 -0
- effusive-0.1.1/src/effusive/napari/plugin.py +70 -0
- effusive-0.1.1/src/effusive/napari/runtime.py +462 -0
- effusive-0.1.1/src/effusive/napari/stack.py +1000 -0
- effusive-0.1.1/src/effusive/napari/theme.py +460 -0
- effusive-0.1.1/src/effusive/napari/udp_control.py +511 -0
- effusive-0.1.1/src/effusive/napari/widget.py +797 -0
- effusive-0.1.1/src/effusive/napari/worker.py +466 -0
- effusive-0.1.1/src/effusive/napari.yaml +10 -0
- effusive-0.1.1/src/effusive/shared_memory.py +584 -0
effusive-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: effusive
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Effusive: fUSI acquisition system with napari viewer.
|
|
5
|
+
Classifier: Development Status :: 3 - Alpha
|
|
6
|
+
Classifier: Framework :: napari
|
|
7
|
+
Classifier: Intended Audience :: Science/Research
|
|
8
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
9
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
|
|
12
|
+
Requires-Dist: matlabengine>=24.1,<24.2
|
|
13
|
+
Requires-Dist: napari[all]>=0.9.0
|
|
14
|
+
Requires-Dist: numpy>=1.24
|
|
15
|
+
Requires-Dist: platformdirs>=3.0
|
|
16
|
+
Requires-Dist: pyserial>=3.5
|
|
17
|
+
Requires-Dist: tomli-w>=1.0
|
|
18
|
+
Requires-Python: >=3.11, <3.12
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
[](https://pypi.org/project/effusive/)
|
|
22
|
+
[](https://pypi.org/project/effusive/)
|
|
23
|
+
[](https://github.com/sdiebolt/effusive/blob/main/LICENSE)
|
|
24
|
+
[](https://doi.org/10.5281/zenodo.22555686)
|
|
25
|
+
[](https://discord.gg/mZd87tgmy2)
|
|
26
|
+
|
|
27
|
+
# Effusive
|
|
28
|
+
|
|
29
|
+
> [!WARNING]
|
|
30
|
+
> **Alpha status** — Effusive is in alpha and under active development. Use it at your
|
|
31
|
+
> own risk, especially on acquisition hardware. If you want to start experimenting with
|
|
32
|
+
> it, please contact the maintainers.
|
|
33
|
+
|
|
34
|
+
Effusive is an open-source acquisition and control application for functional ultrasound
|
|
35
|
+
imaging (fUSI) on Verasonics Vantage systems. It provides a napari-based interface for
|
|
36
|
+
configuring acquisitions, visualizing B-mode and power Doppler images in real time, and
|
|
37
|
+
recording fUSI data. Effusive builds on
|
|
38
|
+
[EchoFrame](https://github.com/BrainEchoLab/EchoFrame) for high-performance ultrasound
|
|
39
|
+
beamforming and acquisition.
|
|
40
|
+
|
|
41
|
+
## Requirements
|
|
42
|
+
|
|
43
|
+
- Linux or Windows.
|
|
44
|
+
- Python 3.11 with `uv`.
|
|
45
|
+
- MATLAB R2024a.
|
|
46
|
+
- Verasonics Vantage software 5.0.0.
|
|
47
|
+
- [EchoFrame](https://github.com/BrainEchoLab/EchoFrame)
|
|
48
|
+
- Optional: a sender for [mpep](https://github.com/cortex-lab/mpep)-compatible UDP experiment-control messages.
|
|
49
|
+
|
|
50
|
+
## Run
|
|
51
|
+
|
|
52
|
+
On Windows, start terminal as administrator.
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
uv run effusive
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Then click the play button in the napari sidebar. The UI stays in `Starting...`
|
|
59
|
+
until the first frame arrives from MATLAB.
|
|
60
|
+
|
|
61
|
+
## Configuration
|
|
62
|
+
|
|
63
|
+
Defaults ship in `src/effusive/default_config.toml`. User settings persist in
|
|
64
|
+
`platformdirs.user_config_dir("effusive")` as `current_config.toml`
|
|
65
|
+
(for example `~/.config/effusive/current_config.toml` on Linux).
|
|
66
|
+
|
|
67
|
+
Config covers probe defaults, acquisition settings, storage path,
|
|
68
|
+
simulate mode, z-stack defaults, BIDS-like metadata, and the last crop ROI.
|
effusive-0.1.1/README.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[](https://pypi.org/project/effusive/)
|
|
2
|
+
[](https://pypi.org/project/effusive/)
|
|
3
|
+
[](https://github.com/sdiebolt/effusive/blob/main/LICENSE)
|
|
4
|
+
[](https://doi.org/10.5281/zenodo.22555686)
|
|
5
|
+
[](https://discord.gg/mZd87tgmy2)
|
|
6
|
+
|
|
7
|
+
# Effusive
|
|
8
|
+
|
|
9
|
+
> [!WARNING]
|
|
10
|
+
> **Alpha status** — Effusive is in alpha and under active development. Use it at your
|
|
11
|
+
> own risk, especially on acquisition hardware. If you want to start experimenting with
|
|
12
|
+
> it, please contact the maintainers.
|
|
13
|
+
|
|
14
|
+
Effusive is an open-source acquisition and control application for functional ultrasound
|
|
15
|
+
imaging (fUSI) on Verasonics Vantage systems. It provides a napari-based interface for
|
|
16
|
+
configuring acquisitions, visualizing B-mode and power Doppler images in real time, and
|
|
17
|
+
recording fUSI data. Effusive builds on
|
|
18
|
+
[EchoFrame](https://github.com/BrainEchoLab/EchoFrame) for high-performance ultrasound
|
|
19
|
+
beamforming and acquisition.
|
|
20
|
+
|
|
21
|
+
## Requirements
|
|
22
|
+
|
|
23
|
+
- Linux or Windows.
|
|
24
|
+
- Python 3.11 with `uv`.
|
|
25
|
+
- MATLAB R2024a.
|
|
26
|
+
- Verasonics Vantage software 5.0.0.
|
|
27
|
+
- [EchoFrame](https://github.com/BrainEchoLab/EchoFrame)
|
|
28
|
+
- Optional: a sender for [mpep](https://github.com/cortex-lab/mpep)-compatible UDP experiment-control messages.
|
|
29
|
+
|
|
30
|
+
## Run
|
|
31
|
+
|
|
32
|
+
On Windows, start terminal as administrator.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
uv run effusive
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Then click the play button in the napari sidebar. The UI stays in `Starting...`
|
|
39
|
+
until the first frame arrives from MATLAB.
|
|
40
|
+
|
|
41
|
+
## Configuration
|
|
42
|
+
|
|
43
|
+
Defaults ship in `src/effusive/default_config.toml`. User settings persist in
|
|
44
|
+
`platformdirs.user_config_dir("effusive")` as `current_config.toml`
|
|
45
|
+
(for example `~/.config/effusive/current_config.toml` on Linux).
|
|
46
|
+
|
|
47
|
+
Config covers probe defaults, acquisition settings, storage path,
|
|
48
|
+
simulate mode, z-stack defaults, BIDS-like metadata, and the last crop ROI.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "effusive"
|
|
3
|
+
version = "0.1.1"
|
|
4
|
+
description = "Effusive: fUSI acquisition system with napari viewer."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11,<3.12"
|
|
7
|
+
classifiers = [
|
|
8
|
+
"Development Status :: 3 - Alpha",
|
|
9
|
+
"Framework :: napari",
|
|
10
|
+
"Intended Audience :: Science/Research",
|
|
11
|
+
"Operating System :: Microsoft :: Windows",
|
|
12
|
+
"Operating System :: POSIX :: Linux",
|
|
13
|
+
"Programming Language :: Python :: 3.11",
|
|
14
|
+
"Topic :: Scientific/Engineering :: Medical Science Apps.",
|
|
15
|
+
]
|
|
16
|
+
dependencies = [
|
|
17
|
+
"matlabengine>=24.1,<24.2",
|
|
18
|
+
"napari[all]>=0.9.0",
|
|
19
|
+
"numpy>=1.24",
|
|
20
|
+
"platformdirs>=3.0",
|
|
21
|
+
"pyserial>=3.5",
|
|
22
|
+
"tomli-w>=1.0",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.scripts]
|
|
26
|
+
effusive = "effusive.cli:main"
|
|
27
|
+
|
|
28
|
+
[project.entry-points."napari.manifest"]
|
|
29
|
+
effusive = "effusive:napari.yaml"
|
|
30
|
+
|
|
31
|
+
[build-system]
|
|
32
|
+
requires = ["uv_build>=0.9.5,<0.10.0"]
|
|
33
|
+
build-backend = "uv_build"
|
|
34
|
+
|
|
35
|
+
[dependency-groups]
|
|
36
|
+
dev = [
|
|
37
|
+
"mkdocstrings[python]>=1.0.3",
|
|
38
|
+
"mkdocstrings-matlab>=2.1.0",
|
|
39
|
+
"prek>=0.3.6",
|
|
40
|
+
"ty>=0.0.61",
|
|
41
|
+
"zensical>=0.0.66",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[tool.numpydoc_validation]
|
|
45
|
+
exclude = [
|
|
46
|
+
'\.__init__$',
|
|
47
|
+
'\._.*$',
|
|
48
|
+
]
|
|
49
|
+
checks = [
|
|
50
|
+
"all",
|
|
51
|
+
"GL01",
|
|
52
|
+
"ES01",
|
|
53
|
+
"SA01",
|
|
54
|
+
"EX01",
|
|
55
|
+
]
|
|
56
|
+
numpydoc_validation_checks = [
|
|
57
|
+
"all",
|
|
58
|
+
"GL01",
|
|
59
|
+
"ES01",
|
|
60
|
+
"SA01",
|
|
61
|
+
"EX01",
|
|
62
|
+
]
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "effusive"
|
|
3
|
+
version = "0.1.1"
|
|
4
|
+
description = "Effusive: fUSI acquisition system with napari viewer."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11,<3.12"
|
|
7
|
+
classifiers = [
|
|
8
|
+
"Development Status :: 3 - Alpha",
|
|
9
|
+
"Framework :: napari",
|
|
10
|
+
"Intended Audience :: Science/Research",
|
|
11
|
+
"Operating System :: Microsoft :: Windows",
|
|
12
|
+
"Operating System :: POSIX :: Linux",
|
|
13
|
+
"Programming Language :: Python :: 3.11",
|
|
14
|
+
"Topic :: Scientific/Engineering :: Medical Science Apps.",
|
|
15
|
+
]
|
|
16
|
+
dependencies = [
|
|
17
|
+
"matlabengine>=24.1,<24.2",
|
|
18
|
+
"napari[all]>=0.9.0",
|
|
19
|
+
"numpy>=1.24",
|
|
20
|
+
"platformdirs>=3.0",
|
|
21
|
+
"pyserial>=3.5",
|
|
22
|
+
"tomli-w>=1.0",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.scripts]
|
|
26
|
+
effusive = "effusive.cli:main"
|
|
27
|
+
|
|
28
|
+
[project.entry-points."napari.manifest"]
|
|
29
|
+
effusive = "effusive:napari.yaml"
|
|
30
|
+
|
|
31
|
+
[build-system]
|
|
32
|
+
requires = ["uv_build>=0.9.5,<0.10.0"]
|
|
33
|
+
build-backend = "uv_build"
|
|
34
|
+
|
|
35
|
+
[dependency-groups]
|
|
36
|
+
dev = [
|
|
37
|
+
"mkdocstrings[python]>=1.0.3",
|
|
38
|
+
"mkdocstrings-matlab>=2.1.0",
|
|
39
|
+
"prek>=0.3.6",
|
|
40
|
+
"ty>=0.0.61",
|
|
41
|
+
"zensical>=0.0.66",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[tool.numpydoc_validation]
|
|
45
|
+
exclude = [
|
|
46
|
+
'\.__init__$',
|
|
47
|
+
'\._.*$',
|
|
48
|
+
]
|
|
49
|
+
checks = [
|
|
50
|
+
"all", # Report on all checks, except the below
|
|
51
|
+
"GL01",
|
|
52
|
+
"ES01",
|
|
53
|
+
"SA01",
|
|
54
|
+
"EX01",
|
|
55
|
+
]
|
|
56
|
+
numpydoc_validation_checks = ["all", "GL01", "ES01", "SA01", "EX01"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Effusive python package for the napari viewer."""
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Effusive assets module.
|
|
2
|
+
|
|
3
|
+
Contains SVG icons and other static assets used by the application.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def load_svg(name: str) -> str:
|
|
10
|
+
"""Load an SVG icon by name.
|
|
11
|
+
|
|
12
|
+
Parameters
|
|
13
|
+
----------
|
|
14
|
+
name : str
|
|
15
|
+
Name of the SVG file (without .svg extension).
|
|
16
|
+
|
|
17
|
+
Returns
|
|
18
|
+
-------
|
|
19
|
+
str
|
|
20
|
+
Contents of the SVG file.
|
|
21
|
+
|
|
22
|
+
Raises
|
|
23
|
+
------
|
|
24
|
+
FileNotFoundError
|
|
25
|
+
If the SVG file does not exist.
|
|
26
|
+
"""
|
|
27
|
+
svg_path = Path(__file__).parent / f"{name}.svg"
|
|
28
|
+
return svg_path.read_text()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="m14.31 8 5.74 9.94"/><path d="M9.69 8h11.48"/><path d="m7.38 12 5.74-9.94"/><path d="M9.69 16 3.95 6.06"/><path d="M14.31 16H2.83"/><path d="m16.62 12-5.74 9.94"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-audio-waveform-icon lucide-audio-waveform"><path d="M2 13a2 2 0 0 0 2-2V7a2 2 0 0 1 4 0v13a2 2 0 0 0 4 0V4a2 2 0 0 1 4 0v13a2 2 0 0 0 4 0v-4a2 2 0 0 1 2-2"/></svg>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
2
|
+
<circle cx="12" cy="12" r="10"/>
|
|
3
|
+
<path d="m15 9-6 6"/>
|
|
4
|
+
<path d="m9 9 6 6"/>
|
|
5
|
+
</svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-cpu-icon lucide-cpu"><path d="M12 20v2"/><path d="M12 2v2"/><path d="M17 20v2"/><path d="M17 2v2"/><path d="M2 12h2"/><path d="M2 17h2"/><path d="M2 7h2"/><path d="M20 12h2"/><path d="M20 17h2"/><path d="M20 7h2"/><path d="M7 20v2"/><path d="M7 2v2"/><rect x="4" y="4" width="16" height="16" rx="2"/><rect x="8" y="8" width="8" height="8" rx="1"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 16h.01"/><path d="M2.212 11.577a2 2 0 0 0-.212.896V18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-5.527a2 2 0 0 0-.212-.896L18.55 5.11A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"/><path d="M21.946 12.013H2.054"/><path d="M6 16h.01"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83z"/><path d="M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12"/><path d="M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="14" y="4" width="4" height="16" rx="1"/><rect x="6" y="4" width="4" height="16" rx="1"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="6 3 20 12 6 21 6 3"/></svg>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<svg
|
|
2
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
3
|
+
width="24"
|
|
4
|
+
height="24"
|
|
5
|
+
viewBox="0 0 24 24"
|
|
6
|
+
fill="none"
|
|
7
|
+
stroke="currentColor"
|
|
8
|
+
stroke-width="2"
|
|
9
|
+
stroke-linecap="round"
|
|
10
|
+
stroke-linejoin="round"
|
|
11
|
+
>
|
|
12
|
+
<path d="M10 5H3" />
|
|
13
|
+
<path d="M12 19H3" />
|
|
14
|
+
<path d="M14 3v4" />
|
|
15
|
+
<path d="M16 17v4" />
|
|
16
|
+
<path d="M21 12h-9" />
|
|
17
|
+
<path d="M21 19h-5" />
|
|
18
|
+
<path d="M21 5h-7" />
|
|
19
|
+
<path d="M8 10v4" />
|
|
20
|
+
<path d="M8 12H3" />
|
|
21
|
+
</svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2"/></svg>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
2
|
+
<path d="m21.73 18-8-14a2 2 0 0 0-3.46 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/>
|
|
3
|
+
<path d="M12 9v4"/>
|
|
4
|
+
<path d="M12 17h.01"/>
|
|
5
|
+
</svg>
|