41 Commits

Author SHA1 Message Date
d1e3a1760a Add preloader animation
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
2024-01-10 21:34:35 -07:00
23a2772468 Move disclaimer to app (#68)
The pre app disclaimer makes more sense initialized by the app and not the home page.  This PR moves the popup to the app and adds basic store functionality to give global access to the state of the apps agreement.

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

Reviewed-on: Georgi_Lab/ALVINN_f7#68
2024-01-10 09:32:41 -07:00
671813958b Cleanup unsed files and configs (#67)
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
Reviewed-on: Georgi_Lab/ALVINN_f7#67
2024-01-09 08:28:19 -07:00
66b135d21a Add a disclaimer popup on home page (#66)
The same disclaimer language on the about page now appears in a modal popup the first time the user loads alvinn.  Further function is not available until the user agrees to the disclaimer

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

Reviewed-on: Georgi_Lab/ALVINN_f7#66
2024-01-06 15:08:39 -07:00
e1102d0baf Fix panel and computed property bugs (#65)
Moving panel to page element was resulting in opening errors after more than one page opened.

viewedAll computer property was returning errors before result data was fetched.

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

Reviewed-on: Georgi_Lab/ALVINN_f7#65
2023-12-29 21:28:03 -07:00
184ff0301f Big fix structure box color
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
2023-12-28 08:42:19 -07:00
59f79fcfa8 Upgrade image and structure outline to canvas (#64)
Closes: #61

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

Reviewed-on: Georgi_Lab/ALVINN_f7#64
2023-12-28 08:06:06 -07:00
01e8b73677 Enable auto search and other fixes (#62)
Closes: #58

Closes: #59

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

Reviewed-on: Georgi_Lab/ALVINN_f7#62
2023-12-24 12:12:10 -07:00
cf4e9f7c3a Fix image popover and home screen size errors (#60)
Closes: #57

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

Reviewed-on: Georgi_Lab/ALVINN_f7#60
2023-12-22 16:31:58 -07:00
619211e827 Allow images and detection upload for future data (#56)
Closes: #48

This PR should allow the user to upload an image that has been taken with the camera and the associated detection information to a file server.  These file can then be used to add data to ALVINN's models.

Current upload destination is set to a file drop folder in Georgi Lab Nextcloud, but eventually (maybe) should be changed to some dedicated ALVINN backend.

Also, prediction data is still not formatted in any specific way.  It is just a JSON array of (top, left, bottom, right, label) objects.

Reviewed-on: Georgi_Lab/ALVINN_f7#56
2023-12-19 09:52:18 -07:00
75c54d6740 Improve android splash icon (#55)
Closes: #54

Had to actually shrink icon down to fit whole thing within cut-off circle.

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

Reviewed-on: Georgi_Lab/ALVINN_f7#55
2023-12-16 15:19:24 -07:00
7ad1f80a0c Fix cordova file load regression (#53)
Closes: #52

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

Reviewed-on: Georgi_Lab/ALVINN_f7#53
2023-12-15 08:26:55 -07:00
ef6ca1e3e6 Add image mode chooser (#51)
Closes: #46

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

Reviewed-on: Georgi_Lab/ALVINN_f7#51
2023-12-14 21:17:53 -07:00
e71429dce9 Add 'no result' output option (#50)
Closes: #49

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

Reviewed-on: Georgi_Lab/ALVINN_f7#50
2023-12-14 20:29:16 -07:00
f1ad49e255 Improve back button function (#47)
Closes: #44

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

Reviewed-on: Georgi_Lab/ALVINN_f7#47
2023-12-14 15:45:17 -07:00
9dca8012e3 Fix minor merge error in doods data
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
2023-12-13 12:51:56 -07:00
e03920255e Add camera capability (#45)
Closes: #29

Fixes access to the camera in the android cordova build and maintains access when using the vite build.

Reviewed-on: Georgi_Lab/ALVINN_f7#45
2023-12-13 12:34:12 -07:00
49ce2450a0 Re-fix dark mode loading on start
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
2023-12-12 20:58:12 -07:00
1d7d8ca0d8 Move result filter to app (#43)
Also changed chip colors to gradient from indexed list.

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

Reviewed-on: Georgi_Lab/ALVINN_f7#43
2023-12-12 20:32:20 -07:00
c805be725d Apply dark mode setting at start (#42)
Closes: #40

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

Reviewed-on: Georgi_Lab/ALVINN_f7#42
2023-12-10 19:24:18 -07:00
b4864aa5ff Update cordova android platform
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
2023-12-09 17:54:38 -07:00
c599e0bd04 Fix errors with detector panel and label lists (#41)
Closes: #39

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

Reviewed-on: Georgi_Lab/ALVINN_f7#41
2023-12-08 21:21:04 -07:00
19dd8ac908 Update colors of android icons and assets (#38)
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
Reviewed-on: Georgi_Lab/ALVINN_f7#38
2023-12-08 08:59:28 -07:00
70153494bf Fix small screen button sizes (#37)
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
Reviewed-on: Georgi_Lab/ALVINN_f7#37
2023-12-08 08:38:23 -07:00
e7a8c43a74 Add small features to detect page (#34)
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
Reviewed-on: Georgi_Lab/ALVINN_f7#34
2023-12-08 07:56:41 -07:00
484dff54c7 Remove old icons and regions
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
2023-12-07 20:41:48 -07:00
056d835b7c Add dark mode (#33)
Closes: #18

This PR adds dark mode styling and an app setting to choose light, dark, or auto mode.

Reviewed-on: Georgi_Lab/ALVINN_f7#33
2023-12-07 20:39:10 -07:00
fb81ebed83 Icons to vue components (#32)
Using inline svg in vue components will:
1. Make it possible to recolor svgs (for example for dark mode)
2. Be a better solution to the cordova resource location issue

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

Reviewed-on: Georgi_Lab/ALVINN_f7#32
2023-12-05 21:16:55 -07:00
34b5816eae Fix delete chip error (#31)
Fix regression from #28 when deleting a chip.  Also remove structure box when chip deleted.

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

Reviewed-on: Georgi_Lab/ALVINN_f7#31
2023-12-05 12:05:51 -07:00
693078768e Resolve issues with cordova android build (#28)
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
Reviewed-on: Georgi_Lab/ALVINN_f7#28
2023-12-02 21:29:04 -07:00
d17dfd062a Fix android icon regression
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
2023-12-02 10:08:59 -07:00
5e6f118a13 Add debug option to detect panel for app issue (#27)
Hoping this will help figure out why doods calls don't work when android app is compiled.

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

Reviewed-on: Georgi_Lab/ALVINN_f7#27
2023-12-01 21:23:44 -07:00
d104ba82c5 Fix cordova splash screen (#26)
Actually merge the splashscreen fix before deleting this time...

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

Reviewed-on: Georgi_Lab/ALVINN_f7#26
2023-12-01 08:48:08 -07:00
e2edbce407 Build updates
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
2023-11-30 09:04:08 -07:00
433e57ba93 Change image loading to promise (#25)
This PR makes the detection page image loading a promise for better loading success and error handling.

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

Reviewed-on: Georgi_Lab/ALVINN_f7#25
2023-11-29 21:39:15 -07:00
0cc9ad385f Fix image size in landscape and version in panel (#24)
Closes #22 and also fixes unlisted issue with the display of the placeholder image on the detect screen when in landscape mode.

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

Reviewed-on: Georgi_Lab/ALVINN_f7#24
2023-11-29 20:52:35 -07:00
3da3b9c1bb Add detection settings panel (#23)
Closes #14 and #20

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

Reviewed-on: Georgi_Lab/ALVINN_f7#23
2023-11-29 15:51:36 -07:00
ff3a13826a Convert detect layout to grid (#21)
Improve basic layout and transition to landscape on smaller screens using css grid.

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

Reviewed-on: Georgi_Lab/ALVINN_f7#21
2023-11-27 16:29:23 -07:00
6e6fc4e7a5 Update version number to 0.1.1
Update app version

Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
2023-11-26 07:49:56 -07:00
fd3123cf17 Fix version in cordova package
Cordova package still said 1.0.0 after initial install.

Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
2023-11-26 07:46:54 -07:00
ed048d0e83 Add responsive button layout (#19)
Took long enough, but buttons are in 2 x 2 grid in narrow screens and 1 x 4 grid in wider screens

Reviewed-on: Georgi_Lab/ALVINN_f7#19
2023-11-25 22:04:25 -07:00
42 changed files with 2603 additions and 371 deletions

1
.gitignore vendored
View File

@@ -33,6 +33,7 @@ node_modules/
# Misc
.DS_Store
Thumbs.db
build_notes.md
# Cordova
cordova/platforms/

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View File

@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<widget id="edu.midwestern.alvinn" version="0.1.0-a" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
<widget id="edu.midwestern.alvinn" version="0.1.2" 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>
<description>Anatomy Lab Visual Identification Neural Network.</description>
<author email="jgeorg@midwestern.edu" href="https://midwestern.edu">
@@ -11,20 +11,19 @@
<allow-navigation href="*" />
<access origin="*" />
<platform name="android">
<edit-config file="app/src/main/AndroidManifest.xml" mode="merge" target="/manifest/application">
<application android:usesCleartextTraffic="true" />
</edit-config>
<preference name="StatusBarOverlaysWebView" value="false" />
<preference name="android-minSdkVersion" value="22" />
<preference name="SplashMaintainAspectRatio" value="true" />
<splash density="land-hdpi" src="res/screen/android/drawable-hdpi/screen.png" />
<splash density="land-mdpi" src="res/screen/android/drawable-mdpi/screen.png" />
<splash density="land-xhdpi" src="res/screen/android/drawable-xhdpi/screen.png" />
<splash density="land-xxhdpi" src="res/screen/android/drawable-xxhdpi/screen.png" />
<splash density="land-xxxhdpi" src="res/screen/android/drawable-xxxhdpi/screen.png" />
<splash density="port-hdpi" src="res/screen/android/drawable-hdpi/screen.png" />
<splash density="port-mdpi" src="res/screen/android/drawable-mdpi/screen.png" />
<splash density="port-xhdpi" src="res/screen/android/drawable-xhdpi/screen.png" />
<splash density="port-xxhdpi" src="res/screen/android/drawable-xxhdpi/screen.png" />
<splash density="port-xxxhdpi" src="res/screen/android/drawable-xxxhdpi/screen.png" />
<preference name="AndroidWindowSplashScreenAnimatedIcon" value="res/screen/android/splashscreen.xml" />
<preference name="AndroidWindowSplashScreenBackground" value="#0f206c" />
<preference name="AndroidInsecureFileModeEnabled" value="true" />
<preference name="hostname" value="localhost" />
<icon density="ldpi" src="res/icon/android/mipmap-ldpi/ic_launcher.png" />
<icon density="mdpi" src="res/icon/android/mipmap-mdpi/ic_launcher.png" />
<icon density="hdpi" src="res/icon/android/mipmap-hdpi/ic_launcher.png" />

1683
cordova/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "edu.midwestern.alvinn",
"displayName": "ALVINN",
"version": "0.1.0-a",
"version": "0.1.0-b",
"description": "Anatomy Lab Visual Identification Neural Network.",
"main": "index.js",
"scripts": {
@@ -14,19 +14,23 @@
"license": "Apache-2.0",
"devDependencies": {
"cordova-android": "^12.0.1",
"cordova-browser": "^7.0.0",
"cordova-ios": "^7.0.1",
"cordova-plugin-camera": "^7.0.0",
"cordova-plugin-keyboard": "^1.2.0",
"cordova-plugin-splashscreen": "^6.0.2",
"cordova-plugin-statusbar": "^4.0.0"
},
"cordova": {
"plugins": {
"cordova-plugin-statusbar": {},
"cordova-plugin-keyboard": {},
"cordova-plugin-splashscreen": {}
"cordova-plugin-camera": {
"ANDROIDX_CORE_VERSION": "1.6.+"
}
},
"platforms": [
"ios",
"browser",
"android"
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -0,0 +1,19 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:pathData="m54,25.98 l-8.34,20.77c0,0 -9.99,4.08 -11.08,6.7 0,1.34 6.61,4.56 6.61,4.56L34.24,75.73H25.3L49.22,18.33h9.61L82.7,75.73H73.89L66.78,58.01c0,0 6.66,-3.22 6.66,-4.56 -1.11,-2.67 -11.12,-6.7 -11.12,-6.7z"
android:fillColor="#ffffff"/>
<path
android:pathData="m49.21,46.17c-2.35,0.97 -13.28,5.91 -13.28,7.24 0,1.33 8.58,5.74 13.28,7.24 -2.62,-1.47 -4.25,-4.24 -4.25,-7.24 0,-3 1.62,-5.77 4.25,-7.24zM58.8,46.17c2.62,1.48 4.24,4.24 4.24,7.24 -0,3 -1.63,5.77 -4.25,7.24 2.36,-0.97 13.29,-5.91 13.29,-7.24 0,-1.33 -8.57,-5.74 -13.27,-7.24z"
android:fillColor="#ffffff"/>
<path
android:pathData="m54,45.9c-1.16,0 -2.48,0.26 -3.86,0.69a7.85,7.82 0,0 0,-4 6.81,7.85 7.82,0 0,0 4,6.81c1.38,0.43 2.7,0.69 3.86,0.69 1.16,0 2.48,-0.26 3.86,-0.69a7.85,7.82 0,0 0,4 -6.81,7.85 7.82,0 0,0 -3.98,-6.8 7.85,7.82 0,0 0,-0 -0c-1.38,-0.43 -2.71,-0.69 -3.87,-0.69z"
android:fillColor="#ffffff"/>
<path
android:pathData="M50.81,53.41a3.19,3.19 0,1 0,6.38 0a3.19,3.19 0,1 0,-6.38 0z"
android:fillColor="#000000"/>
</vector>

View File

@@ -25,7 +25,7 @@
},
"theming": {
"customColor": true,
"color": "#002f65",
"color": "#385cad",
"darkMode": false,
"iconFonts": true
},

View File

@@ -1,7 +1,7 @@
{
"name": "alvinn",
"private": true,
"version": "1.0.0",
"version": "0.1.1",
"description": "ALVINN",
"repository": "",
"license": "UNLICENSED",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="#002F65" height="24" viewBox="0 -960 960 960" width="24"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-80h560v-560H200v560Zm40-80h480L570-480 450-320l-90-120-120 160Zm-40 80v-560 560Z"/></svg>

Before

Width:  |  Height:  |  Size: 323 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="#002F65" height="24" viewBox="0 -960 960 960" width="24"><path d="M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h480q33 0 56.5 23.5T720-720v180l160-160v440L720-420v180q0 33-23.5 56.5T640-160H160Zm0-80h480v-480H160v480Zm0 0v-480 480Z"/></svg>

Before

Width:  |  Height:  |  Size: 300 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="#002F65" height="24" viewBox="0 -960 960 960" width="24"><path d="M480-320q75 0 127.5-52.5T660-500q0-75-52.5-127.5T480-680q-75 0-127.5 52.5T300-500q0 75 52.5 127.5T480-320Zm0-72q-45 0-76.5-31.5T372-500q0-45 31.5-76.5T480-608q45 0 76.5 31.5T588-500q0 45-31.5 76.5T480-392Zm0 192q-146 0-266-81.5T40-500q54-137 174-218.5T480-800q146 0 266 81.5T920-500q-54 137-174 218.5T480-200Zm0-300Zm0 220q113 0 207.5-59.5T832-500q-50-101-144.5-160.5T480-720q-113 0-207.5 59.5T128-500q50 101 144.5 160.5T480-280Z"/></svg>

Before

Width:  |  Height:  |  Size: 550 B

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg width="100" height="100" version="1.1" viewBox="0 0 26.458333 26.458333" xmlns="http://www.w3.org/2000/svg">
<g stroke="#002f65" stroke-width=".264583px">
<path d="m25.402178 7.8631343c-0.487907-0.3670601-0.811572-0.7261214-1.573424-1.106523-0.006122-0.1598737 0.053853-0.2411643-0.072374-0.5438299-0.239221-0.3572156-1.352454-0.987126-2.19723-0.8590224-1.567124 0.9252583-1.879175 1.9380345-3.311246 2.9148849-0.987966 0.103956-2.015535 0.3206455-3.091153 0.6741123-10.556415-1.8721062-8.2481554 5.9196998-14.460584 1.7189868 0 0-0.24989902 0.06545-0.28777276 0.170279-0.0360567 0.0998 0.10708587 0.299783 0.10708587 0.299783 2.0948939 1.933517 4.742145 1.471155 6.6624536-0.07316 0.096935 0.768305 0.3887649 1.92789 0.8180324 3.363404-0.035692 1.245357-1.2923422 2.350278-1.3169003 2.801484-0.013354 0.24535 0.5120291 3.6149 0.7015429 3.650219l0.7793046 0.145235c0.8989154 0.167526 0.7195768-0.420583 0.3224789-0.780361-0.2085791-0.188976-0.3404558-0.252396-0.3637846-0.441707-0.3810495-3.092169 2.1284358-4.423261 2.4023638-6.742929 2.453391 0.120243 3.974486 1.282365 6.721539 1.403033 0.136906 1.035362-0.177341 4.099457-0.120257 4.484465 0.04824 0.325337 0.511082 0.918401 0.497537 1.876854-3e-3 0.211416 0.410117 0.159484 0.619918 0.185743 0.799059 0.09999 1.033405-0.329373 0.42557-0.75884-0.132327-0.0935-0.456134-0.264276-0.476806-0.424973-0.251045-1.951541 1.103782-4.917365 1.103782-4.917365 0.355435-0.554509 0.707693-1.135262 1.002776-2.188396 0.160636-0.543413 0.157772-1.012576 0.119972-1.465872 1.541867-1.5721797 1.123352-2.3466703 2.548492-2.7336036 0.65786 0.059985 1.147615 0.1738285 1.444935 0.3493259 0.420933-0.188852 0.760222-0.5096057 0.993749-1.001227z" fill="none"/>
<path d="m15.156751 8.9427562c-10.556415-1.8721062-8.2481554 5.9196998-14.460584 1.7189868 0 0-0.24989902 0.06545-0.28777276 0.170279-0.0360567 0.0998 0.10708587 0.299783 0.10708587 0.299783 2.0948939 1.933517 4.742145 1.471155 6.6624536-0.07316 0.048468 0.384152 0.1456587 0.866125 0.2843915 1.431499 0.7210773 0.130029 2.5390772 0.501293 3.0586462 0.563846 0.613348 0.03006 1.528237 0.20676 2.05877 0.334503 0.563462-1.044613 0.536275-0.982536 2.57701-4.4457368z" fill="#002f65" fill-rule="evenodd"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg width="100" height="100" version="1.1" viewBox="0 0 26.458333 26.458333" xmlns="http://www.w3.org/2000/svg">
<g stroke="#002f65" stroke-width=".264583px">
<path d="m25.402178 7.8631343c-0.487907-0.3670601-0.811572-0.7261214-1.573424-1.106523-0.006122-0.1598737 0.053853-0.2411643-0.072374-0.5438299-0.239221-0.3572156-1.352454-0.987126-2.19723-0.8590224-1.567124 0.9252583-1.879175 1.9380345-3.311246 2.9148849-0.987966 0.103956-2.015535 0.3206455-3.091153 0.6741123-10.556415-1.8721062-8.2481554 5.9196998-14.460584 1.7189868 0 0-0.24989902 0.06545-0.28777276 0.170279-0.0360567 0.0998 0.10708587 0.299783 0.10708587 0.299783 2.0948939 1.933517 4.742145 1.471155 6.6624536-0.07316 0.096935 0.768305 0.3887649 1.92789 0.8180324 3.363404-0.035692 1.245357-1.2923422 2.350278-1.3169003 2.801484-0.013354 0.24535 0.5120291 3.6149 0.7015429 3.650219l0.7793046 0.145235c0.8989154 0.167526 0.7195768-0.420583 0.3224789-0.780361-0.2085791-0.188976-0.3404558-0.252396-0.3637846-0.441707-0.3810495-3.092169 2.1284358-4.423261 2.4023638-6.742929 2.453391 0.120243 3.974486 1.282365 6.721539 1.403033 0.136906 1.035362-0.177341 4.099457-0.120257 4.484465 0.04824 0.325337 0.511082 0.918401 0.497537 1.876854-3e-3 0.211416 0.410117 0.159484 0.619918 0.185743 0.799059 0.09999 1.033405-0.329373 0.42557-0.75884-0.132327-0.0935-0.456134-0.264276-0.476806-0.424973-0.251045-1.951541 1.103782-4.917365 1.103782-4.917365 0.355435-0.554509 0.707693-1.135262 1.002776-2.188396 0.160636-0.543413 0.157772-1.012576 0.119972-1.465872 1.541867-1.5721797 1.123352-2.3466703 2.548492-2.7336036 0.65786 0.059985 1.147615 0.1738285 1.444935 0.3493259 0.420933-0.188852 0.760222-0.5096057 0.993749-1.001227z" fill="none" stop-color="#000000" style="-inkscape-stroke:none;font-variation-settings:normal"/>
<path d="m25.402178 7.8631343c-0.487907-0.3670601-0.811572-0.7261214-1.573424-1.106523-0.006122-0.1598737 0.053853-0.2411643-0.072374-0.5438299-0.239221-0.3572156-1.352454-0.987126-2.19723-0.8590224-1.567124 0.9252583-1.879175 1.9380345-3.311246 2.9148849 0.566485 0.8398567 1.254642 1.7575311 2.167098 2.9799951 1.541867-1.5721797 1.123352-2.3466703 2.548492-2.7336036 0.65786 0.059985 1.147615 0.1738285 1.444935 0.3493259 0.420933-0.188852 0.760222-0.5096057 0.993749-1.001227z" fill="#002f65" fill-rule="evenodd"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg width="100" height="100" version="1.1" viewBox="0 0 26.458333 26.458333" xmlns="http://www.w3.org/2000/svg">
<g stroke="#002f65" stroke-width=".264583px">
<path d="m25.402178 7.8631343c-0.487907-0.3670601-0.811572-0.7261214-1.573424-1.106523-0.006122-0.1598737 0.053853-0.2411643-0.072374-0.5438299-0.239221-0.3572156-1.352454-0.987126-2.19723-0.8590224-1.567124 0.9252583-1.879175 1.9380345-3.311246 2.9148849-0.987966 0.103956-2.015535 0.3206455-3.091153 0.6741123-10.556415-1.8721062-8.2481554 5.9196998-14.460584 1.7189868 0 0-0.24989902 0.06545-0.28777276 0.170279-0.0360567 0.0998 0.10708587 0.299783 0.10708587 0.299783 2.0948939 1.933517 4.742145 1.471155 6.6624536-0.07316 0.096935 0.768305 0.3887649 1.92789 0.8180324 3.363404-0.035692 1.245357-1.2923422 2.350278-1.3169003 2.801484-0.013354 0.24535 0.5120291 3.6149 0.7015429 3.650219l0.7793046 0.145235c0.8989154 0.167526 0.7195768-0.420583 0.3224789-0.780361-0.2085791-0.188976-0.3404558-0.252396-0.3637846-0.441707-0.3810495-3.092169 2.1284358-4.423261 2.4023638-6.742929 2.453391 0.120243 3.974486 1.282365 6.721539 1.403033 0.136906 1.035362-0.177341 4.099457-0.120257 4.484465 0.04824 0.325337 0.511082 0.918401 0.497537 1.876854-3e-3 0.211416 0.410117 0.159484 0.619918 0.185743 0.799059 0.09999 1.033405-0.329373 0.42557-0.75884-0.132327-0.0935-0.456134-0.264276-0.476806-0.424973-0.251045-1.951541 1.103782-4.917365 1.103782-4.917365 0.355435-0.554509 0.707693-1.135262 1.002776-2.188396 0.160636-0.543413 0.157772-1.012576 0.119972-1.465872 1.541867-1.5721797 1.123352-2.3466703 2.548492-2.7336036 0.65786 0.059985 1.147615 0.1738285 1.444935 0.3493259 0.420933-0.188852 0.760222-0.5096057 0.993749-1.001227z" fill="none" stop-color="#000000" style="-inkscape-stroke:none;font-variation-settings:normal"/>
<path d="m17.24251 14.457023c0.136906 1.035362-0.177341 4.099457-0.120257 4.484465 0.04824 0.325337 0.511082 0.918401 0.497537 1.876854-3e-3 0.211416 0.410117 0.159484 0.619918 0.185743 0.799059 0.09999 1.033405-0.329373 0.42557-0.75884-0.132327-0.0935-0.456134-0.264276-0.476806-0.424973-0.251045-1.951541 1.103782-4.917365 1.103782-4.917365 0.355435-0.554509 0.707693-1.135262 1.002776-2.188396 0.160636-0.543413 0.157772-1.012576 0.119972-1.465872-3.100189-4.8581326-4.866767-0.394712-3.172492 3.208384z" fill="#002f65" fill-rule="evenodd" stop-color="#000000" style="-inkscape-stroke:none;font-variation-settings:normal"/>
<path d="m7.1779333 11.058645c0.096935 0.768305 0.3887649 1.92789 0.8180324 3.363404-0.035692 1.245357-1.2923422 2.350278-1.3169003 2.801484-0.013354 0.24535 0.5120291 3.6149 0.7015429 3.650219l0.7793046 0.145235c0.8989154 0.167526 0.7195768-0.420583 0.3224789-0.780361-0.2085791-0.188976-0.3404558-0.252396-0.3637846-0.441707-0.3810495-3.092169 2.1284358-4.423261 2.4023638-6.742929 2.1562-5.4517681-2.8350883-3.4878487-3.3430377-1.995345z" fill="#002f65" fill-rule="evenodd" stop-color="#000000" style="-inkscape-stroke:none;font-variation-settings:normal"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg width="100" height="100" version="1.1" viewBox="0 0 26.458333 26.458333" xmlns="http://www.w3.org/2000/svg">
<g stroke="#002F65" stroke-width=".264583px">
<path d="m25.402178 7.8631343c-0.487907-0.3670601-0.811572-0.7261214-1.573424-1.106523-0.006122-0.1598737 0.053853-0.2411643-0.072374-0.5438299-0.239221-0.3572156-1.352454-0.987126-2.19723-0.8590224-1.567124 0.9252583-1.879175 1.9380345-3.311246 2.9148849-0.987966 0.103956-2.015535 0.3206455-3.091153 0.6741123-10.556415-1.8721062-8.2481554 5.9196998-14.460584 1.7189868 0 0-0.24989902 0.06545-0.28777276 0.170279-0.0360567 0.0998 0.10708587 0.299783 0.10708587 0.299783 2.0948939 1.933517 4.742145 1.471155 6.6624536-0.07316 0.096935 0.768305 0.3887649 1.92789 0.8180324 3.363404-0.035692 1.245357-1.2923422 2.350278-1.3169003 2.801484-0.013354 0.24535 0.5120291 3.6149 0.7015429 3.650219l0.7793046 0.145235c0.8989154 0.167526 0.7195768-0.420583 0.3224789-0.780361-0.2085791-0.188976-0.3404558-0.252396-0.3637846-0.441707-0.3810495-3.092169 2.1284358-4.423261 2.4023638-6.742929 2.453391 0.120243 3.974486 1.282365 6.721539 1.403033 0.136906 1.035362-0.177341 4.099457-0.120257 4.484465 0.04824 0.325337 0.511082 0.918401 0.497537 1.876854-3e-3 0.211416 0.410117 0.159484 0.619918 0.185743 0.799059 0.09999 1.033405-0.329373 0.42557-0.75884-0.132327-0.0935-0.456134-0.264276-0.476806-0.424973-0.251045-1.951541 1.103782-4.917365 1.103782-4.917365 0.355435-0.554509 0.707693-1.135262 1.002776-2.188396 0.160636-0.543413 0.157772-1.012576 0.119972-1.465872 1.541867-1.5721797 1.123352-2.3466703 2.548492-2.7336036 0.65786 0.059985 1.147615 0.1738285 1.444935 0.3493259 0.420933-0.188852 0.760222-0.5096057 0.993749-1.001227z" fill="none" />
<path d="m18.247904 8.2686439c-0.987966 0.103956-3.091153 0.6741123-3.091153 0.6741123-1.652395 2.7995828-2.197177 3.7434008-2.550516 4.3811848 0 0 3.039112 0.665488 4.636275 1.133082-3e-3 0.01385 2.049744 0.445884 2.049744 0.445884s0.707693-1.135262 1.002776-2.188396c0.160636-0.543413 0.157772-1.012576 0.119972-1.465872-0.291029-0.377705-1.38593-1.9038754-2.167098-2.9799951z" fill-rule="evenodd" fill="#002F65" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -1,8 +1,7 @@
<template>
<f7-app v-bind="f7params">
<!-- Left panel with cover effect-->
<f7-panel left cover dark>
<f7-panel left cover>
<f7-view>
<f7-page>
<f7-navbar title="ALVINN"></f7-navbar>
@@ -10,33 +9,47 @@
<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>
<f7-toolbar class="panel-bar" position="bottom">
<span>version 0.1.1</span>
</f7-toolbar>
</f7-page>
</f7-view>
</f7-panel>
<!-- Your main view, should have "view-main" class -->
<f7-view main class="safe-areas" url="/"></f7-view>
<!-- Popup -->
<f7-popover id="region-popover">
<f7-segmented raised style="flex-wrap: wrap; flex-direction: column;">
<f7-button style="height: auto; width: auto;" href="/detect/thorax/" popover-close="#region-popover">
<img src="../assets/regions/thorax.svg" />
</f7-button>
<f7-button style="height: auto; width: auto;" href="/detect/abdomen/" popover-close="#region-popover">
<img src="../assets/regions/abdpel.svg" />
</f7-button>
<f7-button style="height: auto; width: auto;" href="/detect/limbs/" popover-close="#region-popover">
<img src="../assets/regions/limb.svg" />
</f7-button>
<f7-button style="height: auto; width: auto;" href="/detect/head/" popover-close="#region-popover">
<img src="../assets/regions/headneck.svg" />
</f7-button>
</f7-segmented>
</f7-popover>
<f7-view main class="safe-areas" url="/">
<f7-popup :opened="showDisclaimer"
style="height: auto; text-align: center;"
:close-by-backdrop-click="false"
:close-on-escape="false"
:swipe-to-close="false"
>
<f7-block-title large>
IMPORTANT
</f7-block-title>
<f7-block>
<h3>
ALVINN is for educational purposes only. It may not be used for medical diagnosis, intervention, or treatment.
</h3>
<div style="display: flex; justify-content: space-around; flex-direction: row; align-items: center;">
<span style="height: min-content;">
<f7-checkbox v-model:checked="rememberAgreement"/> Don't show again
</span>
<f7-button text="I agree" fill @click="setAgreement" />
</div>
</f7-block>
</f7-popup>
</f7-view>
</f7-app>
</template>
<style>
.panel-bar > .toolbar-inner {
justify-content: center;
}
</style>
<script>
import { ref, onMounted } from 'vue';
import { f7, f7ready } from 'framework7-vue';
@@ -47,18 +60,76 @@
import store from '../js/store';
export default {
data () {
return {
rememberAgreement: false,
siteAgreement: false,
showDisclaimer: true
}
},
created () {
var loadSiteSettings = localStorage.getItem('siteSettings')
if (loadSiteSettings) {
var loadedSettings = JSON.parse(loadSiteSettings)
this.siteAgreement = loadedSettings.siteAgreement
this.rememberAgreement = loadedSettings.rememberAgreement
}
if (this.siteAgreement && this.rememberAgreement) {
this.showDisclaimer = false
store().agree()
}
},
methods: {
setAgreement () {
this.siteAgreement = true
store().agree()
let newSettings = {
siteAgreement: this.siteAgreement,
rememberAgreement: this.rememberAgreement
}
let saveSiteSettings = new Promise(
(saved,failed) => {
try {
localStorage.setItem('siteSettings',JSON.stringify(newSettings))
saved()
} catch {
failed()
}
}
)
saveSiteSettings.then(
() => {
this.showDisclaimer = false
},
() => {
var toast = f7.toast.create({
text: 'ERROR: No settings saved',
closeTimeout: 2000
})
toast.open()
}
)
}
},
setup() {
const device = getDevice();
// Framework7 Parameters
var loadThemeSettings = localStorage.getItem('themeSettings')
if (loadThemeSettings) var themeSettings = JSON.parse(loadThemeSettings)
try {
if (themeSettings.darkMode.toString()) var darkTheme = themeSettings.darkMode
} catch {
var darkTheme = 'auto'
}
//provide('isAgreed',siteAgreement)
const f7params = {
name: 'ALVINN', // App name
theme: 'auto', // Automatic theme detection
darkMode: darkTheme,
colors: {
primary: '#002f65',
},
// App store
store: store,
// App routes
routes: routes,
@@ -99,7 +170,7 @@
});
return {
f7params
f7params,
}
}
}

View File

@@ -0,0 +1,32 @@
<template>
<svg width="100%" height="100%" version="1.1" viewBox="0 0 26.458333 26.458333" xmlns="http://www.w3.org/2000/svg">
<g stroke="none" :fill="fillColor" >
<path d="m25.402178 7.8631343c-0.487907-0.3670601-0.811572-0.7261214-1.573424-1.106523-0.006122-0.1598737 0.053853-0.2411643-0.072374-0.5438299-0.239221-0.3572156-1.352454-0.987126-2.19723-0.8590224-1.567124 0.9252583-1.879175 1.9380345-3.311246 2.9148849-0.987966 0.103956-2.015535 0.3206455-3.091153 0.6741123-10.556415-1.8721062-8.2481554 5.9196998-14.460584 1.7189868 0 0-0.24989902 0.06545-0.28777276 0.170279-0.0360567 0.0998 0.10708587 0.299783 0.10708587 0.299783 2.0948939 1.933517 4.742145 1.74421 6.6624536-0.07316 0.096935 0.768305 0.3887649 1.92789 0.8180324 3.363404-0.035692 1.245357-1.2923422 2.350278-1.3169003 2.801484-0.013354 0.24535 0.5120291 3.6149 0.7015429 3.650219l0.7793046 0.145235c0.8989154 0.167526 0.7195768-0.420583 0.3224789-0.780361-0.2085791-0.188976-0.3404558-0.252396-0.3637846-0.441707-0.3810495-3.092169 2.1284358-4.423261 2.4023638-6.742929 2.453391 0.120243 3.974486 1.282365 6.721539 1.403033 0.136906 1.035362-0.177341 4.099457-0.120257 4.484465 0.04824 0.325337 0.511082 0.918401 0.497537 1.876854-3e-3 0.211416 0.410117 0.159484 0.619918 0.185743 0.799059 0.09999 1.033405-0.329373 0.42557-0.75884-0.132327-0.0935-0.456134-0.264276-0.476806-0.424973-0.251045-1.951541 1.103782-4.917365 1.103782-4.917365 0.355435-0.554509 0.707693-1.135262 1.002776-2.188396 0.160636-0.543413 0.157772-1.012576 0.119972-1.465872 1.541867-1.5721797 1.123352-2.3466703 2.548492-2.7336036 0.65786 0.059985 1.147615 0.1738285 1.444935 0.3493259 0.420933-0.188852 0.760222-0.5096057 0.993749-1.001227z" style="opacity: .25;"/>
<path v-if="region == 0" d="m 18.247904,8.2686439 c -0.987966,0.103956 -3.091153,0.6741123 -3.091153,0.6741123 -1.652395,2.7995828 -2.226698,3.8098238 -2.580037,4.4476078 0,0 2.617397,0.984666 4.665796,1.066659 -0.003,0.01385 2.049744,0.445884 2.049744,0.445884 0,0 0.707693,-1.135262 1.002776,-2.188396 0.160636,-0.543413 0.157772,-1.012576 0.119972,-1.465872 -0.291029,-0.377705 -1.38593,-1.9038754 -2.167098,-2.9799951 z" fill-rule="evenodd" :fill="fillColor" />
<path v-else-if="region ==1" d="m15.156751 8.9427562c-10.556415-1.8721062-8.2481554 5.9196998-14.460584 1.7189868 0 0-0.24989902 0.06545-0.28777276 0.170279-0.0360567 0.0998 0.10708587 0.299783 0.10708587 0.299783 2.0948939 1.933517 4.742145 1.74421 6.6624536-0.07316 0.048468 0.384152 0.1456587 0.866125 0.2843915 1.431499 0.7210773 0.130029 2.5390772 0.501293 3.0586462 0.563846 0.613348 0.03006 1.528237 0.20676 2.05877 0.334503 0.563462-1.044613 0.536275-0.982536 2.57701-4.4457368z" :fill="fillColor" fill-rule="evenodd"/>
<g v-else-if="region == 2" :fill="fillColor" fill-rule="evenodd">
<path d="m17.24251 14.457023c0.136906 1.035362-0.177341 4.099457-0.120257 4.484465 0.04824 0.325337 0.511082 0.918401 0.497537 1.876854-3e-3 0.211416 0.410117 0.159484 0.619918 0.185743 0.799059 0.09999 1.033405-0.329373 0.42557-0.75884-0.132327-0.0935-0.456134-0.264276-0.476806-0.424973-0.251045-1.951541 1.103782-4.917365 1.103782-4.917365 0.355435-0.554509 0.707693-1.135262 1.002776-2.188396 0.160636-0.543413 0.157772-1.012576 0.119972-1.465872-3.100189-4.8581326-4.866767-0.394712-3.172492 3.208384z" />
<path d="m7.1779333 11.058645c0.096935 0.768305 0.3887649 1.92789 0.8180324 3.363404-0.035692 1.245357-1.2923422 2.350278-1.3169003 2.801484-0.013354 0.24535 0.5120291 3.6149 0.7015429 3.650219l0.7793046 0.145235c0.8989154 0.167526 0.7195768-0.420583 0.3224789-0.780361-0.2085791-0.188976-0.3404558-0.252396-0.3637846-0.441707-0.3810495-3.092169 2.1284358-4.423261 2.4023638-6.742929 2.1562-5.4517681-2.8350883-3.4878487-3.3430377-1.995345z" />
</g>
<path v-else-if="region == 3" d="m25.402178 7.8631343c-0.487907-0.3670601-0.811572-0.7261214-1.573424-1.106523-0.006122-0.1598737 0.053853-0.2411643-0.072374-0.5438299-0.239221-0.3572156-1.352454-0.987126-2.19723-0.8590224-1.567124 0.9252583-1.879175 1.9380345-3.311246 2.9148849 0.566485 0.8398567 1.254642 1.7575311 2.167098 2.9799951 1.541867-1.5721797 1.123352-2.3466703 2.548492-2.7336036 0.65786 0.059985 1.147615 0.1738285 1.444935 0.3493259 0.420933-0.188852 0.760222-0.5096057 0.993749-1.001227z" :fill="fillColor" fill-rule="evenodd"/>
</g>
</svg>
</template>
<script>
export default {
name: "RegionIcon",
props: {
region: {
type: Number,
validator(value) {
return value >= 0 && value <= 3
}
},
fillColor: {
type: String,
default: "var(--avn-theme-color)"
}
}
}
</script>

View File

@@ -0,0 +1,44 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" :fill="fillColor" height="100%" viewBox="0 -960 960 960" width="100%">
<path v-if="icon == 'image'" d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-80h560v-560H200v560Zm40-80h480L570-480 450-320l-90-120-120 160Zm-40 80v-560 560Z"/>
<path v-else-if="icon == 'videocam'" d="M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h480q33 0 56.5 23.5T720-720v180l160-160v440L720-420v180q0 33-23.5 56.5T640-160H160Zm0-80h480v-480H160v480Zm0 0v-480 480Z"/>
<path v-else-if="icon == 'visibility'" d="M480-320q75 0 127.5-52.5T660-500q0-75-52.5-127.5T480-680q-75 0-127.5 52.5T300-500q0 75 52.5 127.5T480-320Zm0-72q-45 0-76.5-31.5T372-500q0-45 31.5-76.5T480-608q45 0 76.5 31.5T588-500q0 45-31.5 76.5T480-392Zm0 192q-146 0-266-81.5T40-500q54-137 174-218.5T480-800q146 0 266 81.5T920-500q-54 137-174 218.5T480-200Zm0-300Zm0 220q113 0 207.5-59.5T832-500q-50-101-144.5-160.5T480-720q-113 0-207.5 59.5T128-500q50 101 144.5 160.5T480-280Z"/>
<path v-else-if="icon == 'photo_library'" d="M360-400h400L622-580l-92 120-62-80-108 140Zm-40 160q-33 0-56.5-23.5T240-320v-480q0-33 23.5-56.5T320-880h480q33 0 56.5 23.5T880-800v480q0 33-23.5 56.5T800-240H320Zm0-80h480v-480H320v480ZM160-80q-33 0-56.5-23.5T80-160v-560h80v560h560v80H160Zm160-720v480-480Z"/>
<path v-else-if="icon == 'no_photography'" d="m880-195-80-80v-405H638l-73-80H395l-38 42-57-57 60-65h240l74 80h126q33 0 56.5 23.5T880-680v485Zm-720 75q-33 0-56.5-23.5T80-200v-480q0-33 23.5-56.5T160-760h41l80 80H160v480h601l80 80H160Zm466-215q-25 34-62.5 54.5T480-260q-75 0-127.5-52.5T300-440q0-46 20.5-83.5T375-586l58 58q-24 13-38.5 36T380-440q0 42 29 71t71 29q29 0 52-14.5t36-38.5l58 58Zm-18-233q25 24 38.5 57t13.5 71v12q0 6-1 12L456-619q6-1 12-1h12q38 0 71 13.5t57 38.5ZM819-28 27-820l57-57L876-85l-57 57ZM407-440Zm171-57Z"/>
<path v-else-if="icon == 'photo_camera'" d="M480-260q75 0 127.5-52.5T660-440q0-75-52.5-127.5T480-620q-75 0-127.5 52.5T300-440q0 75 52.5 127.5T480-260Zm0-80q-42 0-71-29t-29-71q0-42 29-71t71-29q42 0 71 29t29 71q0 42-29 71t-71 29ZM160-120q-33 0-56.5-23.5T80-200v-480q0-33 23.5-56.5T160-760h126l74-80h240l74 80h126q33 0 56.5 23.5T880-680v480q0 33-23.5 56.5T800-120H160Zm0-80h640v-480H638l-73-80H395l-73 80H160v480Zm320-240Z"/>
<path v-else-if="icon == 'cloud_upload'" d="M260-160q-91 0-155.5-63T40-377q0-78 47-139t123-78q25-92 100-149t170-57q117 0 198.5 81.5T760-520q69 8 114.5 59.5T920-340q0 75-52.5 127.5T740-160H520q-33 0-56.5-23.5T440-240v-206l-64 62-56-56 160-160 160 160-56 56-64-62v206h220q42 0 71-29t29-71q0-42-29-71t-71-29h-60v-80q0-83-58.5-141.5T480-720q-83 0-141.5 58.5T280-520h-20q-58 0-99 41t-41 99q0 58 41 99t99 41h100v80H260Zm220-280Z"/>
<path v-else-if="icon == 'cloud_done'" d="m414-280 226-226-58-58-169 169-84-84-57 57 142 142ZM260-160q-91 0-155.5-63T40-377q0-78 47-139t123-78q25-92 100-149t170-57q117 0 198.5 81.5T760-520q69 8 114.5 59.5T920-340q0 75-52.5 127.5T740-160H260Zm0-80h480q42 0 71-29t29-71q0-42-29-71t-71-29h-60v-80q0-83-58.5-141.5T480-720q-83 0-141.5 58.5T280-520h-20q-58 0-99 41t-41 99q0 58 41 99t99 41Zm220-240Z"/>
<path v-else-if="icon == 'check_list'" d="M655-200 513-342l56-56 85 85 170-170 56 57-225 226Zm0-320L513-662l56-56 85 85 170-170 56 57-225 226ZM80-280v-80h360v80H80Zm0-320v-80h360v80H80Z"/>
<path v-else-if="icon == 'refresh_search'" d="M822-142 592-372q-32 26-71 39t-81 13q-42 0-80-12.5T290-368l58-58q20 12 43 19t49 7q75 0 127.5-52.5T620-580q0-75-52.5-127.5T440-760q-69 0-119.5 46.5T262-598l50-50 56 56-148 148L72-592l56-56 54 52q6-103 80-173.5T440-840q109 0 184.5 75.5T700-580q0 42-13 82t-39 70l230 230-56 56Z"/>
</svg>
</template>
<script>
export default {
name: "SvgIcon",
props: {
icon: {
type: String,
validator(value) {
const iconList = [
'image',
'videocam',
'visibility',
'photo_library',
'no_photography',
'photo_camera',
'cloud_upload',
'cloud_done',
'check_list',
'refresh_search'
]
return iconList.includes(value)
}
},
fillColor: {
type: String,
default: "var(--avn-theme-color)"
}
}
}
</script>

View File

@@ -4,16 +4,45 @@
}
/* Your app custom styles here */
.view-main {
--f7-theme-color: #002F65;
html {
--avn-theme-color: #0f206c;
--avn-button-bg-color: #d9d8d6;
--avn-structure-box-color: yellow;
}
.dark {
--avn-theme-color: #d9c79e;
--avn-button-bg-color: #0f206c;
}
body {
font-family: "Open Sans", sans-serif;
}
.navbar {
--f7-navbar-text-color: #002F65;
--f7-navbar-link-color: #002F65;
--f7-navbar-text-color: var(--avn-theme-color);
--f7-navbar-link-color: var(--avn-theme-color);
}
.navbar-bg {
--f7-navbar-bg-color: #BDBCAF;
}
--f7-navbar-bg-color: #d9c79e;
}
.dark .navbar-bg {
--f7-navbar-bg-color: #0f206c;
}
/* MWU Colors
Rich blue: #385cad
Dark blue: #0f206c
Light gold: #d9c79e
Dark gold: #bd9a5f
Secondary:
White: #ffffff
Black: #414042
Dark Gray: #c8c8c8
Light Gray: #d9d8d6
Green: #009490
Purple: #83276b
*/

View File

@@ -23,6 +23,7 @@
<link rel="apple-touch-icon" href="icons/apple-touch-icon.png">
<link rel="icon" href="icons/favicon.png">
<link rel="manifest" href="/manifest.json">
<link href='https://fonts.googleapis.com/css?family=Open Sans' rel='stylesheet'>
<% } %>
<!-- built styles file will be auto injected -->
</head>

View File

@@ -4,7 +4,6 @@ import AboutPage from '../pages/about.vue';
import SettingsPage from '../pages/settings.vue';
import DetectPage from '../pages/detect.vue';
import DynamicRoutePage from '../pages/dynamic-route.vue';
import NotFoundPage from '../pages/404.vue';
var routes = [
@@ -24,12 +23,6 @@ var routes = [
path: '/settings/',
component: SettingsPage,
},
{
path: '/dynamic-route/blog/:blogId/post/:postId/',
component: DynamicRoutePage,
},
{
path: '(.*)',
component: NotFoundPage,

View File

@@ -1,35 +1,14 @@
import { reactive, computed } from 'vue';
import { createStore } from 'framework7/lite';
const state = reactive({
disclaimerAgreement: false
})
const store = createStore({
state: {
products: [
{
id: '1',
title: 'Apple iPhone 8',
description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Nisi tempora similique reiciendis, error nesciunt vero, blanditiis pariatur dolor, minima sed sapiente rerum, dolorem corrupti hic modi praesentium unde saepe perspiciatis.'
},
{
id: '2',
title: 'Apple iPhone 8 Plus',
description: 'Velit odit autem modi saepe ratione totam minus, aperiam, labore quia provident temporibus quasi est ut aliquid blanditiis beatae suscipit odio vel! Nostrum porro sunt sint eveniet maiores, dolorem itaque!'
},
{
id: '3',
title: 'Apple iPhone X',
description: 'Expedita sequi perferendis quod illum pariatur aliquam, alias laboriosam! Vero blanditiis placeat, mollitia necessitatibus reprehenderit. Labore dolores amet quos, accusamus earum asperiores officiis assumenda optio architecto quia neque, quae eum.'
},
]
},
getters: {
products({ state }) {
return state.products;
}
},
actions: {
addProduct({ state }, product) {
state.products = [...state.products, product];
},
},
const agree = () => {
state.disclaimerAgreement = true
}
export default () => ({
isAgreed: computed(() => state.disclaimerAgreement),
agree
})
export default store;

View File

@@ -1,177 +1,539 @@
<template>
<f7-page name="detect">
<!-- Top Navbar -->
<f7-navbar :sliding="false" back-link="Back">
<f7-nav-title sliding>{{ regions[activeRegion] }}</f7-nav-title>
</f7-navbar>
<f7-block style="display: flex; justify-content: flex-start; flex-direction: column; align-items: stretch; height: calc(100% - var(--f7-navbar-height) - var(--f7-safe-area-top) - var(--f7-safe-area-bottom)); box-sizing: border-box; width: 100vw;">
<img :src="imageView" id="im-display" ref="image_src" style="flex: 1 1 0%; object-fit: contain; max-width: 100vw; min-height: 0;" />
<div ref="structure_box" style="border: solid 3px yellow; position: absolute; display: none;" />
<f7-segmented class="image-menu" raised style="margin: 5px; width: 50%; max-width: 400px; min-width: 192px; flex: 0 0 auto;">
<f7-button popover-open="#region-popover">
<img :src="imageRegion" style="height: 100%;" />
</f7-button>
<f7-button @click="setData" :class="(imageLoaded) ? '' : 'disabled'">
<img src="../assets/icons/visibility.svg" style="height: 100%;" />
</f7-button>
<f7-button @click="selectImage">
<img src="../assets/icons/image.svg" style="height: 100%;" />
</f7-button>
<f7-button @click="setData">
<img src="../assets/icons/videocam.svg" style="height: 100%;" />
</f7-button>
</f7-segmented>
<input type="file" ref="image_chooser" @change="getImage()" accept="image/*" style="display: none;"/>
<div v-if="resultData && resultData.detections" class="chip-results" style="flex: 0 0 auto; align-self: center;">
<f7-chip v-for="(result, idx) in resultData.detections" :class="(idx == selectedChip) ? 'selected-chip' : ''" :text="result.label" media=" " :tooltip="result.confidence.toFixed(1)" :media-bg-color="chipColor(result.confidence)" deleteable @click="selectChip(idx)" @delete="deleteChip(idx)" />
</div>
</f7-block>
</f7-page>
<f7-page name="detect" :id="detectorName + '-detect-page'">
<!-- Top Navbar -->
<f7-navbar :sliding="false" back-link="true" back-link-url="/" back-link-force>
<f7-nav-title sliding>{{ regions[activeRegion] }}</f7-nav-title>
</f7-navbar>
<f7-block class="detect-grid">
<div class="image-container">
<canvas id="im-draw" ref="image_cvs" :style="`display: ${imageLoaded ? 'block' : 'none'}; flex: 1 1 0%; object-fit: contain; 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" icon="image" fill-color="var(--avn-theme-color)" @click="selectImage" />
</div>
<div v-if="(resultData && resultData.detections) || detecting" 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 })"
:class="(result.resultIndex == selectedChip) ? 'selected-chip' : ''"
:text="result.label"
media=" "
:tooltip="result.confidence.toFixed(1)"
deleteable
@click="selectChip(result.resultIndex)"
@delete="deleteChip(result.resultIndex)"
:style="chipGradient(result.confidence)"
/>
<span v-if="numResults == 0 && !detecting" style="height: var(--f7-chip-height); font-size: calc(var(--f7-chip-height) - 4px); font-weight: bolder; margin: 2px;">No results.</span>
<f7-preloader v-if="detecting" size="32" style="color: var(--avn-theme-color);" />
</div>
<div v-if="showDetectSettings" class="detect-inputs" style="grid-area: detect-settings;">
<f7-range class="level-slide-horz" :min="0" :max="100" :step="1" @range:change="onLevelChange" v-model:value="detectorLevel" type="range" style="flex: 1 1 50px"/>
<f7-range class="level-slide-vert" vertical :min="0" :max="100" :step="1" @range:change="onLevelChange" v-model:value="detectorLevel" type="range" style="flex: 1 1 50px"/>
<f7-button @click="() => detectPanel = !detectPanel" :panel-open="!detectPanel && `#${detectorName}-settings`" :panel-close="detectPanel && `#${detectorName}-settings`">
<SvgIcon icon="check_list"/>
</f7-button>
<f7-button @click="setData">
<SvgIcon icon="refresh_search"/>
</f7-button>
</div>
<f7-segmented class="image-menu" raised>
<f7-button popover-open="#region-popover">
<RegionIcon :region="activeRegion" />
</f7-button>
<f7-button popover-open="#capture-popover">
<SvgIcon icon="image"/>
</f7-button>
<f7-button @click="() => showDetectSettings = !showDetectSettings" :class="(imageLoaded) ? '' : 'disabled'">
<SvgIcon icon="visibility"/>
</f7-button>
<f7-button :class="(numResults && uploadDirty && viewedAll) ? '' : 'disabled'" @click="submitData">
<SvgIcon :icon="(uploadUid) ? 'cloud_done' : 'cloud_upload'"/>
</f7-button>
</f7-segmented>
<input type="file" ref="image_chooser" @change="getImage()" accept="image/*" capture="environment" style="display: none;"/>
</f7-block>
<f7-panel :id="detectorName + '-settings'" right cover :backdrop="false" :container-el="`#${detectorName}-detect-page`">
<f7-page>
<f7-navbar title="Detection Settings"></f7-navbar>
<f7-list>
<f7-list-button title="Close List" @click="() => detectPanel = false" :panel-close="`#${detectorName}-settings`"></f7-list-button>
<f7-list-item checkbox checked checkbox-icon="end" title="All/none" @change="selectAll"></f7-list-item>
<f7-list-item v-for="structure in detectorLabels" :key="structure.name" checkbox checkbox-icon="end" v-model:checked="structure.detect" :title="structure.name"></f7-list-item>
</f7-list>
</f7-page>
</f7-panel>
<f7-popover id="region-popover" class="popover-button-menu">
<f7-segmented raised class="segment-button-menu">
<f7-button style="height: auto; width: auto;" href="/detect/thorax/" popover-close="#region-popover">
<RegionIcon :region="0" />
</f7-button>
<f7-button style="height: auto; width: auto;" href="/detect/abdomen/" popover-close="#region-popover">
<RegionIcon :region="1" />
</f7-button>
<f7-button style="height: auto; width: auto;" href="/detect/limbs/" popover-close="#region-popover">
<RegionIcon :region="2" />
</f7-button>
<f7-button class="disabled" style="height: auto; width: auto;" href="/detect/head/" popover-close="#region-popover">
<RegionIcon :region="3" />
</f7-button>
</f7-segmented>
</f7-popover>
<f7-popover id="capture-popover" class="popover-button-menu">
<f7-segmented raised class="segment-button-menu">
<f7-button style="height: auto; width: auto;" popover-close="#capture-popover" class="disabled" @click="videoStream">
<SvgIcon icon="videocam"/>
</f7-button>
<f7-button style="height: auto; width: auto;" popover-close="#capture-popover" @click="selectImage('camera')">
<SvgIcon icon="photo_camera" />
</f7-button>
<f7-button style="height: auto; width: auto;" popover-close="#capture-popover" @click="selectImage('file')">
<SvgIcon icon="photo_library" />
</f7-button>
</f7-segmented>
</f7-popover>
</f7-page>
</template>
<style>
.detect-grid {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 1fr auto auto min-content;
grid-template-areas:
"image-view"
"result-view"
"detect-settings"
"menu-view";
justify-items: center;
height: calc(100% - var(--f7-navbar-height) - var(--f7-safe-area-top) - var(--f7-safe-area-bottom));
max-height: calc(100% - var(--f7-navbar-height) - var(--f7-safe-area-top) - var(--f7-safe-area-bottom));
}
.image-container {
grid-area: image-view;
max-width: 100%;
max-height: 100%;
min-width: 0;
min-height: 0;
position: relative;
display: flex;
align-self: stretch;
}
.popover-button-menu {
max-width: 90vw;
max-height: 90vh;
width: auto;
}
.segment-button-menu {
flex-wrap: nowrap;
flex-direction: column;
max-height: 100%;
min-height: 0px;
}
.chip-media {
background-color: var(--chip-media-background) !important;
}
.chip-results {
display: flex;
flex-wrap: wrap;
gap: 5px;
padding: 10px;
--f7-chip-border-radius: 16px;
--f7-chip-media-size: 32px;
--f7-chip-font-weight: normal;
}
.chip-results .chip {
padding-left: 8px;
}
.selected-chip {
font-weight: 500;
box-shadow: 4px 4px 1px var(--avn-theme-color);
transform: translate(-2px, -2px);
}
.detect-inputs {
display: flex;
align-items: center;
margin: 5px;
width: 100%;
max-width: 400px;
min-width: 192px;
}
.level-slide-vert {
display: none;
}
.image-menu {
grid-area: menu-view;
margin: 5px;
/*width: 90%;*/
max-width: 400px;
min-width: 192px;
}
.image-menu .button {
aspect-ratio: 1;
height: auto;
padding: 5px;
flex: 1 1 0%;
}
.image-menu > .button > svg {
aspect-ratio: 1;
height: auto;
width: 100%;
}
.segment-button-menu .button {
padding: 8px;
aspect-ratio: 1;
width: auto;
flex: 1 1 0%;
max-height: 100px;
max-width: 100px;
}
@media (max-height: 450px) and (orientation: landscape) {
.detect-grid {
grid-template-columns: minmax(0,1fr) max-content auto auto;
grid-template-rows: calc(100vh - var(--f7-navbar-height) - var(--f7-safe-area-top) - var(--f7-safe-area-bottom) - 64px);
grid-template-areas:
"image-view result-view detect-settings menu-view";
justify-items: stretch;
align-items: stretch;
height: calc(100% - var(--f7-navbar-height) - var(--f7-safe-area-top) - var(--f7-safe-area-bottom));
max-height: calc(100% - var(--f7-navbar-height) - var(--f7-safe-area-top) - var(--f7-safe-area-bottom));
position: relative;
}
.chip-results {
display: flex;
flex-wrap: wrap;
gap: 5px;
--f7-chip-border-radius: 16px;
--f7-chip-media-size: 32px;
--f7-chip-font-weight: normal;
flex-direction: column;
max-height: 100%;
justify-self: start;
flex-wrap: nowrap;
overflow-y: scroll;
}
.detect-inputs {
flex-direction: column;
min-width: 0;
max-width: 72px;
}
.chip-results .chip {
padding-left: 8px;
.level-slide-horz {
display: none;
}
.selected-chip {
font-weight: 500;
box-shadow: 4px 4px 1px var(--f7-theme-color);
transform: translate(-2px, -2px);
.level-slide-vert {
display: block;
}
.image-container {
flex-direction: column;
}
.image-menu {
flex-direction: column;
aspect-ratio: .25;
width: auto;
min-width: 0;
height: 100%;
}
.image-menu .button {
aspect-ratio: 1;
height: auto;
padding: 5px;
aspect-ratio: 1;
width: auto;
height: 100%;
flex: 1 1 0%;
border-bottom: 1px solid var(--f7-segmented-raised-divider-color);
border-bottom-left-radius: 0px !important;
}
.segment-button-menu {
flex-direction: row;
max-height: 100%;
min-height: 0px;
}
.segment-button-menu .button {
height: auto;
flex: 1 1 0%;
max-height: 100px;
max-width: 100px;
}
.button > svg {
width: 100%;
height: auto;
}
}
</style>
<script>
import { f7 } from 'framework7-vue'
import { f7 } from 'framework7-vue'
export default {
props: {
f7route: Object,
},
data () {
return {
regions: ['Thorax','Abdomen/Pelvis','Limbs','Head and Neck'],
resultData: {},
selectedChip: -1,
activeRegion: 4,
imageRegion: '',
imageLoaded: false,
imageView: '../assets/icons/image.svg',
reader: new FileReader(),
detectorName: ''
}
},
created () {
switch (this.f7route.params.region) {
case 'thorax':
this.activeRegion = 0
this.imageRegion = '../assets/regions/thorax.svg'
this.detectorName = 'thorax'
break;
case 'abdomen':
this.activeRegion = 1
this.imageRegion = '../assets/regions/abdpel.svg'
this.detectorName = 'combined'
break;
case 'limbs':
this.activeRegion = 2
this.imageRegion = '../assets/regions/limb.svg'
this.detectorName = 'defaultNew'
break;
case 'head':
this.activeRegion = 3
this.imageRegion = '../assets/regions/headneck.svg'
break;
}
},
methods: {
chipColor (confVal) {
if (confVal >= 90) return 'green'
if (confVal >= 70) return 'lime'
return 'yellow'
},
setData () {
var self = this
var loadServerSettings = localStorage.getItem('serverSettings')
if (loadServerSettings) var serverSettings = JSON.parse(loadServerSettings)
if (serverSettings && serverSettings.use) {
var modelURL = `http://${serverSettings.address}:${serverSettings.port}/detect`
var xhr = new XMLHttpRequest()
xhr.open("POST", modelURL)
xhr.setRequestHeader('Content-Type', 'application/json')
xhr.onload = function () {
if (this.status !== 200) {
//this.response.text().then(function(message){alert(message)})
console.log(this.response)
return;
}
self.resultData = JSON.parse(this.response)
}
var doodsData = {
"detector_name": this.detectorName,
"detect": {
"*":50
}
}
doodsData.data = this.reader.result.split(',')[1]
import RegionIcon from '../components/region-icon.vue'
import SvgIcon from '../components/svg-icon.vue'
xhr.send(JSON.stringify(doodsData))
} else {
f7.dialog.alert('Using built-in model')
}
},
selectImage () {
var loadResult = this.$refs.image_chooser.click()
},
selectChip ( iChip ) {
var imgWidth
var imgHeight
import submitMixin from './submit-mixin'
this.selectedChip = iChip
const box = this.$refs.structure_box
const img = this.$refs.image_src
var imgAspect = img.naturalWidth / img.naturalHeight
var rendAspect = img.offsetWidth / img.offsetHeight
if (imgAspect >= rendAspect) {
imgWidth = img.offsetWidth
imgHeight = img.offsetWidth / imgAspect
} else {
imgWidth = img.offsetHeight * imgAspect
imgHeight = img.offsetHeight
}
box.style.display = "block"
box.style.left = `${(img.offsetWidth - imgWidth) / 2 + this.resultData.detections[iChip].left * imgWidth}px`
box.style.top = `${(img.offsetHeight - imgHeight) / 2 + this.resultData.detections[iChip].top * imgHeight}px`
box.style.width = `${(this.resultData.detections[iChip].right - this.resultData.detections[iChip].left) * imgWidth}px`
box.style.height = `${(this.resultData.detections[iChip].bottom - this.resultData.detections[iChip].top) * imgHeight}px`
},
deleteChip ( iChip ) {
f7.dialog.confirm(`${this.resultData.detections[iChip].label} is identified with ${this.resultData.detections[iChip].confidence.toFixed(1)}% confidence. Are you sure you want to delete it?`, () => {
this.resultData.detections.splice(iChip, 1)
});
},
getImage () {
const example = this.$refs.image_chooser.files[0];
this.imageView = URL.createObjectURL(example);
this.reader.readAsDataURL(example)
this.imageLoaded = true;
}
export default {
mixins: [submitMixin],
props: {
f7route: Object,
},
components: {
RegionIcon,
SvgIcon
},
data () {
return {
regions: ['Thorax','Abdomen/Pelvis','Limbs','Head and Neck'],
resultData: {},
selectedChip: -1,
activeRegion: 4,
imageLoaded: false,
imageView: null,
imageLoadMode: "environment",
detecting: false,
detectPanel: false,
showDetectSettings: false,
detectorName: '',
detectorLevel: 50,
detectorLabels: [],
serverSettings: {},
isCordova: !!window.cordova,
uploadUid: null,
uploadDirty: false
}
},
created () {
switch (this.f7route.params.region) {
case 'thorax':
this.activeRegion = 0
this.detectorName = 'thorax'
break;
case 'abdomen':
this.activeRegion = 1
this.detectorName = 'combined'
break;
case 'limbs':
this.activeRegion = 2
this.detectorName = 'defaultNew'
break;
case 'head':
this.activeRegion = 3
break;
}
var loadServerSettings = localStorage.getItem('serverSettings')
if (loadServerSettings) this.serverSettings = JSON.parse(loadServerSettings)
var self = this
if (this.serverSettings && this.serverSettings.use) {
var modelURL = `http://${this.serverSettings.address}:${this.serverSettings.port}/detectors`
var xhr = new XMLHttpRequest()
xhr.open("GET", modelURL)
xhr.setRequestHeader('Content-Type', 'application/json')
xhr.onload = function () {
if (this.status !== 200) {
//this.response.text().then(function(message){alert(message)})
console.log(xhr.response)
return;
}
var detectors = JSON.parse(xhr.response).detectors
var findLabel = detectors
.find( d => { return d.name == self.detectorName } )?.labels
.filter( l => { return l != "" } ).sort()
.map( l => { return {'name': l, 'detect': true} } )
self.detectorLabels = findLabel || []
}
xhr.send()
}
},
computed: {
showResults () {
var filteredResults = this.resultData.detections
if (!filteredResults) return []
var allSelect = this.detectorLabels.every( s => { return s.detect } )
var selectedLabels = this.detectorLabels
.filter( l => { return l.detect })
.map( l => { return l.name })
filteredResults.forEach( (d, i) => {
filteredResults[i].resultIndex = i
filteredResults[i].aboveThreshold = d.confidence >= this.detectorLevel
filteredResults[i].isSearched = allSelect || selectedLabels.includes(d.label)
})
return filteredResults
},
numResults () {
return this.showResults.filter( r => { return r.aboveThreshold && r.isSearched && !r.isDeleted }).length
},
viewedAll () {
if (this.resultData && this.resultData.detections) {
return this.resultData.detections
.filter( s => { return s.confidence >= this.detectorLevel})
.every( s => { return s.beenViewed })
} else {
return false
}
}
},
methods: {
chipGradient (confVal) {
return `--chip-media-background: hsl(${confVal / 100 * 120}deg 100% 50%)`
},
setData () {
var self = this
if (this.serverSettings && this.serverSettings.use) {
var modelURL = `http://${this.serverSettings.address}:${this.serverSettings.port}/detect`
var xhr = new XMLHttpRequest()
xhr.open("POST", modelURL)
xhr.setRequestHeader('Content-Type', 'application/json')
xhr.onload = function () {
if (this.status !== 200) {
//this.response.text().then(function(message){alert(message)})
console.log(xhr.response)
self.detecting = false
return;
}
self.resultData = JSON.parse(xhr.response)
self.uploadDirty = true
self.detecting = false
}
var doodsData = {
"detector_name": this.detectorName,
"detect": {
"*": 1
},
"data": this.imageView.src.split(',')[1]
}
xhr.send(JSON.stringify(doodsData))
} else {
//TODO
f7.dialog.alert('Using built-in model')
}
},
selectAll (ev) {
if (ev.target.checked) {
this.detectorLabels.forEach( s => s.detect = true )
} else {
this.detectorLabels.forEach( s => s.detect = false )
}
},
selectImage (mode) {
this.imageLoadMode = mode
if (mode == "camera") {
this.$refs.image_chooser.setAttribute("capture","environment")
} else {
this.$refs.image_chooser.removeAttribute("capture")
}
if (this.isCordova && mode == "camera") {
navigator.camera.getPicture(this.getImage, this.onFail, { quality: 50, destinationType: Camera.DestinationType.DATA_URL, correctOrientation: true });
} else {
var loadResult = this.$refs.image_chooser.click()
}
},
onFail (message) {
alert(`Camera fail: ${message}`)
},
selectChip ( iChip ) {
const [imCanvas, imageCtx] = this.resetView()
if (this.selectedChip == iChip) {
this.selectedChip = -1
return
}
var imgWidth
var imgHeight
this.selectedChip = iChip
var imgAspect = this.imageView.width / this.imageView.height
var rendAspect = imCanvas.width / imCanvas.height
if (imgAspect >= rendAspect) {
imgWidth = imCanvas.width
imgHeight = imCanvas.width / imgAspect
} else {
imgWidth = imCanvas.height * imgAspect
imgHeight = imCanvas.height
}
var boxLeft = (imCanvas.width - imgWidth) / 2 + this.resultData.detections[iChip].left * imgWidth
var boxTop = (imCanvas.height - imgHeight) / 2 + this.resultData.detections[iChip].top * imgHeight
var boxWidth = (Math.min(this.resultData.detections[iChip].right, 1) - Math.max(this.resultData.detections[iChip].left, 0)) * imgWidth
var boxHeight = (Math.min(this.resultData.detections[iChip].bottom, 1) - Math.max(this.resultData.detections[iChip].top, 0)) * imgHeight
imageCtx.strokeRect(boxLeft,boxTop,boxWidth,boxHeight)
this.resultData.detections[iChip].beenViewed = true
},
deleteChip ( iChip ) {
f7.dialog.confirm(`${this.resultData.detections[iChip].label} is identified with ${this.resultData.detections[iChip].confidence.toFixed(1)}% confidence. Are you sure you want to delete it?`, () => {
this.resetView()
this.resultData.detections.splice(iChip, 1)
this.selectedChip = -1
this.uploadDirty = true
});
},
resetView () {
const imCanvas = this.$refs.image_cvs
const imageCtx = imCanvas.getContext("2d")
imCanvas.width = imCanvas.clientWidth
imCanvas.height = imCanvas.clientHeight
imageCtx.clearRect(0,0,imCanvas.width,imCanvas.height)
imageCtx.strokeStyle = 'yellow'
imageCtx.lineWidth = 3
return [imCanvas, imageCtx]
},
getImage (searchImage) {
let loadImage =new Promise(resolve => {
if (this.isCordova && this.imageLoadMode == "camera") {
resolve('data:image/jpg;base64,' + searchImage)
} else {
const searchImage = this.$refs.image_chooser.files[0]
var reader = new FileReader()
reader.addEventListener("loadend", () => {
this.detecting = true
resolve(reader.result)
})
reader.readAsDataURL(searchImage)
}
})
loadImage.then((imgData) => {
this.imageLoaded = true
this.resultData = {}
this.imageView = new Image()
this.imageView.src = imgData
return(this.imageView.decode())
}).then( () => {
const [imCanvas, _] = this.resetView()
imCanvas.style['background-image'] = `url(${this.imageView.src})`
this.setData()
}).catch((e) => {
console.log(e.message)
f7.dialog.alert(`Error loading image: ${e.message}`)
})
},
videoStream() {
//TODO
return null
},
async submitData () {
var uploadData = this.showResults
.filter( d => { return d.aboveThreshold && d.isSearched && !d.isDeleted })
.map( r => { return {"top": r.top, "left": r.left, "bottom": r.bottom, "right": r.right, "label": r.label}})
this.uploadUid = await this.uploadData(this.imageView.src.split(',')[1],uploadData,this.uploadUid)
if (this.uploadUid) { this.uploadDirty = false }
},
onLevelChange(value) {
this.detectorLevel = value
}
}
}
</script>

View File

@@ -1,40 +0,0 @@
<template>
<f7-page>
<f7-navbar title="Dynamic Route" back-link="Back"></f7-navbar>
<f7-block strong inset>
<ul>
<li><b>Url:</b> {{ f7route.url }}</li>
<li><b>Path:</b> {{ f7route.path }}</li>
<li><b>Hash:</b> {{ f7route.hash }}</li>
<li>
<b>Params:</b>
<ul>
<li v-for="(value, key) in f7route.params" :key="key">
<b>{{ key }}:</b> {{ value }}
</li>
</ul>
</li>
<li>
<b>Query:</b>
<ul>
<li v-for="(value, key) in f7route.query" :key="key">
<b>{{ key }}:</b> {{ value }}
</li>
</ul>
</li>
<li><b>Route:</b> {{ f7route.route.path }}</li>
</ul>
</f7-block>
<f7-block strong inset>
<f7-link @click="f7router.back()">Go back via Router API</f7-link>
</f7-block>
</f7-page>
</template>
<script>
export default {
props: {
f7route: Object,
f7router: Object,
},
};
</script>

View File

@@ -8,24 +8,98 @@
<f7-nav-title sliding>A.L.V.I.N.N.</f7-nav-title>
</f7-navbar>
<!-- Page content-->
<div style="display: flex; flex-direction: column; justify-content: center; align-items: center; width: 100%;">
<h2>Anatomy Lab Visual Identification Neural Network</h2>
<h4>Veterinary Anatomy Edition</h4>
<p>Select a region to begin</p>
<f7-segmented raised style="flex-wrap: wrap;">
<f7-button style="height: auto; width: auto;" href="/detect/thorax/">
<img src="../assets/regions/thorax.svg" />
<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>
<h4 style="text-align: center; margin: 0;">Veterinary Anatomy Edition</h4>
<p style="text-align: center; margin: 0;">Select a region to begin.</p>
<div class="region-grid">
<!--</f7-button><f7-button :class="`region-button thorax`" :href="'/detect/thorax/'">-->
<f7-button :class="`region-button thorax${isAgreed ? '' : ' disabled'}`" :href="isAgreed && '/detect/thorax/'">
<RegionIcon class="region-image" :region="0" />
</f7-button>
<f7-button style="height: auto; width: auto;" href="/detect/abdomen/">
<img src="../assets/regions/abdpel.svg" />
<!--<f7-button :class="`region-button abdomen${siteSettings.siteAgreement ? '' : ' disabled'}`" :href="siteSettings.siteAgreement && '/detect/abdomen/'">-->
<f7-button :class="`region-button abdomen${isAgreed ? '' : ' disabled'}`" :href="isAgreed && '/detect/abdomen/'">
<RegionIcon class="region-image" :region="1" />
</f7-button>
<f7-button style="height: auto; width: auto;" href="/detect/limbs/">
<img src="../assets/regions/limb.svg" />
<!--<f7-button :class="`region-button limbs${siteSettings.siteAgreement ? '' : ' disabled'}`" :href="siteSettings.siteAgreement && '/detect/limbs/'">-->
<f7-button :class="`region-button limbs${isAgreed ? '' : ' disabled'}`" :href="isAgreed && '/detect/limbs/'">
<RegionIcon class="region-image" :region="2" />
</f7-button>
<f7-button style="height: auto; width: auto;" href="/detect/head/">
<img src="../assets/regions/headneck.svg" />
<!--<f7-button class="region-button headneck disabled" :href="siteSettings.siteAgreement && '/detect/head/'">-->
<f7-button class="region-button headneck disabled" :href="'/detect/head/'">
<RegionIcon class="region-image" :region="3" />
</f7-button>
</f7-segmented>
</div>
</div>
</f7-page>
</template>
</template>
<style>
.region-grid {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: minmax(120px,max-content) minmax(120px,max-content);
justify-items: center;
align-content: center;
gap: 20px 20px;
margin: 30px;
margin-top: 0;
transform: scale(97%);
}
.region-button {
aspect-ratio: 1;
min-width: 100px;
height: auto;
width: 100%;
background-color: var(--avn-button-bg-color);
padding: 5px;
max-width: 200px;
}
.region-image {
width: 100%;
}
@media (min-width: 600px) {
.region-grid {
grid-template-columns: 1fr [thorax] auto [abdomen] auto [limbs] auto [headneck] auto 1fr;
grid-template-rows: minmax(0, 1fr);
}
.thorax {
grid-column-start: thorax;
}
.abdomen {
grid-column-start: abdomen;
}
.limbs {
grid-column-start: limbs;
}
.headneck {
grid-column-start: headneck;
}
.region-button {
max-height: calc(100% - 15px);
}
}
</style>
<script>
import RegionIcon from '../components/region-icon.vue'
import store from '../js/store'
export default {
components: {
RegionIcon
},
setup() {
return store()
}
}
</script>

View File

@@ -15,7 +15,13 @@
<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.port" label="Server port" type="text" placeholder="9001" />
</f7-list>
<f7-button @click="saveServerSettings" >Save</f7-button>
<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-button @click="saveAllSettings" >Save</f7-button>
</div>
</f7-block>
</f7-page>
@@ -27,23 +33,29 @@
export default {
data () {
return {
serverSettings: {
use: false,
address: '10.170.64.22',
port: '9001'
},
serverSettings: {
use: false,
address: '10.170.64.22',
port: '9001'
},
themeSettings: {
darkMode: 'auto'
}
}
},
created () {
var loadServerSettings = localStorage.getItem('serverSettings')
if (loadServerSettings) this.serverSettings = JSON.parse(loadServerSettings)
var loadThemeSettings = localStorage.getItem('themeSettings')
if (loadThemeSettings) this.themeSettings = JSON.parse(loadThemeSettings)
},
methods: {
saveServerSettings () {
saveAllSettings () {
let saveSetting = new Promise(
(saved,failed) => {
try {
localStorage.setItem('serverSettings',JSON.stringify(this.serverSettings))
localStorage.setItem('themeSettings',JSON.stringify(this.themeSettings))
saved()
} catch {
failed()
@@ -67,6 +79,10 @@
}
)
},
setDarkMode (mode) {
this.themeSettings.darkMode = mode
f7.setDarkMode(mode)
}
}
}
</script>

67
src/pages/submit-mixin.js Normal file
View File

@@ -0,0 +1,67 @@
import { f7 } from 'framework7-vue'
export default {
methods: {
newUid (length) {
const uidLength = length || 16
const uidChars = 'abcdefghijklmnopqrstuvwxyz0123456789'
var uid = []
for (var i = 0; i < uidLength; i++) {
uid.push(uidChars.charAt(Math.floor(Math.random() * ((i < 4) ? 26 : 36))))
}
return uid.join('')
},
uploadData (imagePayload, classPayload, prevUid) {
let uploadImage = new Promise (resolve => {
const dataUid = prevUid || this.newUid(16)
var byteChars = window.atob(imagePayload)
var byteArrays = []
var len = byteChars.length
for (var offset = 0; offset < len; offset += 1024) {
var slice = byteChars.slice(offset, offset + 1024)
var byteNumbers = new Array(slice.length)
for (var i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i)
}
var byteArray = new Uint8Array(byteNumbers)
byteArrays.push(byteArray)
}
var imageBlob = new Blob(byteArrays, {type: 'image/jpeg'})
var xhrJpg = new XMLHttpRequest()
var uploadUrl = `https://nextcloud.azgeorgis.net/public.php/webdav/${dataUid}.jpeg`
xhrJpg.open("PUT", uploadUrl)
xhrJpg.setRequestHeader('Content-Type', 'image/jpeg')
xhrJpg.setRequestHeader('X-Method-Override', 'PUT')
xhrJpg.setRequestHeader('X-Requested-With', 'XMLHttpRequest')
xhrJpg.setRequestHeader("Authorization", "Basic " + btoa("LKBm3H6JdSaywyg:"))
xhrJpg.send(imageBlob)
var xhrTxt = new XMLHttpRequest()
var uploadUrl = `https://nextcloud.azgeorgis.net/public.php/webdav/${dataUid}.txt`
xhrTxt.open("PUT", uploadUrl)
xhrTxt.setRequestHeader('Content-Type', 'text/plain')
xhrTxt.setRequestHeader('X-Method-Override', 'PUT')
xhrTxt.setRequestHeader('X-Requested-With', 'XMLHttpRequest')
xhrTxt.setRequestHeader("Authorization", "Basic " + btoa("LKBm3H6JdSaywyg:"))
xhrTxt.send(JSON.stringify(classPayload))
resolve(dataUid)
})
return uploadImage.then((newUid) => {
var toast = f7.toast.create({
text: 'Detections Uploaded: thank you.',
closeTimeout: 2000
})
toast.open()
return newUid
}).catch((e) => {
console.log(e.message)
f7.dialog.alert(`Error uploading image: ${e.message}`)
return null
})
}
}
}