26 Commits

Author SHA1 Message Date
5860939f65 Update release version
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
2024-03-27 19:38:29 -07:00
ecdf74a483 Fill chip media propotationally to confidence value (#156)
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
Reviewed-on: #156
2024-03-27 19:20:10 -07:00
e8778f3e24 Improve the help page info and formatting (#155)
Closes: #146

Signed-off-by: Justin Georgi <justin.georgi@gmail.com>

Reviewed-on: #155
2024-03-27 16:33:37 -07:00
b7119a240e Restore material icon set (#154)
Closes: #153

Fixes a regression from the culling of extra icons and build files. Restores the basic material icon set which is used by the buttons in the f7 text editor.

Signed-off-by: Justin Georgi <justin.georgi@gmail.com>

Reviewed-on: #154
2024-03-27 08:58:35 -07:00
e311b34e00 Add alpha version warning to home screen
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
2024-03-27 08:35:05 -07:00
596c28a880 Move assets to vite public folder (#152)
Closes: #151

Models and samples are now properly structured in the vite public folder so they are properly automatically incorporated into the the builds and work with both builds and dev tests.

Reviewed-on: #152
2024-03-26 20:23:33 -07:00
42faae18eb Fix real-time target location on camera stream
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
2024-03-26 12:58:13 -07:00
a3f63749b1 Add fullscreen setting to detect pages (#149)
Closes: #147

Signed-off-by: Justin Georgi <justin.georgi@gmail.com>

Reviewed-on: #149
2024-03-26 08:33:56 -07:00
90d23a70b3 Improve model location determination (#148)
Vite and cordova properties now automatically determine the path to the models.

Reviewed-on: #148
2024-03-25 21:03:56 -07:00
53869aa268 Add help page (#144)
Closes: #140

Signed-off-by: Justin Georgi <justin.georgi@gmail.com>

Reviewed-on: #144
2024-03-24 20:23:23 -07:00
e88feb17b6 Add repo link to About page
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
2024-03-24 19:41:08 -07:00
22cbb157ee Fix coco data class list
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
2024-03-24 09:36:53 -07:00
79316bb83b Add real-time detection to camera stream (#143)
Closes: #30

When the camera is being used to find an image to capture, the region mini model now runs in real time to give an estimate of where there are identifiable structures.

Reviewed-on: #143
2024-03-24 08:51:08 -07:00
f09180875a Add mini models to tech specs and improve styling (#142)
Closes: #133

Signed-off-by: Justin Georgi <justin.georgi@gmail.com>

Reviewed-on: #142
2024-03-24 08:06:28 -07:00
c16e44016b Fix Android sample image fetching
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
2024-03-14 16:35:56 -07:00
c6f17d7187 Add custom navigation guard to settings page (#137)
Closes: #131

There is probably a better ("more native") way to do this, but I spent hours trying to figure it out and in the end it was way easier to circumvent the built-in f7 router events and just use my own custom back button. D.U.M. - dumb, but not my fault (I think...).

Signed-off-by: Justin Georgi <justin.georgi@gmail.com>

Reviewed-on: #137
2024-03-14 16:03:03 -07:00
f39c811e40 Add sample image option (#136)
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
Reviewed-on: #136
2024-03-13 17:19:44 -07:00
4756f49423 Cleanup unused plugins and fonts
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
2024-03-13 10:36:24 -07:00
6e35fcb141 Add alvinn mini settings
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
2024-03-11 21:10:06 -07:00
76a0cba2af Add dynamic camera size request
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
2024-03-11 20:22:28 -07:00
a665fb591d Fix empty detection bug (#135)
Closes: #134

Fixes regression in post-processing from #129.

Signed-off-by: Justin Georgi <justin.georgi@gmail.com>

Reviewed-on: #135
2024-03-11 09:47:26 -07:00
2cb128fd51 Auto-hide advanced settings
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
2024-03-10 21:20:56 -07:00
9d64cbd2d6 Set local detect as default
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
2024-03-10 20:35:37 -07:00
6e867e00d1 Reduce timeout wait beofre setData
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
2024-03-10 20:27:34 -07:00
ea43aa3306 Add specs page and link from version in panel (#130)
Closes: #126

Signed-off-by: Justin Georgi <justin.georgi@gmail.com>

Reviewed-on: #130
2024-03-10 19:23:31 -07:00
76b2fe5b0c Optimize detection steps (#129)
Closes: #128
Reviewed-on: #129
2024-03-09 21:06:50 -07:00
82 changed files with 996 additions and 615 deletions

View File

@@ -17,6 +17,7 @@ const build = async () => {
format: 'iife', format: 'iife',
name: 'MyApp', name: 'MyApp',
sourcemap: false, sourcemap: false,
inlineDynamicImports :true
}); });
// Remove old chunk files // Remove old chunk files

View File

@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<widget id="edu.midwestern.alvinn" version="0.4.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0" xmlns:android="http://schemas.android.com/apk/res/android"> <widget id="edu.midwestern.alvinn" version="0.5.0-rc" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0" xmlns:android="http://schemas.android.com/apk/res/android">
<name>ALVINN</name> <name>ALVINN</name>
<description>Anatomy Lab Visual Identification Neural Network.</description> <description>Anatomy Lab Visual Identification Neural Network.</description>
<author email="jgeorg@midwestern.edu" href="https://midwestern.edu"> <author email="jgeorg@midwestern.edu" href="https://midwestern.edu">

View File

@@ -1,20 +1,18 @@
{ {
"name": "edu.midwestern.alvinn", "name": "edu.midwestern.alvinn",
"version": "0.3.0", "version": "0.4.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "edu.midwestern.alvinn", "name": "edu.midwestern.alvinn",
"version": "0.3.0", "version": "0.4.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"devDependencies": { "devDependencies": {
"cordova-android": "^12.0.1", "cordova-android": "^12.0.1",
"cordova-browser": "^7.0.0", "cordova-browser": "^7.0.0",
"cordova-ios": "^7.0.1", "cordova-ios": "^7.0.1",
"cordova-plugin-camera": "^7.0.0", "cordova-plugin-camera": "^7.0.0"
"cordova-plugin-keyboard": "^1.2.0",
"cordova-plugin-statusbar": "^4.0.0"
} }
}, },
"node_modules/@netflix/nerror": { "node_modules/@netflix/nerror": {
@@ -542,39 +540,6 @@
} }
} }
}, },
"node_modules/cordova-plugin-keyboard": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/cordova-plugin-keyboard/-/cordova-plugin-keyboard-1.2.0.tgz",
"integrity": "sha512-Zng4SgDQQ2BCqeDOvrsgNlM9tcjnxmJoh0Qhex0KltMsoR0g/ONbMTpaVvI8EhNKVO8HJPnhFxxzHxrCPLQ7sQ==",
"dev": true,
"engines": [
{
"name": "cordova",
"version": ">=3.2.0"
}
]
},
"node_modules/cordova-plugin-statusbar": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/cordova-plugin-statusbar/-/cordova-plugin-statusbar-4.0.0.tgz",
"integrity": "sha512-NTVdQhj9msydnarCH8ijOhASx+6fO16hg17AilAjfzWRvcBoF+4kKkPhTkZM7D2bOBePhfHEaMhEnxnA3M4Dlg==",
"dev": true,
"engines": {
"cordovaDependencies": {
"0.1.0": {
"cordova": ">=3.0.0"
},
"4.0.0": {
"cordova": ">=3.0.0",
"cordova-android": ">=10.0.0",
"cordova-ios": ">=6.0.0"
},
"5.0.0": {
"cordova": ">100"
}
}
}
},
"node_modules/cordova-serve": { "node_modules/cordova-serve": {
"version": "4.0.1", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/cordova-serve/-/cordova-serve-4.0.1.tgz", "resolved": "https://registry.npmjs.org/cordova-serve/-/cordova-serve-4.0.1.tgz",
@@ -2652,18 +2617,6 @@
"integrity": "sha512-OVQWZTBb18Y6e5c+bbXt3E4Z1yGnYqaywh2h5vVr/+nxMcdMIE+lm527bRK5vLN/RUqhGYP/Z+5n+O7Fk7fVNw==", "integrity": "sha512-OVQWZTBb18Y6e5c+bbXt3E4Z1yGnYqaywh2h5vVr/+nxMcdMIE+lm527bRK5vLN/RUqhGYP/Z+5n+O7Fk7fVNw==",
"dev": true "dev": true
}, },
"cordova-plugin-keyboard": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/cordova-plugin-keyboard/-/cordova-plugin-keyboard-1.2.0.tgz",
"integrity": "sha512-Zng4SgDQQ2BCqeDOvrsgNlM9tcjnxmJoh0Qhex0KltMsoR0g/ONbMTpaVvI8EhNKVO8HJPnhFxxzHxrCPLQ7sQ==",
"dev": true
},
"cordova-plugin-statusbar": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/cordova-plugin-statusbar/-/cordova-plugin-statusbar-4.0.0.tgz",
"integrity": "sha512-NTVdQhj9msydnarCH8ijOhASx+6fO16hg17AilAjfzWRvcBoF+4kKkPhTkZM7D2bOBePhfHEaMhEnxnA3M4Dlg==",
"dev": true
},
"cordova-serve": { "cordova-serve": {
"version": "4.0.1", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/cordova-serve/-/cordova-serve-4.0.1.tgz", "resolved": "https://registry.npmjs.org/cordova-serve/-/cordova-serve-4.0.1.tgz",

View File

@@ -1,7 +1,7 @@
{ {
"name": "edu.midwestern.alvinn", "name": "edu.midwestern.alvinn",
"displayName": "ALVINN", "displayName": "ALVINN",
"version": "0.4.0", "version": "0.5.0-rc",
"description": "Anatomy Lab Visual Identification Neural Network.", "description": "Anatomy Lab Visual Identification Neural Network.",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
@@ -16,14 +16,10 @@
"cordova-android": "^12.0.1", "cordova-android": "^12.0.1",
"cordova-browser": "^7.0.0", "cordova-browser": "^7.0.0",
"cordova-ios": "^7.0.1", "cordova-ios": "^7.0.1",
"cordova-plugin-camera": "^7.0.0", "cordova-plugin-camera": "^7.0.0"
"cordova-plugin-keyboard": "^1.2.0",
"cordova-plugin-statusbar": "^4.0.0"
}, },
"cordova": { "cordova": {
"plugins": { "plugins": {
"cordova-plugin-statusbar": {},
"cordova-plugin-keyboard": {},
"cordova-plugin-camera": { "cordova-plugin-camera": {
"ANDROIDX_CORE_VERSION": "1.6.+" "ANDROIDX_CORE_VERSION": "1.6.+"
} }

15
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "alvinn", "name": "alvinn",
"version": "0.3.0", "version": "0.4.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "alvinn", "name": "alvinn",
"version": "0.3.0", "version": "0.4.0",
"hasInstallScript": true, "hasInstallScript": true,
"license": "UNLICENSED", "license": "UNLICENSED",
"dependencies": { "dependencies": {
@@ -15,7 +15,6 @@
"framework7": "^8.3.0", "framework7": "^8.3.0",
"framework7-icons": "^5.0.5", "framework7-icons": "^5.0.5",
"framework7-vue": "^8.3.0", "framework7-vue": "^8.3.0",
"material-icons": "^1.13.12",
"skeleton-elements": "^4.0.1", "skeleton-elements": "^4.0.1",
"swiper": "^11.0.3", "swiper": "^11.0.3",
"vue": "^3.3.8" "vue": "^3.3.8"
@@ -6644,11 +6643,6 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/material-icons": {
"version": "1.13.12",
"resolved": "https://registry.npmjs.org/material-icons/-/material-icons-1.13.12.tgz",
"integrity": "sha512-/2YoaB79IjUK2B2JB+vIXXYGtBfHb/XG66LvoKVM5ykHW7yfrV5SP6d7KLX6iijY6/G9GqwgtPQ/sbhFnOURVA=="
},
"node_modules/meow": { "node_modules/meow": {
"version": "12.1.1", "version": "12.1.1",
"resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz",
@@ -14206,11 +14200,6 @@
"integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==",
"dev": true "dev": true
}, },
"material-icons": {
"version": "1.13.12",
"resolved": "https://registry.npmjs.org/material-icons/-/material-icons-1.13.12.tgz",
"integrity": "sha512-/2YoaB79IjUK2B2JB+vIXXYGtBfHb/XG66LvoKVM5ykHW7yfrV5SP6d7KLX6iijY6/G9GqwgtPQ/sbhFnOURVA=="
},
"meow": { "meow": {
"version": "12.1.1", "version": "12.1.1",
"resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz",

View File

@@ -1,7 +1,7 @@
{ {
"name": "alvinn", "name": "alvinn",
"private": true, "private": true,
"version": "0.4.0", "version": "0.5.0-rc",
"description": "ALVINN", "description": "ALVINN",
"repository": "", "repository": "",
"license": "UNLICENSED", "license": "UNLICENSED",
@@ -14,7 +14,7 @@
"cordova-ios": "cross-env TARGET=cordova cross-env NODE_ENV=production vite build && node ./build/build-cordova.js && cd cordova && cordova run ios", "cordova-ios": "cross-env TARGET=cordova cross-env NODE_ENV=production vite build && node ./build/build-cordova.js && cd cordova && cordova run ios",
"build-cordova-android": "cross-env TARGET=cordova cross-env NODE_ENV=production vite build && node ./build/build-cordova.js && cd cordova && cordova build android", "build-cordova-android": "cross-env TARGET=cordova cross-env NODE_ENV=production vite build && node ./build/build-cordova.js && cd cordova && cordova build android",
"cordova-android": "cross-env TARGET=cordova cross-env NODE_ENV=production vite build && node ./build/build-cordova.js && cd cordova && cordova run android", "cordova-android": "cross-env TARGET=cordova cross-env NODE_ENV=production vite build && node ./build/build-cordova.js && cd cordova && cordova run android",
"postinstall": "cpy --flat ./node_modules/framework7-icons/fonts/*.* ./src/fonts/ && cpy --flat ./node_modules/material-icons/iconfont/*.* ./src/fonts/" "postinstall": "cpy --flat ./node_modules/framework7-icons/fonts/*.* ./src/fonts/"
}, },
"browserslist": [ "browserslist": [
"IOS >= 15", "IOS >= 15",
@@ -28,7 +28,6 @@
"framework7": "^8.3.0", "framework7": "^8.3.0",
"framework7-icons": "^5.0.5", "framework7-icons": "^5.0.5",
"framework7-vue": "^8.3.0", "framework7-vue": "^8.3.0",
"material-icons": "^1.13.12",
"skeleton-elements": "^4.0.1", "skeleton-elements": "^4.0.1",
"swiper": "^11.0.3", "swiper": "^11.0.3",
"vue": "^3.3.8" "vue": "^3.3.8"

View File

@@ -0,0 +1,11 @@
{
"version": "0.0.0-n1",
"region": "Coco",
"size": 640,
"epochs": 1000,
"name": "coco128 test",
"yolo-version": "8.1.20 docker",
"date": "2024-03-12",
"export": "coco128.yaml"
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,92 @@
description: Ultralytics best model trained on /usr/src/ultralytics/ultralytics/cfg/datasets/coco128.yaml
author: Ultralytics
license: AGPL-3.0 https://ultralytics.com/license
date: '2024-03-12T16:25:00.089873'
version: 8.1.20
stride: 32
task: detect
batch: 1
imgsz:
- 640
- 640
names:
0: person
1: bicycle
2: car
3: motorcycle
4: airplane
5: bus
6: train
7: truck
8: boat
9: traffic light
10: fire hydrant
11: stop sign
12: parking meter
13: bench
14: bird
15: cat
16: dog
17: horse
18: sheep
19: cow
20: elephant
21: bear
22: zebra
23: giraffe
24: backpack
25: umbrella
26: handbag
27: tie
28: suitcase
29: frisbee
30: skis
31: snowboard
32: sports ball
33: kite
34: baseball bat
35: baseball glove
36: skateboard
37: surfboard
38: tennis racket
39: bottle
40: wine glass
41: cup
42: fork
43: knife
44: spoon
45: bowl
46: banana
47: apple
48: sandwich
49: orange
50: broccoli
51: carrot
52: hot dog
53: pizza
54: donut
55: cake
56: chair
57: couch
58: potted plant
59: bed
60: dining table
61: toilet
62: tv
63: laptop
64: mouse
65: remote
66: keyboard
67: cell phone
68: microwave
69: oven
70: toaster
71: sink
72: refrigerator
73: book
74: clock
75: vase
76: scissors
77: teddy bear
78: hair drier
79: toothbrush

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,82 @@
[
"person",
"bicycle",
"car",
"motorcycle",
"airplane",
"bus",
"train",
"truck",
"boat",
"traffic light",
"fire hydrant",
"stop sign",
"parking meter",
"bench",
"bird",
"cat",
"dog",
"horse",
"sheep",
"cow",
"elephant",
"bear",
"zebra",
"giraffe",
"backpack",
"umbrella",
"handbag",
"tie",
"suitcase",
"frisbee",
"skis",
"snowboard",
"sports ball",
"kite",
"baseball bat",
"baseball glove",
"skateboard",
"surfboard",
"tennis racket",
"bottle",
"wine glass",
"cup",
"fork",
"knife",
"spoon",
"bowl",
"banana",
"apple",
"sandwich",
"orange",
"broccoli",
"carrot",
"hot dog",
"pizza",
"donut",
"cake",
"chair",
"couch",
"potted plant",
"bed",
"dining table",
"toilet",
"tv",
"laptop",
"mouse",
"remote",
"keyboard",
"cell phone",
"microwave",
"oven",
"toaster",
"sink",
"refrigerator",
"book",
"clock",
"vase",
"scissors",
"teddy bear",
"hair drier",
"toothbrush"
]

View File

@@ -0,0 +1,11 @@
{
"version": "0.0.0-n1",
"region": "Coco",
"size": 640,
"epochs": 1000,
"name": "coco128 test",
"yolo-version": "8.1.20 docker",
"date": "2024-03-12",
"export": "coco128.yaml"
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,92 @@
description: Ultralytics best model trained on /usr/src/ultralytics/ultralytics/cfg/datasets/coco128.yaml
author: Ultralytics
license: AGPL-3.0 https://ultralytics.com/license
date: '2024-03-12T16:25:00.089873'
version: 8.1.20
stride: 32
task: detect
batch: 1
imgsz:
- 640
- 640
names:
0: person
1: bicycle
2: car
3: motorcycle
4: airplane
5: bus
6: train
7: truck
8: boat
9: traffic light
10: fire hydrant
11: stop sign
12: parking meter
13: bench
14: bird
15: cat
16: dog
17: horse
18: sheep
19: cow
20: elephant
21: bear
22: zebra
23: giraffe
24: backpack
25: umbrella
26: handbag
27: tie
28: suitcase
29: frisbee
30: skis
31: snowboard
32: sports ball
33: kite
34: baseball bat
35: baseball glove
36: skateboard
37: surfboard
38: tennis racket
39: bottle
40: wine glass
41: cup
42: fork
43: knife
44: spoon
45: bowl
46: banana
47: apple
48: sandwich
49: orange
50: broccoli
51: carrot
52: hot dog
53: pizza
54: donut
55: cake
56: chair
57: couch
58: potted plant
59: bed
60: dining table
61: toilet
62: tv
63: laptop
64: mouse
65: remote
66: keyboard
67: cell phone
68: microwave
69: oven
70: toaster
71: sink
72: refrigerator
73: book
74: clock
75: vase
76: scissors
77: teddy bear
78: hair drier
79: toothbrush

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,10 @@
{
"version": "0.1.0-n4",
"region": "Thorax",
"size": 640,
"epochs": 1000,
"name": "nano4",
"yolo-version": "8.1.20 docker",
"date": "2024-03-08",
"export": "0.1.0-th"
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,53 @@
description: Ultralytics best model trained on /data/ALVINN/Thorax/Thorax 0.1.0/thorax.yaml
author: Ultralytics
license: AGPL-3.0 https://ultralytics.com/license
date: '2024-03-08T20:14:34.118186'
version: 8.1.20
stride: 32
task: detect
batch: 1
imgsz:
- 640
- 640
names:
0: Abdominal diaphragm
1: Aorta
2: Azygous vein
3: Brachiocephalic trunk
4: Caudal vena cava
5: Cranial vena cava
6: Esophagus
7: External abdominal oblique
8: Iliocostalis
9: Latissimus dorsi
10: Left atrium
11: Left auricle
12: Left lung
13: Left subclavian artery
14: Left ventricle
15: Longissimus
16: Pectoralis profundus
17: Pectoralis superficialis
18: Pericardium
19: Phrenic nerve
20: Primary bronchus
21: Pulmonary artery
22: Pulmonary trunk
23: Pulmonary vein
24: Rectus abdominis
25: Rectus thoracis
26: Recurrent laryngeal nerve
27: Rhomboideus
28: Right atrium
29: Right auricle
30: Right lung
31: Right ventricle
32: Scalenus
33: Serratus dorsalis caudalis
34: Serratus dorsalis cranialis
35: Serratus ventralis
36: Spinalis
37: Sympathetic chain
38: Trachea
39: Trapezius
40: Vagus nerve

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,10 @@
{
"version": "0.1.0-s1",
"region": "Thorax",
"size": 640,
"epochs": 1000,
"name": "small1",
"yolo-version": "8.1.20 docker",
"date": "2024-03-07",
"export": "0.1.0-th"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 714 KiB

14
src/assets/target.svg Normal file
View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg version="1.1" viewBox="0 0 10 10" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<filter id="filter1847" x="-.075" y="-.075" width="1.15" height="1.15" color-interpolation-filters="sRGB">
<feGaussianBlur stdDeviation="0.3125"/>
</filter>
<radialGradient id="radialGradient1903" cx="5" cy="5" r="5.75" gradientUnits="userSpaceOnUse">
<stop stop-color="#ff0" offset="0"/>
<stop stop-color="#ff0" stop-opacity="0" offset="1"/>
</radialGradient>
</defs>
<path d="m0 5 3.833274-1.166726 1.166726-3.833274 1.166726 3.833274 3.833274 1.166726-3.833274 1.166726-1.166726 3.833274-1.166726-3.833274z" color="#000000" fill="url(#radialGradient1903)" fill-rule="evenodd" filter="url(#filter1847)" opacity=".63"/>
</svg>

After

Width:  |  Height:  |  Size: 870 B

View File

@@ -7,11 +7,11 @@
<f7-navbar title="ALVINN"></f7-navbar> <f7-navbar title="ALVINN"></f7-navbar>
<f7-list> <f7-list>
<f7-list-item link="/settings/" view=".view-main" panel-close=".panel-left">Settings</f7-list-item> <f7-list-item link="/settings/" view=".view-main" panel-close=".panel-left">Settings</f7-list-item>
<f7-list-item link="/about/" >About ALVINN</f7-list-item> <f7-list-item link="/about/" view=".view-main" panel-close=".panel-left">About ALVINN</f7-list-item>
<f7-list-item link="/contact/" view=".view-main" panel-close=".panel-left">Contact</f7-list-item> <f7-list-item link="/contact/" view=".view-main" panel-close=".panel-left">Contact</f7-list-item>
</f7-list> </f7-list>
<f7-toolbar class="panel-bar" position="bottom"> <f7-toolbar class="panel-bar" position="bottom">
<span>version 0.4.0</span> <f7-link href="/specs/">version {{ alvinnVersion }}</f7-link>
</f7-toolbar> </f7-toolbar>
</f7-page> </f7-page>
</f7-view> </f7-view>
@@ -66,7 +66,8 @@
rememberAgreement: false, rememberAgreement: false,
siteAgreement: false, siteAgreement: false,
dateAgreement: null, dateAgreement: null,
showDisclaimer: true showDisclaimer: true,
alvinnVersion: store().getVersion
} }
}, },
created () { created () {
@@ -85,8 +86,7 @@
} }
var loadServerSettings = localStorage.getItem('serverSettings') var loadServerSettings = localStorage.getItem('serverSettings')
if (!loadServerSettings) { if (!loadServerSettings) {
//Temp default settings for MWU deployment localStorage.setItem('serverSettings','{"use":false,"address":"10.188.0.98","port":"9001","previous":{"10.188.0.98":"9001"}}')
localStorage.setItem('serverSettings','{"use":true,"address":"10.188.0.98","port":"9001","previous":{"10.188.0.98":"9001"}}')
} }
}, },
methods: { methods: {
@@ -132,7 +132,6 @@
} catch { } catch {
var darkTheme = 'auto' var darkTheme = 'auto'
} }
//provide('isAgreed',siteAgreement)
const f7params = { const f7params = {
name: 'ALVINN', // App name name: 'ALVINN', // App name
theme: 'auto', // Automatic theme detection theme: 'auto', // Automatic theme detection

View File

@@ -15,6 +15,7 @@
<path v-else-if="icon == 'abdomen'" d="M120-80v-240q0-50 35-85t85-35h80q50 0 85-35t35-85q0-17-11.5-28.5T400-600q-33 0-56.5-23.5T320-680v-200h80v200q50 0 85 35t35 85q0 83-58.5 141.5T320-360h-80q-17 0-28.5 11.5T200-320v240h-80Zm240 0h-80v-80q0-50 35-85t85-35h160q83 0 141.5-58.5T760-480v-40q0-83-58.5-141.5T560-720q-33 0-56.5-23.5T480-800v-80h80v80q117 0 198.5 81.5T840-520v40q0 117-81.5 198.5T560-200H400q-17 0-28.5 11.5T360-160v80Zm-160 0v-240q0-17 11.5-28.5T240-360h80q83 0 141.5-58.5T520-560q0-50-35-85t-85-35v-200 200q50 0 85 35t35 85q0 83-58.5 141.5T320-360h-80q-17 0-28.5 11.5T200-320v240Z"/> <path v-else-if="icon == 'abdomen'" d="M120-80v-240q0-50 35-85t85-35h80q50 0 85-35t35-85q0-17-11.5-28.5T400-600q-33 0-56.5-23.5T320-680v-200h80v200q50 0 85 35t35 85q0 83-58.5 141.5T320-360h-80q-17 0-28.5 11.5T200-320v240h-80Zm240 0h-80v-80q0-50 35-85t85-35h160q83 0 141.5-58.5T760-480v-40q0-83-58.5-141.5T560-720q-33 0-56.5-23.5T480-800v-80h80v80q117 0 198.5 81.5T840-520v40q0 117-81.5 198.5T560-200H400q-17 0-28.5 11.5T360-160v80Zm-160 0v-240q0-17 11.5-28.5T240-360h80q83 0 141.5-58.5T520-560q0-50-35-85t-85-35v-200 200q50 0 85 35t35 85q0 83-58.5 141.5T320-360h-80q-17 0-28.5 11.5T200-320v240Z"/>
<path v-else-if="icon == 'limbs'" d="M540-440q17 0 28.5-11.5T580-480q0-7-1.5-12.5T574-503q11-4 18.5-14t7.5-23q0-17-11.5-28.5T560-580q-13 0-23 7t-14 19l-146-70q2-4 2.5-8t.5-8q0-17-11.5-28.5T340-680q-17 0-28.5 11.5T300-640q0 6 2 11.5t5 10.5q-11 4-19 14t-8 24q0 17 11.5 28.5T320-540q14 0 24-7.5t14-19.5l146 70-4 17q0 17 11.5 28.5T540-440ZM394-80q-16-47-24-92.5t-10-86q-2-40.5-.5-74.5t4.5-58q-1 0 0 0-22-5-50.5-12.5t-61-20.5Q220-437 186-455.5T119-500l50-70q39 35 81.5 55.5t78.5 32q36 11.5 60 15l24 3.5q18 1 28.5 15t7.5 32l-4.5 33.5q-4.5 33.5-5 83.5t7.5 109q8 59 33 111h-86Zm366 0h-80v-423q0-48-25.5-87T586-649L313-772l49-67 257 117q64 29 102.5 88T760-503v423Zm-280 0q-25-52-33-111t-7.5-109q.5-50 5-83.5L449-417q3-18-7.5-32T413-464l-24-3.5q-24-3.5-60-15t-78.5-32Q208-535 169-570q39 35 81.5 55.5t78.5 32q36 11.5 60 15l24 3.5q18 1 28.5 15t7.5 32l-4.5 33.5q-4.5 33.5-5 83.5t7.5 109q8 59 33 111Z"/> <path v-else-if="icon == 'limbs'" d="M540-440q17 0 28.5-11.5T580-480q0-7-1.5-12.5T574-503q11-4 18.5-14t7.5-23q0-17-11.5-28.5T560-580q-13 0-23 7t-14 19l-146-70q2-4 2.5-8t.5-8q0-17-11.5-28.5T340-680q-17 0-28.5 11.5T300-640q0 6 2 11.5t5 10.5q-11 4-19 14t-8 24q0 17 11.5 28.5T320-540q14 0 24-7.5t14-19.5l146 70-4 17q0 17 11.5 28.5T540-440ZM394-80q-16-47-24-92.5t-10-86q-2-40.5-.5-74.5t4.5-58q-1 0 0 0-22-5-50.5-12.5t-61-20.5Q220-437 186-455.5T119-500l50-70q39 35 81.5 55.5t78.5 32q36 11.5 60 15l24 3.5q18 1 28.5 15t7.5 32l-4.5 33.5q-4.5 33.5-5 83.5t7.5 109q8 59 33 111h-86Zm366 0h-80v-423q0-48-25.5-87T586-649L313-772l49-67 257 117q64 29 102.5 88T760-503v423Zm-280 0q-25-52-33-111t-7.5-109q.5-50 5-83.5L449-417q3-18-7.5-32T413-464l-24-3.5q-24-3.5-60-15t-78.5-32Q208-535 169-570q39 35 81.5 55.5t78.5 32q36 11.5 60 15l24 3.5q18 1 28.5 15t7.5 32l-4.5 33.5q-4.5 33.5-5 83.5t7.5 109q8 59 33 111Z"/>
<path v-else-if="icon == 'head'" d="M194-80v-395h80v315h280v-193l105-105q29-29 45-65t16-77q0-40-16.5-76T659-741l-25-26-127 127H347l-43 43-57-56 67-67h160l160-160 82 82q40 40 62 90.5T800-600q0 57-22 107.5T716-402l-82 82v240H194Zm197-187L183-475q-11-11-17-26t-6-31q0-16 6-30.5t17-25.5l84-85 124 123q28 28 43.5 64.5T450-409q0 40-15 76.5T391-267Z"/> <path v-else-if="icon == 'head'" d="M194-80v-395h80v315h280v-193l105-105q29-29 45-65t16-77q0-40-16.5-76T659-741l-25-26-127 127H347l-43 43-57-56 67-67h160l160-160 82 82q40 40 62 90.5T800-600q0 57-22 107.5T716-402l-82 82v240H194Zm197-187L183-475q-11-11-17-26t-6-31q0-16 6-30.5t17-25.5l84-85 124 123q28 28 43.5 64.5T450-409q0 40-15 76.5T391-267Z"/>
<path v-else-if="icon == 'photo_sample'" d="M240-80q-33 0-56.5-23.5T160-160v-640q0-33 23.5-56.5T240-880h480q33 0 56.5 23.5T800-800v640q0 33-23.5 56.5T720-80H240Zm0-80h480v-640h-80v280l-100-60-100 60v-280H240v640Zm40-80h400L545-420 440-280l-65-87-95 127Zm-40 80v-640 640Zm200-360 100-60 100 60-100-60-100 60Z"/>
</svg> </svg>
</template> </template>
@@ -40,7 +41,8 @@
'thorax', 'thorax',
'abdomen', 'abdomen',
'limbs', 'limbs',
'head' 'head',
'photo_sample'
] ]
return iconList.includes(value) return iconList.includes(value)
} }

View File

@@ -39,7 +39,8 @@
} }
.chip-media { .chip-media {
background-color: var(--chip-media-background) !important; /*background-color: var(--chip-media-background) !important;*/
background: var(--chip-media-gradient) !important;
} }
.chip-results { .chip-results {
@@ -111,6 +112,15 @@
max-width: 100px; max-width: 100px;
} }
.dialog-buttons {
align-items: center;
}
.avn-dialog-button {
font-size: 17px;
align-self: center;
}
/*Additional styles for small format landscape orientation*/ /*Additional styles for small format landscape orientation*/
@media (max-height: 450px) and (orientation: landscape) { @media (max-height: 450px) and (orientation: landscape) {
.detect-grid { .detect-grid {

View File

@@ -1,30 +1,3 @@
/* Material Icons Font (for MD theme) */
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: local('Material Icons'), local('MaterialIcons-Regular'),
url(../fonts/material-icons.woff2) format('woff2'),
url(../fonts/material-icons.woff) format('woff');
}
.material-icons {
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
font-size: 24px;
display: inline-block;
line-height: 1;
text-transform: none;
letter-spacing: normal;
word-wrap: normal;
white-space: nowrap;
direction: ltr;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
-moz-osx-font-smoothing: grayscale;
font-feature-settings: 'liga';
}
/* Framework7 Icons Font (for iOS theme) */ /* Framework7 Icons Font (for iOS theme) */
@font-face { @font-face {
font-family: 'Framework7 Icons'; font-family: 'Framework7 Icons';
@@ -54,3 +27,29 @@
font-feature-settings: 'liga'; font-feature-settings: 'liga';
text-align: center; text-align: center;
} }
/* Material Icons for text editor toolbar */
@font-face {
font-family: "Material Icons";
font-style: normal;
font-weight: 400;
font-display: block;
src: url("../fonts/material-icons.woff2") format("woff2"), url("../fonts/material-icons.woff") format("woff");
}
.material-icons {
font-family: "Material Icons";
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
font-feature-settings: "liga";
}

View File

@@ -1,55 +0,0 @@
// @see https://github.com/twbs/bootstrap/blob/main/scss/_functions.scss
@function material-icons-str-replace($string, $search, $replace: '') {
$index: str-index($string, $search);
@if $index {
@return str-slice($string, 1, $index - 1) + $replace +
material-icons-str-replace(
str-slice($string, $index + str-length($search)),
$search,
$replace
);
}
@return $string;
}
@mixin material-icons-font-class($font-family) {
font-family: $font-family;
font-weight: normal;
font-style: normal;
font-size: $material-icons-font-size;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-smoothing: antialiased; // Support for all WebKit browsers
-moz-osx-font-smoothing: grayscale; // Support for Firefox
text-rendering: optimizeLegibility; // Support for Safari and Chrome
font-feature-settings: 'liga'; // Support for IE
}
@mixin material-icons-font($font-family) {
$class-name: to-lower-case($font-family);
$class-name: material-icons-str-replace($class-name, ' ', '-');
$font-file: $material-icons-font-path + $class-name;
@font-face {
font-family: $font-family;
font-style: normal;
font-weight: 400;
font-display: $material-icons-font-display;
src: url('#{$font-file}.woff2') format('woff2'),
url('#{$font-file}.woff') format('woff');
}
.#{$class-name} {
@include material-icons-font-class($font-family);
}
}
@mixin material-icons() {
@warn "material-icons() Sass mixin has been deprecated as of 1.0. Use '@extend .material-icons;' instead of '@include material-icons();'.";
@include material-icons-font-class('Material Icons');
}

View File

@@ -1,3 +0,0 @@
$material-icons-font-path: './' !default;
$material-icons-font-size: 24px !default;
$material-icons-font-display: block !default;

View File

@@ -1,24 +0,0 @@
@font-face {
font-family: "Material Icons";
font-style: normal;
font-weight: 400;
font-display: block;
src: url("./material-icons.woff2") format("woff2"), url("./material-icons.woff") format("woff");
}
.material-icons {
font-family: "Material Icons";
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
font-feature-settings: "liga";
}

View File

@@ -1,4 +0,0 @@
@import 'variables';
@import 'mixins';
@include material-icons-font('Material Icons');

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,124 +0,0 @@
@font-face {
font-family: "Material Icons";
font-style: normal;
font-weight: 400;
font-display: block;
src: url("./material-icons.woff2") format("woff2"), url("./material-icons.woff") format("woff");
}
.material-icons {
font-family: "Material Icons";
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
font-feature-settings: "liga";
}
@font-face {
font-family: "Material Icons Outlined";
font-style: normal;
font-weight: 400;
font-display: block;
src: url("./material-icons-outlined.woff2") format("woff2"), url("./material-icons-outlined.woff") format("woff");
}
.material-icons-outlined {
font-family: "Material Icons Outlined";
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
font-feature-settings: "liga";
}
@font-face {
font-family: "Material Icons Round";
font-style: normal;
font-weight: 400;
font-display: block;
src: url("./material-icons-round.woff2") format("woff2"), url("./material-icons-round.woff") format("woff");
}
.material-icons-round {
font-family: "Material Icons Round";
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
font-feature-settings: "liga";
}
@font-face {
font-family: "Material Icons Sharp";
font-style: normal;
font-weight: 400;
font-display: block;
src: url("./material-icons-sharp.woff2") format("woff2"), url("./material-icons-sharp.woff") format("woff");
}
.material-icons-sharp {
font-family: "Material Icons Sharp";
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
font-feature-settings: "liga";
}
@font-face {
font-family: "Material Icons Two Tone";
font-style: normal;
font-weight: 400;
font-display: block;
src: url("./material-icons-two-tone.woff2") format("woff2"), url("./material-icons-two-tone.woff") format("woff");
}
.material-icons-two-tone {
font-family: "Material Icons Two Tone";
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
font-feature-settings: "liga";
}

View File

@@ -1,5 +0,0 @@
@import 'filled';
@import 'outlined';
@import 'round';
@import 'sharp';
@import 'two-tone';

View File

@@ -1,24 +0,0 @@
@font-face {
font-family: "Material Icons Outlined";
font-style: normal;
font-weight: 400;
font-display: block;
src: url("./material-icons-outlined.woff2") format("woff2"), url("./material-icons-outlined.woff") format("woff");
}
.material-icons-outlined {
font-family: "Material Icons Outlined";
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
font-feature-settings: "liga";
}

View File

@@ -1,4 +0,0 @@
@import 'variables';
@import 'mixins';
@include material-icons-font('Material Icons Outlined');

View File

@@ -1,24 +0,0 @@
@font-face {
font-family: "Material Icons Round";
font-style: normal;
font-weight: 400;
font-display: block;
src: url("./material-icons-round.woff2") format("woff2"), url("./material-icons-round.woff") format("woff");
}
.material-icons-round {
font-family: "Material Icons Round";
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
font-feature-settings: "liga";
}

View File

@@ -1,4 +0,0 @@
@import 'variables';
@import 'mixins';
@include material-icons-font('Material Icons Round');

View File

@@ -1,24 +0,0 @@
@font-face {
font-family: "Material Icons Sharp";
font-style: normal;
font-weight: 400;
font-display: block;
src: url("./material-icons-sharp.woff2") format("woff2"), url("./material-icons-sharp.woff") format("woff");
}
.material-icons-sharp {
font-family: "Material Icons Sharp";
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
font-feature-settings: "liga";
}

View File

@@ -1,4 +0,0 @@
@import 'variables';
@import 'mixins';
@include material-icons-font('Material Icons Sharp');

View File

@@ -1,24 +0,0 @@
@font-face {
font-family: "Material Icons Two Tone";
font-style: normal;
font-weight: 400;
font-display: block;
src: url("./material-icons-two-tone.woff2") format("woff2"), url("./material-icons-two-tone.woff") format("woff");
}
.material-icons-two-tone {
font-family: "Material Icons Two Tone";
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
font-feature-settings: "liga";
}

View File

@@ -1,4 +0,0 @@
@import 'variables';
@import 'mixins';
@include material-icons-font('Material Icons Two Tone');

View File

@@ -4,6 +4,8 @@ import AboutPage from '../pages/about.vue';
import SettingsPage from '../pages/settings.vue'; import SettingsPage from '../pages/settings.vue';
import DetectPage from '../pages/detect.vue'; import DetectPage from '../pages/detect.vue';
import ContactPage from '../pages/contact.vue'; import ContactPage from '../pages/contact.vue';
import SpecsPage from '../pages/specs.vue';
import HelpPage from '../pages/help.vue';
import NotFoundPage from '../pages/404.vue'; import NotFoundPage from '../pages/404.vue';
@@ -16,13 +18,21 @@ var routes = [
path: '/about/', path: '/about/',
component: AboutPage, component: AboutPage,
}, },
{
path: '/help/',
component: HelpPage,
},
{
path: '/specs/',
component: SpecsPage,
},
{ {
path: '/detect/:region/', path: '/detect/:region/',
component: DetectPage, component: DetectPage,
}, },
{ {
path: '/settings/', path: '/settings/',
component: SettingsPage, component: SettingsPage
}, },
{ {
path: '/contact/', path: '/contact/',

View File

@@ -2,7 +2,8 @@ import { reactive, computed } from 'vue';
const state = reactive({ const state = reactive({
disclaimerAgreement: false, disclaimerAgreement: false,
enabledRegions: ['thorax','abdomen','limbs'] enabledRegions: ['thorax','abdomen','limbs'],
version: '0.5.0-rc'
}) })
const agree = () => { const agree = () => {
@@ -12,5 +13,6 @@ const agree = () => {
export default () => ({ export default () => ({
isAgreed: computed(() => state.disclaimerAgreement), isAgreed: computed(() => state.disclaimerAgreement),
getRegions: computed(() => state.enabledRegions), getRegions: computed(() => state.enabledRegions),
getVersion: computed(() => state.version),
agree agree
}) })

View File

@@ -1,8 +1,8 @@
<template> <template>
<f7-page name="about"> <f7-page name="about">
<f7-navbar title="About" back-link="Back"></f7-navbar> <f7-navbar title="About" back-link="Back"></f7-navbar>
<f7-block-title>About A.L.V.I.N.N.</f7-block-title>
<f7-block> <f7-block>
<h2>About A.L.V.I.N.N.</h2>
<p> <p>
ALVINN is an object detection neural network specializing in the identification of anatomical structures from imagery of dissected, embalmed specimens in anatomy teaching labratories. ALVINN is an object detection neural network specializing in the identification of anatomical structures from imagery of dissected, embalmed specimens in anatomy teaching labratories.
ALVINN is intended as a learning aid only; it is not a tool for medical diagnosis, intervention, or treatment. ALVINN is intended as a learning aid only; it is not a tool for medical diagnosis, intervention, or treatment.
@@ -10,8 +10,14 @@
<p> <p>
All of the images in ALVINN's models have been collected by students and faculty of Midwestern University's anatomy classes. All of the images in ALVINN's models have been collected by students and faculty of Midwestern University's anatomy classes.
</p> </p>
<p>
Source code for ALVINN can be <f7-link :external="true" target="_blank" href="https://gitea.azgeorgis.net/ALVINN/ALVINN_f7/issues">found here</f7-link>.
</p>
<h3>Credits</h3>
<p> <p>
ALVINN is conceived and created by Justin Georgi. ALVINN is conceived and created by Justin Georgi.
</p>
<p>
Other contributions from: Other contributions from:
<ul> <ul>
<li>Alexandra Davis</li> <li>Alexandra Davis</li>

View File

@@ -1,18 +1,17 @@
export default { export default {
methods: { methods: {
async openCamera() { async openCamera(imContain) {
var cameraLoaded = false var cameraLoaded = false
const devicesList = await navigator.mediaDevices.enumerateDevices() const devicesList = await navigator.mediaDevices.enumerateDevices()
this.videoDeviceAvailable = devicesList.some( d => d.kind == "videoinput") this.videoDeviceAvailable = devicesList.some( d => d.kind == "videoinput")
if (this.videoDeviceAvailable) { if (this.videoDeviceAvailable) {
navigator.mediaDevices.getUserMedia({video: true})
var vidConstraint = { var vidConstraint = {
video: { video: {
width: { width: {
ideal: 1920 ideal: imContain.offsetWidth
}, },
height: { height: {
ideal: 1080 ideal: imContain.offsetHeight
}, },
facingMode: 'environment' facingMode: 'environment'
}, },
@@ -37,9 +36,6 @@ export default {
const tempCtx = tempCVS.getContext('2d') const tempCtx = tempCVS.getContext('2d')
tempCtx.drawImage(vidViewer, 0, 0) tempCtx.drawImage(vidViewer, 0, 0)
this.getImage(tempCVS.toDataURL()) this.getImage(tempCVS.toDataURL())
},
async videoStream() {
//TODO
} }
} }
} }

View File

@@ -3,15 +3,19 @@
<!-- Top Navbar --> <!-- Top Navbar -->
<f7-navbar :sliding="false" :back-link="true" back-link-url="/" back-link-force> <f7-navbar :sliding="false" :back-link="true" back-link-url="/" back-link-force>
<f7-nav-title sliding>{{ regions[activeRegion] }}</f7-nav-title> <f7-nav-title sliding>{{ regions[activeRegion] }}</f7-nav-title>
<f7-nav-right>
<f7-link v-if="!isCordova" :icon-only="true" tooltip="Fullscreen" :icon-f7="isFullscreen ? 'viewfinder_circle_fill' : 'viewfinder'" @click="toggleFullscreen"></f7-link>
<f7-link :icon-only="true" tooltip="ALVINN help" icon-f7="question_circle_fill" href="/help/"></f7-link>
</f7-nav-right>
</f7-navbar> </f7-navbar>
<f7-block class="detect-grid"> <f7-block class="detect-grid">
<div class="image-container"> <div class="image-container" ref="image_container">
<canvas id="im-draw" ref="image_cvs" @click="structureClick" :style="`display: ${imageLoaded ? 'block' : 'none'}; flex: 1 1 0%; max-width: 100%; max-height: 100%; min-width: 0; min-height: 0; background-size: contain; background-position: center; background-repeat: no-repeat`" />
<SvgIcon v-if="!imageView && !videoAvailable" :icon="f7route.params.region" fill-color="var(--avn-theme-color)" @click="selectImage" /> <SvgIcon v-if="!imageView && !videoAvailable" :icon="f7route.params.region" fill-color="var(--avn-theme-color)" @click="selectImage" />
<div class="vid-container" v-if="videoAvailable" style="width: 100%; height: 100%"> <div class="vid-container" :style="`display: ${videoAvailable ? 'block' : 'none'}; position: absolute; width: 100%; height: 100%;`">
<video id="vid-view" ref="vid_viewer" :srcObject="cameraStream" :autoPlay="true" style="width: 100%; height: 100%"></video> <video id="vid-view" ref="vid_viewer" :srcObject="cameraStream" :autoPlay="true" style="width: 100%; height: 100%"></video>
<f7-button @click="captureVidFrame()" style="position: absolute; bottom: 32px; left: 50%; transform: translateX(-50%);" fill large>Capture</f7-button> <f7-button @click="captureVidFrame()" style="position: absolute; bottom: 32px; left: 50%; transform: translateX(-50%); z-index: 3;" fill large>Capture</f7-button>
</div> </div>
<canvas id="im-draw" ref="image_cvs" @click="structureClick" :style="`display: ${(imageLoaded || videoAvailable) ? 'block' : 'none'}; flex: 1 1 0%; max-width: 100%; max-height: 100%; min-width: 0; min-height: 0; background-size: contain; background-position: center; background-repeat: no-repeat; z-index: 2;`" />
</div> </div>
<div class="chip-results" style="grid-area: result-view; flex: 0 0 auto; align-self: center;"> <div class="chip-results" style="grid-area: result-view; flex: 0 0 auto; align-self: center;">
<f7-chip v-for="result in showResults.filter( r => { return r.aboveThreshold && r.isSearched && !r.isDeleted })" <f7-chip v-for="result in showResults.filter( r => { return r.aboveThreshold && r.isSearched && !r.isDeleted })"
@@ -56,6 +60,7 @@
</f7-button> </f7-button>
</f7-segmented> </f7-segmented>
<input type="file" ref="image_chooser" @change="getImage()" accept="image/*" style="display: none;"/> <input type="file" ref="image_chooser" @change="getImage()" accept="image/*" style="display: none;"/>
<img src="../assets/target.svg" ref="target_image" style="display: none;" />
</f7-block> </f7-block>
<f7-panel :id="detectorName + '-settings'" right cover :backdrop="false" :container-el="`#${detectorName}-detect-page`"> <f7-panel :id="detectorName + '-settings'" right cover :backdrop="false" :container-el="`#${detectorName}-detect-page`">
@@ -94,8 +99,8 @@
<f7-button style="height: auto; width: auto;" popover-close="#capture-popover" @click="selectImage('file')"> <f7-button style="height: auto; width: auto;" popover-close="#capture-popover" @click="selectImage('file')">
<SvgIcon icon="photo_library" /> <SvgIcon icon="photo_library" />
</f7-button> </f7-button>
<f7-button style="height: auto; width: auto;" popover-close="#capture-popover" @click="videoStream"> <f7-button v-if="otherSettings.demo" style="height: auto; width: auto;" popover-close="#capture-popover" @click="selectImage('sample')">
<SvgIcon icon="videocam"/> <SvgIcon icon="photo_sample"/>
</f7-button> </f7-button>
</f7-segmented> </f7-segmented>
</f7-popover> </f7-popover>
@@ -116,8 +121,6 @@
import detectionMixin from './detection-mixin' import detectionMixin from './detection-mixin'
import cameraMixin from './camera-mixin' import cameraMixin from './camera-mixin'
import thoraxClasses from '../models/thorax/classes.json'
export default { export default {
mixins: [submitMixin, detectionMixin, cameraMixin], mixins: [submitMixin, detectionMixin, cameraMixin],
props: { props: {
@@ -144,11 +147,15 @@
detectorLevel: 50, detectorLevel: 50,
detectorLabels: [], detectorLabels: [],
serverSettings: {}, serverSettings: {},
otherSettings: {},
isCordova: !!window.cordova, isCordova: !!window.cordova,
isFullscreen: false,
uploadUid: null, uploadUid: null,
uploadDirty: false, uploadDirty: false,
modelLocation: '', modelLocation: '',
miniLocation: '',
modelLoading: true, modelLoading: true,
reloadModel: false,
videoDeviceAvailable: false, videoDeviceAvailable: false,
videoAvailable: false, videoAvailable: false,
cameraStream: null cameraStream: null
@@ -158,20 +165,17 @@
return store() return store()
}, },
created () { created () {
let loadOtherSettings = localStorage.getItem('otherSettings')
if (loadOtherSettings) this.otherSettings = JSON.parse(loadOtherSettings)
let modelRoot = this.isCordova ? 'https://localhost' : '.'
switch (this.f7route.params.region) { switch (this.f7route.params.region) {
case 'thorax': case 'thorax':
this.activeRegion = 0 this.activeRegion = 0
this.detectorName = 'thorax' this.detectorName = 'thorax'
this.classesList = thoraxClasses
/* VITE setting */
this.modelLocation = '../models/thorax/model.json'
/* PWA Build setting */
//this.modelLocation = './models/thorax/model.json'
this.modelLocationCordova = 'https://localhost/models/thorax/model.json'
break; break;
case 'abdomen': case 'abdomen':
this.activeRegion = 1 this.activeRegion = 1
this.detectorName = 'combined' this.detectorName = 'abdomen'
break; break;
case 'limbs': case 'limbs':
this.activeRegion = 2 this.activeRegion = 2
@@ -181,6 +185,14 @@
this.activeRegion = 3 this.activeRegion = 3
break; break;
} }
this.modelLocation = `${modelRoot}/models/${this.detectorName}${this.otherSettings.mini ? '-mini' : ''}/model.json`
this.miniLocation = `${modelRoot}/models/${this.detectorName}-mini/model.json`
fetch(`${this.isCordova ? 'https://localhost' : '.'}/models/${this.detectorName}/classes.json`)
.then((mod) => { return mod.json() })
.then((classes) => {
this.classesList = classes
this.detectorLabels = this.classesList.map( l => { return {'name': l, 'detect': true} } )
})
var loadServerSettings = localStorage.getItem('serverSettings') var loadServerSettings = localStorage.getItem('serverSettings')
if (loadServerSettings) this.serverSettings = JSON.parse(loadServerSettings) if (loadServerSettings) this.serverSettings = JSON.parse(loadServerSettings)
}, },
@@ -190,8 +202,7 @@
this.modelLoading = false this.modelLoading = false
} else { } else {
this.modelLoading = true this.modelLoading = true
this.detectorLabels = this.classesList.map( l => { return {'name': l, 'detect': true} } ) this.loadModel(this.modelLocation, true).then(() => {
this.loadModel(this.isCordova ? this.modelLocationCordova : this.modelLocation).then(() => {
this.modelLoading = false this.modelLoading = false
}).catch((e) => { }).catch((e) => {
console.log(e.message) console.log(e.message)
@@ -247,9 +258,14 @@
}, },
methods: { methods: {
chipGradient (confVal) { chipGradient (confVal) {
return `--chip-media-background: hsl(${confVal / 100 * 120}deg 100% 50%)` let confFactor = confVal / 100
return `--chip-media-gradient: conic-gradient(from ${270 - (confFactor * 360 / 2)}deg, hsl(${confFactor * 120}deg, 100%, 50%) ${confFactor}turn, hsl(${confFactor * 120}deg, 50%, 66%) ${confFactor}turn)`
}, },
setData () { async setData () {
if (this.reloadModel) {
await this.loadModel(this.modelLocation)
this.reloadModel = false
}
if (this.serverSettings && this.serverSettings.use) { if (this.serverSettings && this.serverSettings.use) {
this.remoteDetect() this.remoteDetect()
} else { } else {
@@ -279,8 +295,34 @@
return return
} }
if (mode == "camera") { if (mode == "camera") {
this.videoAvailable = await this.openCamera() this.videoAvailable = await this.openCamera(this.$refs.image_container)
if (this.videoAvailable) { return } if (this.videoAvailable) {
this.imageLoaded = false
this.imageView = null
this.$refs.image_cvs.style['background-image'] = 'none'
this.resultData = {}
var trackDetails = this.cameraStream.getVideoTracks()[0].getSettings()
var vidElement = this.$refs.vid_viewer
vidElement.width = trackDetails.width
vidElement.height = trackDetails.height
if (!this.otherSettings.disableVideo) {
this.videoFrameDetect(vidElement)
}
return
}
}
if (mode == 'sample') {
f7.dialog.create({
title: 'Sample images',
buttons: [
{text: 'Sample 1', close: true, onClick: () => this.getImage('sample1'), cssClass: 'avn-dialog-button'},
{text: 'Sample 2', close: true, onClick: () => this.getImage('sample2'), cssClass: 'avn-dialog-button'},
{text: 'Sample 3', close: true, onClick: () => this.getImage('sample3'), cssClass: 'avn-dialog-button'},
{text: 'Cancel', close: true, color: 'red', cssClass: 'avn-dialog-button'}
],
verticalButtons: true
}).open()
return
} }
this.$refs.image_chooser.click() this.$refs.image_chooser.click()
}, },
@@ -333,19 +375,30 @@
if (this.videoAvailable) { if (this.videoAvailable) {
this.closeCamera() this.closeCamera()
this.detecting = true this.detecting = true
this.reloadModel = true
resolve(searchImage) resolve(searchImage)
} else if (this.isCordova && this.imageLoadMode == "camera") { } else if (this.isCordova && this.imageLoadMode == "camera") {
this.detecting = true this.detecting = true
resolve('data:image/jpg;base64,' + searchImage) resolve('data:image/jpg;base64,' + searchImage)
} else { }
const searchImage = this.$refs.image_chooser.files[0]
this.$refs.image_chooser.value=[]
var reader = new FileReader() var reader = new FileReader()
reader.addEventListener("load", () => { reader.addEventListener("load", () => {
this.detecting = true this.detecting = true
resolve(reader.result) resolve(reader.result)
}) })
reader.readAsDataURL(searchImage) if (this.imageLoadMode == 'sample') {
fetch(`${this.isCordova ? 'https://localhost' : '.'}/samples/${this.detectorName}-${searchImage}.jpeg`).then( resp => {
return resp.blob()
}).then(respBlob => {
reader.readAsDataURL(respBlob)
}).catch((e) => {
console.log(e.message)
f7.dialog.alert(`Error loading image: ${e.message}`)
})
} else {
const fileImage = this.$refs.image_chooser.files[0]
this.$refs.image_chooser.value=[]
reader.readAsDataURL(fileImage)
} }
}) })
loadImage.then((imgData) => { loadImage.then((imgData) => {
@@ -360,11 +413,11 @@
imCanvas.style['background-image'] = `url(${this.imageView.src})` imCanvas.style['background-image'] = `url(${this.imageView.src})`
/****** /******
* setTimeout is not a good solution, but it's the only way * setTimeout is not a good solution, but it's the only way
* I can find to not cut off drawing of the progress spinner * I can find to not cut off drawing of the canvas background
******/ ******/
setTimeout(() => { setTimeout(() => {
this.setData() this.setData()
}, 250) }, 1)
}).catch((e) => { }).catch((e) => {
console.log(e.message) console.log(e.message)
f7.dialog.alert(`Error loading image: ${e.message}`) f7.dialog.alert(`Error loading image: ${e.message}`)
@@ -394,7 +447,7 @@
this.selectChip(findBox >= 0 ? this.resultData.detections[findBox].resultIndex : this.selectedChip) this.selectChip(findBox >= 0 ? this.resultData.detections[findBox].resultIndex : this.selectedChip)
}, },
box2cvs(boxInput) { box2cvs(boxInput) {
if (!boxInput) return [] if (!boxInput || boxInput.length == 0) return []
const boxList = boxInput.length ? boxInput : [boxInput] const boxList = boxInput.length ? boxInput : [boxInput]
const [imCanvas, imageCtx] = this.resetView() const [imCanvas, imageCtx] = this.resetView()
var imgWidth var imgWidth
@@ -417,6 +470,17 @@
} }
}) })
return cvsCoords return cvsCoords
},
toggleFullscreen() {
if (document.fullscreenElement) {
document.exitFullscreen().then( () => {
this.isFullscreen = false
})
} else {
app.requestFullscreen().then( () => {
this.isFullscreen = true
})
}
} }
} }
} }

View File

@@ -1,17 +1,27 @@
import * as tf from '@tensorflow/tfjs' import * as tf from '@tensorflow/tfjs'
import { f7 } from 'framework7-vue' import { f7 } from 'framework7-vue'
import { nextTick } from 'vue'
var model = null var model = null
export default { export default {
methods: { methods: {
async loadModel(weights) { async loadModel(weights, preload) {
await nextTick() if (model && model.modelURL == weights) {
return model
} else if (model) {
model.dispose()
}
model = await tf.loadGraphModel(weights) model = await tf.loadGraphModel(weights)
const [modelWidth, modelHeight] = model.inputs[0].shape.slice(1, 3) const [modelWidth, modelHeight] = model.inputs[0].shape.slice(1, 3)
/*****************
* If preloading then run model
* once on fake data to preload
* weights for a faster response
*****************/
if (preload) {
const dummyT = tf.ones([1,modelWidth,modelHeight,3]) const dummyT = tf.ones([1,modelWidth,modelHeight,3])
model.predict(dummyT) //Run model once to preload weights for better response time model.predict(dummyT)
}
return model return model
}, },
async localDetect(imageData) { async localDetect(imageData) {
@@ -24,17 +34,18 @@ export default {
console.time('run prediction') console.time('run prediction')
const res = model.predict(input) const res = model.predict(input)
const rawRes = tf.transpose(res,[0,2,1]).arraySync()[0]
console.timeEnd('run prediction') console.timeEnd('run prediction')
console.time('post-process') console.time('post-process')
const detectAttempts = res.shape[2]
const outputSize = res.shape[1] const outputSize = res.shape[1]
const rawRes = tf.transpose(res,[0,2,1]).dataSync()
let rawBoxes = [] let rawBoxes = []
let rawScores = [] let rawScores = []
for (var i = 0; i < detectAttempts; i++) { for (var i = 0; i < rawRes.length; i++) {
var getBox = rawRes.slice((i * outputSize),(i * outputSize) + 4) var getScores = rawRes[i].slice(4)
if (getScores.every( s => s < .05)) { continue }
var getBox = rawRes[i].slice(0,4)
var boxCalc = [ var boxCalc = [
(getBox[0] - (getBox[2] / 2)) / modelWidth, (getBox[0] - (getBox[2] / 2)) / modelWidth,
(getBox[1] - (getBox[3] / 2)) / modelHeight, (getBox[1] - (getBox[3] / 2)) / modelHeight,
@@ -42,25 +53,31 @@ export default {
(getBox[1] + (getBox[3] / 2)) / modelHeight, (getBox[1] + (getBox[3] / 2)) / modelHeight,
] ]
rawBoxes.push(boxCalc) rawBoxes.push(boxCalc)
rawScores.push(rawRes.slice((i * outputSize) + 4,(i + 1) * outputSize)) rawScores.push(getScores)
} }
if (rawBoxes.length > 0) {
const tBoxes = tf.tensor2d(rawBoxes) const tBoxes = tf.tensor2d(rawBoxes)
let tScores = null let tScores = null
let structureScores = null
let boxes_data = [] let boxes_data = []
let scores_data = [] let scores_data = []
let classes_data = [] let classes_data = []
for (var c = 0; c < outputSize - 4; c++) { for (var c = 0; c < outputSize - 4; c++) {
tScores = rawScores.map(x => x[c]) structureScores = rawScores.map(x => x[c])
var validBoxes = await tf.image.nonMaxSuppressionAsync(tBoxes,tf.tensor1d(tScores),10,0.5,.05) tScores = tf.tensor1d(structureScores)
var validBoxes = await tf.image.nonMaxSuppressionAsync(tBoxes,tScores,10,0.5,.05)
validBoxes = validBoxes.dataSync() validBoxes = validBoxes.dataSync()
if (validBoxes) { if (validBoxes) {
boxes_data.push(...rawBoxes.filter( (_, idx) => validBoxes.includes(idx))) boxes_data.push(...rawBoxes.filter( (_, idx) => validBoxes.includes(idx)))
var outputScores = tScores.filter( (_, idx) => validBoxes.includes(idx)) var outputScores = structureScores.filter( (_, idx) => validBoxes.includes(idx))
scores_data.push(...outputScores) scores_data.push(...outputScores)
classes_data.push(...outputScores.fill(c)) classes_data.push(...outputScores.fill(c))
} }
} }
tf.dispose(tBoxes)
tf.dispose(tScores)
const valid_detections_data = classes_data.length const valid_detections_data = classes_data.length
var output = { var output = {
detections: [] detections: []
@@ -76,12 +93,12 @@ export default {
"confidence": scores_data[i] * 100 "confidence": scores_data[i] * 100
}) })
} }
}
tf.dispose(res) tf.dispose(res)
tf.dispose(tBoxes) tf.dispose(input)
console.timeEnd('post-process') console.timeEnd('post-process')
return output return output || { detections: [] }
}, },
getRemoteLabels() { getRemoteLabels() {
var self = this var self = this
@@ -145,7 +162,60 @@ export default {
remoteTimeout () { remoteTimeout () {
this.detecting = false this.detecting = false
f7.dialog.alert('No connection to remote ALVINN instance. Please check app settings.') f7.dialog.alert('No connection to remote ALVINN instance. Please check app settings.')
},
async videoFrameDetect (vidData) {
await this.loadModel(this.miniLocation)
const [modelWidth, modelHeight] = model.inputs[0].shape.slice(1, 3)
const imCanvas = this.$refs.image_cvs
const imageCtx = imCanvas.getContext("2d")
const target = this.$refs.target_image
await tf.nextFrame();
imCanvas.width = imCanvas.clientWidth
imCanvas.height = imCanvas.clientHeight
imageCtx.clearRect(0,0,imCanvas.width,imCanvas.height)
var imgWidth
var imgHeight
const imgAspect = vidData.width / vidData.height
const rendAspect = imCanvas.width / imCanvas.height
if (imgAspect >= rendAspect) {
imgWidth = imCanvas.width
imgHeight = imCanvas.width / imgAspect
} else {
imgWidth = imCanvas.height * imgAspect
imgHeight = imCanvas.height
}
while (this.videoAvailable) {
console.time('frame-process')
try {
const input = tf.tidy(() => {
return tf.image.resizeBilinear(tf.browser.fromPixels(vidData), [modelWidth, modelHeight]).div(255.0).expandDims(0)
})
const res = model.predict(input)
const rawRes = tf.transpose(res,[0,2,1]).arraySync()[0]
let rawCoords = []
if (rawRes) {
for (var i = 0; i < rawRes.length; i++) {
var getScores = rawRes[i].slice(4)
if (getScores.some( s => s > .5)) {
rawCoords.push(rawRes[i].slice(0,2))
}
} }
imageCtx.clearRect(0,0,imCanvas.width,imCanvas.height)
for (var coord of rawCoords) {
console.log(`x: ${coord[0]}, y: ${coord[1]}`)
let pointX = (imCanvas.width - imgWidth) / 2 + (coord[0] / modelWidth) * imgWidth -5
let pointY = (imCanvas.height - imgHeight) / 2 + (coord[1] / modelHeight) * imgHeight -5
imageCtx.drawImage(target, pointX, pointY, 20, 20)
}
}
} catch (e) {
console.log(e)
}
console.timeEnd('frame-process')
await tf.nextFrame();
}
}
} }
} }

89
src/pages/help.vue Normal file
View File

@@ -0,0 +1,89 @@
<template>
<f7-page name="help">
<f7-navbar title="Help" back-link="Back"></f7-navbar>
<f7-block>
<h2>Quick Start</h2>
<ol>
<li>Select the region of the body you want to identify structures from.</li>
<li>Load an image:
<ul>
<li>Click on the camera icon <SvgIcon icon="photo_camera" class="list-svg"/> to take a new picture.
<ul>
<li>ALVINN will highlight areas with potential structures as you aim the camera.</li>
<li>Press <span class="cap-button">Capture</span> to use the current camera view.</li>
</ul>
</li>
<li>Click on the image file icon <SvgIcon icon="photo_library" class="list-svg"/> to load a picture from the device storage.</li>
<li>If demo mode is turned on, you can click on the marked image icon <SvgIcon icon="photo_sample" class="list-svg"/> to load an ALVINN sample image.</li>
</ul>
</li>
<li>When the picture is captured or loaded, any identifiable structures will be listed as tags below the image:
<f7-chip text="Structure name" media=" " class="demo-chip"/>
<ul>
<li>Click on each tag to see the structure highlighted in the image.</li>
<li>Tag color and proportion filled indicate ALVINN's level of confidence in the identification.</li>
<li>If there are potential structures that do not satisfy the current detection threshold, a badge on the detection menu icon <SvgIcon icon="visibility" class="list-svg"/> will indicate the number of un-displayed structures.</li>
</ul>
</li>
</ol>
<h2>Advanced Features</h2>
<h3>Detection Parameters</h3>
<p>
After an image has been loaded and structure detection has been performed, the detection parameters can be adjusted using the detection menu icon <SvgIcon icon="visibility" class="list-svg"/>.
This button will make three tools available:
</p>
<ol>
<li>Confidence slider: You can use the slider to change the confidence threshold for identifying structures. The default threshold is 50% confidence.</li>
<li>Refresh detections <SvgIcon icon="refresh_search" class="list-svg"/>: If there has been a permanent change to the structures detections, such as deleting a tag, the detection list can be reset to its original state.</li>
<li>Structure list <SvgIcon icon="check_list" class="list-svg"/>: you can view a list of all the structures available for detection in that region and select/deselect individual structures for detection.</li>
</ol>
<h3>Submitting Images</h3>
<p>
If all of the detection tags that are currently visible have been viewed, then the cloud upload button <SvgIcon icon="cloud_upload" class="list-svg"/> on the detection menu will be enabled.
This button will cause the image and the verified structures to be uploaded to the ALVINN project servers where that data will be available for further training of the neural net. If after the image has been uploaded, the available detection tags change, then the option to re-upload the image will be available if all the new tags have been viewed and verified.
</p>
</f7-block>
</f7-page>
</template>
<style>
li, p {
font-size: 16px;
}
.list-svg {
width: 2em;
position:relative;
top: .5em;
}
.cap-button {
background-color: var(--f7-theme-color);
color: white;
border-radius: 4px;
padding-left: 8px;
padding-right: 8px;
}
.demo-chip {
height: 24px;
padding-left: 8px;
--f7-chip-border-radius: 12px;
--f7-chip-media-size: 24px;
--f7-chip-font-weight: normal;
}
.demo-chip .chip-media {
background: conic-gradient(from 135deg, #00cc00 .75turn, #00cc0088 .75turn) !important;
}
</style>
<script>
import SvgIcon from '../components/svg-icon.vue'
export default {
components: {
SvgIcon
}
}
</script>

View File

@@ -3,14 +3,20 @@
<!-- Top Navbar --> <!-- Top Navbar -->
<f7-navbar> <f7-navbar>
<f7-nav-left> <f7-nav-left>
<f7-link icon-ios="f7:menu" icon-md="material:menu" panel-open="left"></f7-link> <f7-link icon-ios="f7:bars" icon-md="f7:bars" panel-open="left"></f7-link>
</f7-nav-left> </f7-nav-left>
<f7-nav-title sliding>A.L.V.I.N.N.</f7-nav-title> <f7-nav-title sliding>A.L.V.I.N.N.</f7-nav-title>
</f7-navbar> </f7-navbar>
<!-- Page content--> <!-- Page content-->
<div style="display: grid; grid-template-columns: 100%; grid-template-rows: min-content min-content min-content 1fr; align-content: stretch; gap: 15px; min-height: 0px; max-height: calc(100vh - (var(--f7-navbar-height) + var(--f7-safe-area-top))); height: calc(100vh - (var(--f7-navbar-height) + var(--f7-safe-area-top)));"> <div style="display: grid; grid-template-columns: 100%; grid-template-rows: min-content min-content min-content 1fr; align-content: stretch; gap: 15px; min-height: 0px; max-height: calc(100vh - (var(--f7-navbar-height) + var(--f7-safe-area-top))); height: calc(100vh - (var(--f7-navbar-height) + var(--f7-safe-area-top)));">
<h2 style="text-align: center; padding-left: 30px; padding-right: 30px;">Anatomy Lab Visual Identification Neural Network</h2> <h2 style="text-align: center; padding-left: 30px; padding-right: 30px;">Anatomy Lab Visual Identification Neural Network</h2>
<h4 style="text-align: center; margin: 0;">Veterinary Anatomy Edition</h4> <h4 style="text-align: center; margin: 0;">
Veterinary Anatomy Edition
<f7-link @click="alphaWarn">
ALPHA RELEASE
<f7-badge style="margin-left: 3px;" v-if="!alphaCheck" color="red">!</f7-badge>
</f7-link>
</h4>
<p style="text-align: center; margin: 0;">Select a region to begin.</p> <p style="text-align: center; margin: 0;">Select a region to begin.</p>
<div class="region-grid"> <div class="region-grid">
<f7-button :class="`region-button thorax${isAgreed && getRegions.includes('thorax') ? '' : ' disabled'}`" :href="isAgreed && getRegions.includes('thorax') && '/detect/thorax/'"> <f7-button :class="`region-button thorax${isAgreed && getRegions.includes('thorax') ? '' : ' disabled'}`" :href="isAgreed && getRegions.includes('thorax') && '/detect/thorax/'">
@@ -89,13 +95,24 @@
<script> <script>
import RegionIcon from '../components/region-icon.vue' import RegionIcon from '../components/region-icon.vue'
import store from '../js/store' import store from '../js/store'
import { f7 } from 'framework7-vue'
export default { export default {
components: { components: {
RegionIcon RegionIcon
}, },
data () {
return {
alphaCheck: false
}
},
setup() { setup() {
return store() return store()
},
methods: {
alphaWarn () {
f7.dialog.alert('This is an alpha release. Expect bugs and use the contact page to report any bugs you encounter.', 'Warning', () => { this.alphaCheck = true })
}
} }
} }
</script> </script>

View File

@@ -1,15 +1,39 @@
<template> <template>
<f7-page name="settings"> <f7-page name="settings">
<!-- Top Navbar --> <!-- Top Navbar -->
<f7-navbar :sliding="false" back-link="Back"> <f7-navbar :sliding="false">
<f7-nav-left>
<f7-link class="link icon-only ripple-inset" @click="confirmBack">
<f7-icon class="icon-back"></f7-icon>
</f7-link>
</f7-nav-left>
<f7-nav-title sliding>Settings</f7-nav-title> <f7-nav-title sliding>Settings</f7-nav-title>
</f7-navbar> </f7-navbar>
<f7-block style="display: flex; flex-direction: column; align-items: center;"> <f7-block style="display: flex; flex-direction: column; align-items: center;">
<div style="display: flex; flex-direction: column; align-items: center;"> <div style="display: flex; flex-direction: column; align-items: center;">
<f7-block-title medium>Server Settings</f7-block-title> <f7-block-title medium>Dark Mode</f7-block-title>
<f7-list style="width: 100%;">
<f7-list-item title="Auto" :checked="themeSettings.darkMode == 'auto'" radio name="darkmode" radio-icon="end" @change="setDarkMode('auto')" ></f7-list-item>
<f7-list-item title="Light" :checked="themeSettings.darkMode.toString() == 'false'" radio name="darkmode" radio-icon="end" @change="setDarkMode(false)" ></f7-list-item>
<f7-list-item title="Dark" :checked="themeSettings.darkMode.toString() == 'true'" radio name="darkmode" radio-icon="end" @change="setDarkMode(true)" ></f7-list-item>
</f7-list>
<f7-block-title @click="toggleSettingsView" medium><f7-icon :f7="showAdvanced ? 'chevron_down' : 'chevron_right'" /> Advanced Settings </f7-block-title>
<div ref="advancedSettings" class="settings-container">
<div style="display:flex; justify-content:space-between; width: 100%; margin-bottom: 10px;">
<span style="margin-left: 16px;">Use mini ALVINN<f7-icon size="16" style="padding-left: 5px;" f7="question_diamond_fill" tooltip="faster, less accurate: recommended for slower devices" /></span>
<f7-toggle v-model:checked="otherSettings.mini" style="margin-right: 16px;" @change="setDirty()" />
</div>
<div style="display:flex; justify-content:space-between; width: 100%; margin-bottom: 10px;">
<span style="margin-left: 16px;">Enable demo mode</span>
<f7-toggle v-model:checked="otherSettings.demo" style="margin-right: 16px;" @change="setDirty()" />
</div>
<div style="display:flex; justify-content:space-between; width: 100%; margin-bottom: 10px;">
<span style="margin-left: 16px;">Disable video estimates<f7-icon size="16" style="padding-left: 5px;" f7="question_diamond_fill" tooltip="faster: recommended for slower devices" /></span>
<f7-toggle v-model:checked="otherSettings.disableVideo" style="margin-right: 16px;" />
</div>
<div style="display:flex; justify-content:space-between; width: 100%"> <div style="display:flex; justify-content:space-between; width: 100%">
<span style="margin-left: 16px;">Use external server</span> <span style="margin-left: 16px;">Use external server</span>
<f7-toggle v-model:checked="serverSettings.use" style="margin-right: 16px;" /> <f7-toggle v-model:checked="serverSettings.use" style="margin-right: 16px;" @change="setDirty()" />
</div> </div>
<f7-list> <f7-list>
<f7-list-input :disabled="!serverSettings.use" v-model:value="serverSettings.address" label="Server address" type="text" placeholder="127.0.0.1" /> <f7-list-input :disabled="!serverSettings.use" v-model:value="serverSettings.address" label="Server address" type="text" placeholder="127.0.0.1" />
@@ -20,24 +44,32 @@
<f7-list-item v-for="(port, add) in otherIp" :disabled="!serverSettings.use" :title="add" @click="setServerProps(add, port)">{{ port }}</f7-list-item> <f7-list-item v-for="(port, add) in otherIp" :disabled="!serverSettings.use" :title="add" @click="setServerProps(add, port)">{{ port }}</f7-list-item>
<f7-list-item v-if="Object.keys(otherIp).length == 0" title="No previous server settings"></f7-list-item> <f7-list-item v-if="Object.keys(otherIp).length == 0" title="No previous server settings"></f7-list-item>
</f7-list> </f7-list>
<f7-block-title medium>Dark Mode</f7-block-title> </div>
<f7-list style="width: 100%;"> <f7-button fill @click="saveAllSettings">SAVE</f7-button>
<f7-list-item title="Auto" :checked="themeSettings.darkMode == 'auto'" radio name="darkmode" radio-icon="end" @change="setDarkMode('auto')" ></f7-list-item>
<f7-list-item title="Light" :checked="themeSettings.darkMode.toString() == 'false'" radio name="darkmode" radio-icon="end" @change="setDarkMode(false)" ></f7-list-item>
<f7-list-item title="Dark" :checked="themeSettings.darkMode.toString() == 'true'" radio name="darkmode" radio-icon="end" @change="setDarkMode(true)" ></f7-list-item>
</f7-list>
<f7-button @click="saveAllSettings" >Save</f7-button>
</div> </div>
</f7-block> </f7-block>
</f7-page> </f7-page>
</template> </template>
<style>
.settings-container {
max-height: 0px;
overflow: hidden;
transition: max-height 0.2s ease-out;
}
</style>
<script> <script>
import { f7 } from 'framework7-vue' import { f7 } from 'framework7-vue'
export default { export default {
data () { data () {
return { return {
showAdvanced: false,
isDirty: false,
otherSettings: {
mini: false
},
serverSettings: { serverSettings: {
use: false, use: false,
address: '10.170.64.22', address: '10.170.64.22',
@@ -66,6 +98,8 @@
if (!this.serverSettings.previous) this.serverSettings.previous = {} if (!this.serverSettings.previous) this.serverSettings.previous = {}
var loadThemeSettings = localStorage.getItem('themeSettings') var loadThemeSettings = localStorage.getItem('themeSettings')
if (loadThemeSettings) this.themeSettings = JSON.parse(loadThemeSettings) if (loadThemeSettings) this.themeSettings = JSON.parse(loadThemeSettings)
var loadOtherSettings = localStorage.getItem('otherSettings')
if (loadOtherSettings) this.otherSettings = JSON.parse(loadOtherSettings)
}, },
methods: { methods: {
saveAllSettings () { saveAllSettings () {
@@ -77,6 +111,7 @@
} }
localStorage.setItem('serverSettings',JSON.stringify(this.serverSettings)) localStorage.setItem('serverSettings',JSON.stringify(this.serverSettings))
localStorage.setItem('themeSettings',JSON.stringify(this.themeSettings)) localStorage.setItem('themeSettings',JSON.stringify(this.themeSettings))
localStorage.setItem('otherSettings',JSON.stringify(this.otherSettings))
saved() saved()
} catch { } catch {
failed() failed()
@@ -90,6 +125,7 @@
closeTimeout: 2000 closeTimeout: 2000
}) })
toast.open() toast.open()
this.isDirty = false;
}, },
() => { () => {
var toast = f7.toast.create({ var toast = f7.toast.create({
@@ -100,13 +136,31 @@
} }
) )
}, },
setDirty () {
this.isDirty = true
},
setDarkMode (mode) { setDarkMode (mode) {
this.themeSettings.darkMode = mode this.themeSettings.darkMode = mode
f7.setDarkMode(mode) f7.setDarkMode(mode)
this.isDirty = true
}, },
setServerProps (add, port) { setServerProps (add, port) {
this.serverSettings.address = add this.serverSettings.address = add
this.serverSettings.port = port this.serverSettings.port = port
this.isDirty = true
},
toggleSettingsView () {
this.showAdvanced = !this.showAdvanced
this.$refs.advancedSettings.style.maxHeight = `${this.showAdvanced ? this.$refs.advancedSettings.scrollHeight : 0}px`
},
confirmBack () {
if (this.isDirty) {
f7.dialog.confirm('If you leave this page you will loose unsaved changes to your settings.', () => {
f7.views.main.router.back()
})
} else {
f7.views.main.router.back()
}
} }
} }
} }

74
src/pages/specs.vue Normal file
View File

@@ -0,0 +1,74 @@
<template>
<f7-page name="specs">
<f7-navbar :sliding="false" back-link="Back">
<f7-nav-title sliding>Tech Specs</f7-nav-title>
</f7-navbar>
<f7-block style="display: flex; flex-direction: column; align-items: stretch;">
<div style="display: flex; flex-direction: column; align-items: center;">
<f7-block-title medium>Details</f7-block-title>
<f7-list>
<f7-list-item title="Version" :after="alvinnVersion"></f7-list-item>
</f7-list>
<f7-block-title medium>Models</f7-block-title>
<f7-list style="width: 100%;">
<f7-list-item :class="otherSettings.mini ? 'unused-model' : ''" title="Thorax" :after="thoraxDetails.version"></f7-list-item>
<f7-list-item title="Thorax-m" :after="miniThoraxDetails.version"></f7-list-item>
<f7-list-item :class="otherSettings.mini ? 'unused-model' : ''" title="Abdomen/Pelvis" :after="abdomenDetails.version"></f7-list-item>
<f7-list-item title="Abd/Pel-m" :after="miniAbdomenDetails.version"></f7-list-item>
<f7-list-item title="Limbs" :after="limbsDetails.version"></f7-list-item>
<f7-list-item title="Head/Neck" :after="headneckDetails.version"></f7-list-item>
</f7-list>
</div>
</f7-block>
</f7-page>
</template>
<style>
.unused-model {
opacity: .75;
}
.unused-model .item-title {
color: var(--f7-list-item-after-text-color)
}
</style>
<script>
import store from '../js/store'
export default {
data () {
return {
thoraxDetails: {},
miniThoraxDetails: {},
abdomenDetails: {},
miniAbdomenDetails: {},
limbsDetails: { "version": "N/A" },
headneckDetails: { "version": "N/A" },
alvinnVersion: store().getVersion,
isCordova: !!window.cordova,
otherSettings: {}
}
},
setup() {
return store()
},
created () {
var loadOtherSettings = localStorage.getItem('otherSettings')
if (loadOtherSettings) this.otherSettings = JSON.parse(loadOtherSettings)
fetch(`${this.isCordova ? 'https://localhost' : '.'}/models/thorax/descript.json`)
.then((mod) => { return mod.json() })
.then((desc) => { this.thoraxDetails = desc })
fetch(`${this.isCordova ? 'https://localhost' : '.'}/models/thorax-mini/descript.json`)
.then((mod) => { return mod.json() })
.then((desc) => { this.miniThoraxDetails = desc })
fetch(`${this.isCordova ? 'https://localhost' : '.'}/models/abdomen/descript.json`)
.then((mod) => { return mod.json() })
.then((desc) => { this.abdomenDetails = desc })
fetch(`${this.isCordova ? 'https://localhost' : '.'}/models/abdomen-mini/descript.json`)
.then((mod) => { return mod.json() })
.then((desc) => { this.miniAbdomenDetails = desc })
},
methods: {
}
}
</script>