Spaces:
Sleeping
Sleeping
Updated the Project
Browse files- .claude/settings.local.json +22 -0
- .gitattributes +35 -35
- .gitignore +222 -0
- .superpowers/brainstorm/1147-1782279753/content/backend-choice.html +81 -0
- .superpowers/brainstorm/1147-1782279753/content/layout-v2.html +71 -0
- .superpowers/brainstorm/1147-1782279753/content/layout.html +92 -0
- .superpowers/brainstorm/1147-1782279753/content/waiting.html +3 -0
- .superpowers/brainstorm/1147-1782279753/state/server-info +1 -0
- Dockerfile +19 -19
- README.md +19 -19
- requirements.txt +19 -3
- src/backends/__init__.py +0 -0
- src/backends/deepface_backend.py +36 -0
- src/backends/hf_backend.py +66 -0
- src/backends/insightface_backend.py +47 -0
- src/streamlit_app.py +108 -38
.claude/settings.local.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"permissions": {
|
| 3 |
+
"allow": [
|
| 4 |
+
"WebSearch",
|
| 5 |
+
"WebFetch(domain:www.sciencedirect.com)",
|
| 6 |
+
"WebFetch(domain:link.springer.com)",
|
| 7 |
+
"WebFetch(domain:pmc.ncbi.nlm.nih.gov)",
|
| 8 |
+
"WebFetch(domain:sefiks.com)",
|
| 9 |
+
"WebFetch(domain:lookscompare.com)",
|
| 10 |
+
"WebFetch(domain:idp.springer.com)",
|
| 11 |
+
"WebFetch(domain:github.com)",
|
| 12 |
+
"WebFetch(domain:arxiv.org)",
|
| 13 |
+
"Bash(gh api *)",
|
| 14 |
+
"WebFetch(domain:rigr.ai)",
|
| 15 |
+
"WebFetch(domain:www.kinfacew.com)",
|
| 16 |
+
"WebFetch(domain:pubmed.ncbi.nlm.nih.gov)",
|
| 17 |
+
"WebFetch(domain:www.media.io)",
|
| 18 |
+
"WebFetch(domain:www.ncbi.nlm.nih.gov)",
|
| 19 |
+
"WebFetch(domain:raw.githubusercontent.com)"
|
| 20 |
+
]
|
| 21 |
+
}
|
| 22 |
+
}
|
.gitattributes
CHANGED
|
@@ -1,35 +1,35 @@
|
|
| 1 |
-
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
-
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
-
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
-
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
-
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
-
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
-
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
-
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
-
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
-
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
-
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
-
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
-
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
-
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
-
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
-
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
-
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
-
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
-
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
-
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
-
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
-
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
-
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
-
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Byte-compiled / optimized / DLL files
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[codz]
|
| 4 |
+
*$py.class
|
| 5 |
+
|
| 6 |
+
# C extensions
|
| 7 |
+
*.so
|
| 8 |
+
|
| 9 |
+
# Distribution / packaging
|
| 10 |
+
.Python
|
| 11 |
+
build/
|
| 12 |
+
develop-eggs/
|
| 13 |
+
dist/
|
| 14 |
+
downloads/
|
| 15 |
+
eggs/
|
| 16 |
+
.eggs/
|
| 17 |
+
lib/
|
| 18 |
+
lib64/
|
| 19 |
+
parts/
|
| 20 |
+
sdist/
|
| 21 |
+
var/
|
| 22 |
+
wheels/
|
| 23 |
+
share/python-wheels/
|
| 24 |
+
*.egg-info/
|
| 25 |
+
.installed.cfg
|
| 26 |
+
*.egg
|
| 27 |
+
MANIFEST
|
| 28 |
+
|
| 29 |
+
# PyInstaller
|
| 30 |
+
# Usually these files are written by a python script from a template
|
| 31 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
| 32 |
+
*.manifest
|
| 33 |
+
*.spec
|
| 34 |
+
|
| 35 |
+
# Installer logs
|
| 36 |
+
pip-log.txt
|
| 37 |
+
pip-delete-this-directory.txt
|
| 38 |
+
|
| 39 |
+
# Unit test / coverage reports
|
| 40 |
+
htmlcov/
|
| 41 |
+
.tox/
|
| 42 |
+
.nox/
|
| 43 |
+
.coverage
|
| 44 |
+
.coverage.*
|
| 45 |
+
.cache
|
| 46 |
+
nosetests.xml
|
| 47 |
+
coverage.xml
|
| 48 |
+
*.cover
|
| 49 |
+
*.py.cover
|
| 50 |
+
*.lcov
|
| 51 |
+
.hypothesis/
|
| 52 |
+
.pytest_cache/
|
| 53 |
+
cover/
|
| 54 |
+
|
| 55 |
+
# Translations
|
| 56 |
+
*.mo
|
| 57 |
+
*.pot
|
| 58 |
+
|
| 59 |
+
# Django stuff:
|
| 60 |
+
*.log
|
| 61 |
+
local_settings.py
|
| 62 |
+
db.sqlite3
|
| 63 |
+
db.sqlite3-journal
|
| 64 |
+
|
| 65 |
+
# Flask stuff:
|
| 66 |
+
instance/
|
| 67 |
+
.webassets-cache
|
| 68 |
+
|
| 69 |
+
# Scrapy stuff:
|
| 70 |
+
.scrapy
|
| 71 |
+
|
| 72 |
+
# Sphinx documentation
|
| 73 |
+
docs/_build/
|
| 74 |
+
|
| 75 |
+
# PyBuilder
|
| 76 |
+
.pybuilder/
|
| 77 |
+
target/
|
| 78 |
+
|
| 79 |
+
# Jupyter Notebook
|
| 80 |
+
.ipynb_checkpoints
|
| 81 |
+
|
| 82 |
+
# IPython
|
| 83 |
+
profile_default/
|
| 84 |
+
ipython_config.py
|
| 85 |
+
|
| 86 |
+
# pyenv
|
| 87 |
+
# For a library or package, you might want to ignore these files since the code is
|
| 88 |
+
# intended to run in multiple environments; otherwise, check them in:
|
| 89 |
+
# .python-version
|
| 90 |
+
|
| 91 |
+
# pipenv
|
| 92 |
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
| 93 |
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
| 94 |
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
| 95 |
+
# install all needed dependencies.
|
| 96 |
+
# Pipfile.lock
|
| 97 |
+
|
| 98 |
+
# UV
|
| 99 |
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
| 100 |
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
| 101 |
+
# commonly ignored for libraries.
|
| 102 |
+
# uv.lock
|
| 103 |
+
|
| 104 |
+
# poetry
|
| 105 |
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
| 106 |
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
| 107 |
+
# commonly ignored for libraries.
|
| 108 |
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
| 109 |
+
# poetry.lock
|
| 110 |
+
# poetry.toml
|
| 111 |
+
|
| 112 |
+
# pdm
|
| 113 |
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
| 114 |
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
| 115 |
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
| 116 |
+
# pdm.lock
|
| 117 |
+
# pdm.toml
|
| 118 |
+
.pdm-python
|
| 119 |
+
.pdm-build/
|
| 120 |
+
|
| 121 |
+
# pixi
|
| 122 |
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
| 123 |
+
# pixi.lock
|
| 124 |
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
| 125 |
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
| 126 |
+
.pixi/*
|
| 127 |
+
!.pixi/config.toml
|
| 128 |
+
|
| 129 |
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
| 130 |
+
__pypackages__/
|
| 131 |
+
|
| 132 |
+
# Celery stuff
|
| 133 |
+
celerybeat-schedule*
|
| 134 |
+
celerybeat.pid
|
| 135 |
+
|
| 136 |
+
# Redis
|
| 137 |
+
*.rdb
|
| 138 |
+
*.aof
|
| 139 |
+
*.pid
|
| 140 |
+
|
| 141 |
+
# RabbitMQ
|
| 142 |
+
mnesia/
|
| 143 |
+
rabbitmq/
|
| 144 |
+
rabbitmq-data/
|
| 145 |
+
|
| 146 |
+
# ActiveMQ
|
| 147 |
+
activemq-data/
|
| 148 |
+
|
| 149 |
+
# SageMath parsed files
|
| 150 |
+
*.sage.py
|
| 151 |
+
|
| 152 |
+
# Environments
|
| 153 |
+
.env
|
| 154 |
+
.envrc
|
| 155 |
+
.venv
|
| 156 |
+
env/
|
| 157 |
+
venv/
|
| 158 |
+
ENV/
|
| 159 |
+
env.bak/
|
| 160 |
+
venv.bak/
|
| 161 |
+
|
| 162 |
+
# Spyder project settings
|
| 163 |
+
.spyderproject
|
| 164 |
+
.spyproject
|
| 165 |
+
|
| 166 |
+
# Rope project settings
|
| 167 |
+
.ropeproject
|
| 168 |
+
|
| 169 |
+
# mkdocs documentation
|
| 170 |
+
/site
|
| 171 |
+
|
| 172 |
+
# mypy
|
| 173 |
+
.mypy_cache/
|
| 174 |
+
.dmypy.json
|
| 175 |
+
dmypy.json
|
| 176 |
+
|
| 177 |
+
# Pyre type checker
|
| 178 |
+
.pyre/
|
| 179 |
+
|
| 180 |
+
# pytype static type analyzer
|
| 181 |
+
.pytype/
|
| 182 |
+
|
| 183 |
+
# Cython debug symbols
|
| 184 |
+
cython_debug/
|
| 185 |
+
|
| 186 |
+
# PyCharm
|
| 187 |
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
| 188 |
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
| 189 |
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
| 190 |
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
| 191 |
+
# .idea/
|
| 192 |
+
|
| 193 |
+
# Abstra
|
| 194 |
+
# Abstra is an AI-powered process automation framework.
|
| 195 |
+
# Ignore directories containing user credentials, local state, and settings.
|
| 196 |
+
# Learn more at https://abstra.io/docs
|
| 197 |
+
.abstra/
|
| 198 |
+
|
| 199 |
+
# Visual Studio Code
|
| 200 |
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
| 201 |
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
| 202 |
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
| 203 |
+
# you could uncomment the following to ignore the entire vscode folder
|
| 204 |
+
# .vscode/
|
| 205 |
+
# Temporary file for partial code execution
|
| 206 |
+
tempCodeRunnerFile.py
|
| 207 |
+
|
| 208 |
+
# Ruff stuff:
|
| 209 |
+
.ruff_cache/
|
| 210 |
+
|
| 211 |
+
# PyPI configuration file
|
| 212 |
+
.pypirc
|
| 213 |
+
|
| 214 |
+
# Marimo
|
| 215 |
+
marimo/_static/
|
| 216 |
+
marimo/_lsp/
|
| 217 |
+
__marimo__/
|
| 218 |
+
|
| 219 |
+
# Streamlit
|
| 220 |
+
.streamlit/secrets.toml
|
| 221 |
+
.claude
|
| 222 |
+
.superpowers
|
.superpowers/brainstorm/1147-1782279753/content/backend-choice.html
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<h2>Choose your ML backend</h2>
|
| 2 |
+
<p class="subtitle">Select one β shown as a horizontal toggle at the top of the app</p>
|
| 3 |
+
|
| 4 |
+
<div class="mockup">
|
| 5 |
+
<div class="mockup-header">MoreCloseToWhom β App Preview</div>
|
| 6 |
+
<div class="mockup-body">
|
| 7 |
+
|
| 8 |
+
<!-- Top selector bar -->
|
| 9 |
+
<div style="display:flex;gap:0;border:1px solid #2a3a5a;border-radius:8px;overflow:hidden;margin-bottom:20px">
|
| 10 |
+
<div class="backend-opt selected-opt" data-choice="deepface" onclick="toggleSelect(this)" style="flex:1;padding:10px 8px;text-align:center;background:#0f3460;cursor:pointer;border-right:1px solid #2a3a5a">
|
| 11 |
+
<div style="font-size:13px;color:#7ec8e3;font-weight:bold">π§ DeepFace</div>
|
| 12 |
+
<div style="font-size:9px;color:#aaa;margin-top:2px">Easy Β· pip install deepface</div>
|
| 13 |
+
<div style="font-size:9px;color:#888;margin-top:1px">Age MAE ~10yr</div>
|
| 14 |
+
</div>
|
| 15 |
+
<div class="backend-opt" onclick="toggleSelect(this)" style="flex:1;padding:10px 8px;text-align:center;background:#16213e;cursor:pointer;border-right:1px solid #2a3a5a">
|
| 16 |
+
<div style="font-size:13px;color:#c8d8e8;font-weight:bold">β‘ InsightFace</div>
|
| 17 |
+
<div style="font-size:9px;color:#aaa;margin-top:2px">Faster Β· better accuracy</div>
|
| 18 |
+
<div style="font-size:9px;color:#888;margin-top:1px">Age MAE ~7.5yr</div>
|
| 19 |
+
</div>
|
| 20 |
+
<div class="backend-opt" onclick="toggleSelect(this)" style="flex:1;padding:10px 8px;text-align:center;background:#16213e;cursor:pointer">
|
| 21 |
+
<div style="font-size:13px;color:#c8d8e8;font-weight:bold">π€ HF Model</div>
|
| 22 |
+
<div style="font-size:9px;color:#aaa;margin-top:2px">HF-native Β· custom model</div>
|
| 23 |
+
<div style="font-size:9px;color:#888;margin-top:1px">Manual setup</div>
|
| 24 |
+
</div>
|
| 25 |
+
</div>
|
| 26 |
+
|
| 27 |
+
<!-- Upload row: Father | Child (center) | Mother -->
|
| 28 |
+
<div style="display:flex;gap:16px;align-items:flex-end;margin-bottom:16px">
|
| 29 |
+
<div style="flex:1;background:#16213e;border:1px dashed #4a4a7a;border-radius:8px;padding:14px;text-align:center">
|
| 30 |
+
<div style="font-size:24px;margin-bottom:4px">π¨</div>
|
| 31 |
+
<div style="font-size:10px;color:#7ec8e3;font-weight:bold;margin-bottom:4px">FATHER</div>
|
| 32 |
+
<div style="background:#0d1b2a;border-radius:4px;padding:5px;font-size:9px;color:#555">Drop photo here</div>
|
| 33 |
+
</div>
|
| 34 |
+
<div style="flex:1.3;background:#1a2a1a;border:2px solid #3a7a3a;border-radius:10px;padding:18px;text-align:center">
|
| 35 |
+
<div style="font-size:30px;margin-bottom:4px">π§</div>
|
| 36 |
+
<div style="font-size:10px;color:#7ec8e3;font-weight:bold;margin-bottom:4px">CHILD</div>
|
| 37 |
+
<div style="background:#0d1b0d;border-radius:4px;padding:5px;font-size:9px;color:#555">Drop photo here</div>
|
| 38 |
+
</div>
|
| 39 |
+
<div style="flex:1;background:#16213e;border:1px dashed #4a4a7a;border-radius:8px;padding:14px;text-align:center">
|
| 40 |
+
<div style="font-size:24px;margin-bottom:4px">π©</div>
|
| 41 |
+
<div style="font-size:10px;color:#c8a0d8;font-weight:bold;margin-bottom:4px">MOTHER</div>
|
| 42 |
+
<div style="background:#0d1b2a;border-radius:4px;padding:5px;font-size:9px;color:#555">Drop photo here</div>
|
| 43 |
+
</div>
|
| 44 |
+
</div>
|
| 45 |
+
|
| 46 |
+
<div style="text-align:center;margin-bottom:16px">
|
| 47 |
+
<div style="display:inline-block;background:#1a6b3a;border-radius:6px;padding:8px 28px;font-size:12px;color:#fff">βΆ Analyze Resemblance</div>
|
| 48 |
+
</div>
|
| 49 |
+
|
| 50 |
+
<div style="background:#16213e;border-radius:8px;padding:12px">
|
| 51 |
+
<div style="font-size:11px;color:#7ec8e3;font-weight:bold;margin-bottom:8px">Results</div>
|
| 52 |
+
<div style="display:flex;gap:14px">
|
| 53 |
+
<div style="flex:1">
|
| 54 |
+
<div style="font-size:9px;color:#aaa;margin-bottom:3px">π¨ Father resemblance</div>
|
| 55 |
+
<div style="background:#0d1b2a;border-radius:3px;height:7px;margin-bottom:2px">
|
| 56 |
+
<div style="background:#3a8fd4;width:84%;height:7px;border-radius:3px"></div>
|
| 57 |
+
</div>
|
| 58 |
+
<div style="font-size:10px;color:#3a8fd4;font-weight:bold">84%</div>
|
| 59 |
+
</div>
|
| 60 |
+
<div style="flex:1">
|
| 61 |
+
<div style="font-size:9px;color:#aaa;margin-bottom:3px">π© Mother resemblance</div>
|
| 62 |
+
<div style="background:#0d1b2a;border-radius:3px;height:7px;margin-bottom:2px">
|
| 63 |
+
<div style="background:#a060c0;width:71%;height:7px;border-radius:3px"></div>
|
| 64 |
+
</div>
|
| 65 |
+
<div style="font-size:10px;color:#a060c0;font-weight:bold">71%</div>
|
| 66 |
+
</div>
|
| 67 |
+
<div style="flex:1;text-align:center">
|
| 68 |
+
<div style="font-size:9px;color:#aaa;margin-bottom:3px">π§ Estimated age</div>
|
| 69 |
+
<div style="font-size:20px;color:#fff;font-weight:bold">8</div>
|
| 70 |
+
<div style="font-size:9px;color:#888">years old</div>
|
| 71 |
+
</div>
|
| 72 |
+
</div>
|
| 73 |
+
<div style="margin-top:10px;background:#1a3a1a;border-radius:6px;padding:7px;text-align:center;font-size:11px;color:#7ec8e3">
|
| 74 |
+
β
Child resembles <strong>Father</strong> more closely
|
| 75 |
+
</div>
|
| 76 |
+
</div>
|
| 77 |
+
|
| 78 |
+
</div>
|
| 79 |
+
</div>
|
| 80 |
+
|
| 81 |
+
<p class="subtitle" style="margin-top:16px">Click a backend tab above to select it, or just tell me in the terminal.</p>
|
.superpowers/brainstorm/1147-1782279753/content/layout-v2.html
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<h2>Layout A β confirmed</h2>
|
| 2 |
+
<p class="subtitle">Father and Mother on the sides, Child centered. Results appear below after analysis.</p>
|
| 3 |
+
|
| 4 |
+
<div class="mockup">
|
| 5 |
+
<div class="mockup-header">MoreCloseToWhom β App Preview</div>
|
| 6 |
+
<div class="mockup-body">
|
| 7 |
+
<div style="text-align:center;margin-bottom:18px">
|
| 8 |
+
<div style="font-size:20px;font-weight:bold;color:#e0e0e0">More Close To Whom?</div>
|
| 9 |
+
<div style="font-size:12px;color:#888;margin-top:4px">Upload photos of father, mother, and child to find out</div>
|
| 10 |
+
</div>
|
| 11 |
+
|
| 12 |
+
<!-- Upload row: Father | Child (center) | Mother -->
|
| 13 |
+
<div style="display:flex;gap:16px;align-items:flex-end;margin-bottom:20px">
|
| 14 |
+
<!-- Father -->
|
| 15 |
+
<div style="flex:1;background:#16213e;border:1px dashed #4a4a7a;border-radius:8px;padding:16px;text-align:center">
|
| 16 |
+
<div style="font-size:28px;margin-bottom:6px">π¨</div>
|
| 17 |
+
<div style="font-size:11px;color:#7ec8e3;font-weight:bold;margin-bottom:4px">FATHER</div>
|
| 18 |
+
<div style="background:#0d1b2a;border-radius:4px;padding:6px;font-size:10px;color:#555">Drop photo here</div>
|
| 19 |
+
</div>
|
| 20 |
+
|
| 21 |
+
<!-- Child β larger/central -->
|
| 22 |
+
<div style="flex:1.3;background:#1a2a1a;border:2px solid #3a7a3a;border-radius:10px;padding:20px;text-align:center;position:relative">
|
| 23 |
+
<div style="position:absolute;top:-10px;left:50%;transform:translateX(-50%);background:#2a5a2a;border-radius:10px;padding:2px 10px;font-size:9px;color:#7ec8e3">CHILD</div>
|
| 24 |
+
<div style="font-size:36px;margin-bottom:6px">π§</div>
|
| 25 |
+
<div style="font-size:11px;color:#7ec8e3;font-weight:bold;margin-bottom:4px">CHILD</div>
|
| 26 |
+
<div style="background:#0d1b0d;border-radius:4px;padding:6px;font-size:10px;color:#555">Drop photo here</div>
|
| 27 |
+
</div>
|
| 28 |
+
|
| 29 |
+
<!-- Mother -->
|
| 30 |
+
<div style="flex:1;background:#16213e;border:1px dashed #4a4a7a;border-radius:8px;padding:16px;text-align:center">
|
| 31 |
+
<div style="font-size:28px;margin-bottom:6px">π©</div>
|
| 32 |
+
<div style="font-size:11px;color:#c8a0d8;font-weight:bold;margin-bottom:4px">MOTHER</div>
|
| 33 |
+
<div style="background:#0d1b2a;border-radius:4px;padding:6px;font-size:10px;color:#555">Drop photo here</div>
|
| 34 |
+
</div>
|
| 35 |
+
</div>
|
| 36 |
+
|
| 37 |
+
<!-- Analyze button -->
|
| 38 |
+
<div style="text-align:center;margin-bottom:20px">
|
| 39 |
+
<div style="display:inline-block;background:#1a6b3a;border-radius:6px;padding:10px 32px;font-size:13px;color:#fff;cursor:pointer">βΆ Analyze Resemblance</div>
|
| 40 |
+
</div>
|
| 41 |
+
|
| 42 |
+
<!-- Results -->
|
| 43 |
+
<div style="background:#16213e;border-radius:8px;padding:14px">
|
| 44 |
+
<div style="font-size:12px;color:#7ec8e3;font-weight:bold;margin-bottom:10px">Results</div>
|
| 45 |
+
<div style="display:flex;gap:16px">
|
| 46 |
+
<div style="flex:1">
|
| 47 |
+
<div style="font-size:10px;color:#aaa;margin-bottom:4px">π¨ Father resemblance</div>
|
| 48 |
+
<div style="background:#0d1b2a;border-radius:3px;height:8px;margin-bottom:3px">
|
| 49 |
+
<div style="background:#3a8fd4;width:84%;height:8px;border-radius:3px"></div>
|
| 50 |
+
</div>
|
| 51 |
+
<div style="font-size:11px;color:#3a8fd4;font-weight:bold">84%</div>
|
| 52 |
+
</div>
|
| 53 |
+
<div style="flex:1">
|
| 54 |
+
<div style="font-size:10px;color:#aaa;margin-bottom:4px">π© Mother resemblance</div>
|
| 55 |
+
<div style="background:#0d1b2a;border-radius:3px;height:8px;margin-bottom:3px">
|
| 56 |
+
<div style="background:#a060c0;width:71%;height:8px;border-radius:3px"></div>
|
| 57 |
+
</div>
|
| 58 |
+
<div style="font-size:11px;color:#a060c0;font-weight:bold">71%</div>
|
| 59 |
+
</div>
|
| 60 |
+
<div style="flex:1;text-align:center">
|
| 61 |
+
<div style="font-size:10px;color:#aaa;margin-bottom:4px">π§ Estimated age</div>
|
| 62 |
+
<div style="font-size:24px;color:#fff;font-weight:bold">8</div>
|
| 63 |
+
<div style="font-size:10px;color:#888">years old</div>
|
| 64 |
+
</div>
|
| 65 |
+
</div>
|
| 66 |
+
<div style="margin-top:12px;background:#1a3a1a;border-radius:6px;padding:8px;text-align:center;font-size:12px;color:#7ec8e3">
|
| 67 |
+
β
Child resembles <strong>Father</strong> more closely
|
| 68 |
+
</div>
|
| 69 |
+
</div>
|
| 70 |
+
</div>
|
| 71 |
+
</div>
|
.superpowers/brainstorm/1147-1782279753/content/layout.html
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<h2>How should the app be laid out?</h2>
|
| 2 |
+
<p class="subtitle">All three options upload Father, Mother, and Child photos and display resemblance + age results. The difference is in layout and flow.</p>
|
| 3 |
+
|
| 4 |
+
<div class="cards">
|
| 5 |
+
<div class="card" data-choice="a" onclick="toggleSelect(this)">
|
| 6 |
+
<div class="card-image">
|
| 7 |
+
<div style="background:#1a1a2e;padding:16px;border-radius:8px;font-family:monospace;font-size:12px;color:#e0e0e0">
|
| 8 |
+
<div style="display:flex;gap:8px;margin-bottom:12px">
|
| 9 |
+
<div style="flex:1;background:#16213e;border:1px dashed #4a4a7a;border-radius:6px;padding:10px;text-align:center;color:#888;font-size:11px">
|
| 10 |
+
π· Father<br><span style="font-size:9px;color:#555">Upload photo</span>
|
| 11 |
+
</div>
|
| 12 |
+
<div style="flex:1;background:#16213e;border:1px dashed #4a4a7a;border-radius:6px;padding:10px;text-align:center;color:#888;font-size:11px">
|
| 13 |
+
π· Mother<br><span style="font-size:9px;color:#555">Upload photo</span>
|
| 14 |
+
</div>
|
| 15 |
+
<div style="flex:1;background:#16213e;border:1px dashed #4a4a7a;border-radius:6px;padding:10px;text-align:center;color:#888;font-size:11px">
|
| 16 |
+
π· Child<br><span style="font-size:9px;color:#555">Upload photo</span>
|
| 17 |
+
</div>
|
| 18 |
+
</div>
|
| 19 |
+
<div style="background:#0f3460;border-radius:4px;padding:6px;text-align:center;margin-bottom:10px;font-size:11px">βΆ Analyze</div>
|
| 20 |
+
<div style="background:#16213e;border-radius:6px;padding:10px">
|
| 21 |
+
<div style="color:#7ec8e3;font-size:11px;margin-bottom:4px">Results</div>
|
| 22 |
+
<div style="display:flex;justify-content:space-between;font-size:10px;color:#aaa">
|
| 23 |
+
<span>π¨ Father match: 84%</span>
|
| 24 |
+
<span>π© Mother match: 71%</span>
|
| 25 |
+
</div>
|
| 26 |
+
<div style="font-size:10px;color:#aaa;margin-top:4px">π§ Estimated age: 8 years</div>
|
| 27 |
+
</div>
|
| 28 |
+
</div>
|
| 29 |
+
</div>
|
| 30 |
+
<div class="card-body">
|
| 31 |
+
<h3>A β All-in-one panel</h3>
|
| 32 |
+
<p>Upload all 3 photos side-by-side, click Analyze, results appear below. Clean and fast.</p>
|
| 33 |
+
</div>
|
| 34 |
+
</div>
|
| 35 |
+
|
| 36 |
+
<div class="card" data-choice="b" onclick="toggleSelect(this)">
|
| 37 |
+
<div class="card-image">
|
| 38 |
+
<div style="background:#1a1a2e;padding:16px;border-radius:8px;font-family:monospace;font-size:12px;color:#e0e0e0">
|
| 39 |
+
<div style="display:flex;gap:10px">
|
| 40 |
+
<div style="flex:1">
|
| 41 |
+
<div style="color:#888;font-size:10px;margin-bottom:6px">INPUTS</div>
|
| 42 |
+
<div style="background:#16213e;border:1px dashed #4a4a7a;border-radius:4px;padding:8px;text-align:center;font-size:10px;color:#888;margin-bottom:4px">π· Father</div>
|
| 43 |
+
<div style="background:#16213e;border:1px dashed #4a4a7a;border-radius:4px;padding:8px;text-align:center;font-size:10px;color:#888;margin-bottom:4px">π· Mother</div>
|
| 44 |
+
<div style="background:#16213e;border:1px dashed #4a4a7a;border-radius:4px;padding:8px;text-align:center;font-size:10px;color:#888;margin-bottom:10px">π· Child</div>
|
| 45 |
+
<div style="background:#0f3460;border-radius:4px;padding:5px;text-align:center;font-size:10px">βΆ Analyze</div>
|
| 46 |
+
</div>
|
| 47 |
+
<div style="flex:1">
|
| 48 |
+
<div style="color:#888;font-size:10px;margin-bottom:6px">RESULTS</div>
|
| 49 |
+
<div style="background:#16213e;border-radius:4px;padding:8px;margin-bottom:6px">
|
| 50 |
+
<div style="font-size:10px;color:#7ec8e3">Resemblance</div>
|
| 51 |
+
<div style="background:#0f3460;height:6px;border-radius:3px;margin:4px 0;width:84%"></div>
|
| 52 |
+
<div style="font-size:9px;color:#aaa">Father 84% β²</div>
|
| 53 |
+
<div style="background:#3d1a4f;height:6px;border-radius:3px;margin:4px 0;width:71%"></div>
|
| 54 |
+
<div style="font-size:9px;color:#aaa">Mother 71%</div>
|
| 55 |
+
</div>
|
| 56 |
+
<div style="background:#16213e;border-radius:4px;padding:8px">
|
| 57 |
+
<div style="font-size:10px;color:#7ec8e3">Age estimate</div>
|
| 58 |
+
<div style="font-size:18px;text-align:center;color:#fff;margin-top:4px">8 yrs</div>
|
| 59 |
+
</div>
|
| 60 |
+
</div>
|
| 61 |
+
</div>
|
| 62 |
+
</div>
|
| 63 |
+
</div>
|
| 64 |
+
<div class="card-body">
|
| 65 |
+
<h3>B β Split panel (inputs left, results right)</h3>
|
| 66 |
+
<p>Two-column layout: uploads on the left, results with bar charts on the right. More visual detail.</p>
|
| 67 |
+
</div>
|
| 68 |
+
</div>
|
| 69 |
+
|
| 70 |
+
<div class="card" data-choice="c" onclick="toggleSelect(this)">
|
| 71 |
+
<div class="card-image">
|
| 72 |
+
<div style="background:#1a1a2e;padding:16px;border-radius:8px;font-family:monospace;font-size:12px;color:#e0e0e0">
|
| 73 |
+
<div style="display:flex;gap:6px;margin-bottom:8px;justify-content:center">
|
| 74 |
+
<div style="background:#16213e;border:2px solid #0f3460;border-radius:50%;width:40px;height:40px;display:flex;align-items:center;justify-content:center;font-size:16px">π¨</div>
|
| 75 |
+
<div style="background:#16213e;border:1px dashed #4a4a7a;border-radius:50%;width:40px;height:40px;display:flex;align-items:center;justify-content:center;font-size:16px">π§</div>
|
| 76 |
+
<div style="background:#16213e;border:2px solid #4a0f3c;border-radius:50%;width:40px;height:40px;display:flex;align-items:center;justify-content:center;font-size:16px">π©</div>
|
| 77 |
+
</div>
|
| 78 |
+
<div style="display:flex;align-items:center;justify-content:center;gap:8px;margin-bottom:8px;font-size:9px;color:#aaa">
|
| 79 |
+
<span>Father</span><span style="color:#0f3460">ββ84%ββ</span><span>Child</span><span style="color:#4a0f3c">ββ71%ββ</span><span>Mother</span>
|
| 80 |
+
</div>
|
| 81 |
+
<div style="background:#16213e;border-radius:4px;padding:8px;text-align:center">
|
| 82 |
+
<div style="font-size:11px;color:#7ec8e3">Closer to: π¨ Father</div>
|
| 83 |
+
<div style="font-size:10px;color:#888;margin-top:4px">Child age: ~8 years</div>
|
| 84 |
+
</div>
|
| 85 |
+
</div>
|
| 86 |
+
</div>
|
| 87 |
+
<div class="card-body">
|
| 88 |
+
<h3>C β Visual family tree style</h3>
|
| 89 |
+
<p>Photos displayed as nodes in a family tree with connection lines showing similarity scores. More playful and engaging.</p>
|
| 90 |
+
</div>
|
| 91 |
+
</div>
|
| 92 |
+
</div>
|
.superpowers/brainstorm/1147-1782279753/content/waiting.html
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<div style="display:flex;align-items:center;justify-content:center;min-height:60vh">
|
| 2 |
+
<p class="subtitle">Continuing in terminal...</p>
|
| 3 |
+
</div>
|
.superpowers/brainstorm/1147-1782279753/state/server-info
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"type":"server-started","port":52079,"host":"127.0.0.1","url_host":"localhost","url":"http://localhost:52079","screen_dir":"D:\\Projects\\MoreCloseToWhom\\.superpowers\\brainstorm\\1147-1782279753\\content","state_dir":"D:\\Projects\\MoreCloseToWhom\\.superpowers\\brainstorm\\1147-1782279753\\state"}
|
Dockerfile
CHANGED
|
@@ -1,20 +1,20 @@
|
|
| 1 |
-
FROM python:3.13.5-slim
|
| 2 |
-
|
| 3 |
-
WORKDIR /app
|
| 4 |
-
|
| 5 |
-
RUN apt-get update && apt-get install -y \
|
| 6 |
-
build-essential \
|
| 7 |
-
curl \
|
| 8 |
-
git \
|
| 9 |
-
&& rm -rf /var/lib/apt/lists/*
|
| 10 |
-
|
| 11 |
-
COPY requirements.txt ./
|
| 12 |
-
COPY src/ ./src/
|
| 13 |
-
|
| 14 |
-
RUN pip3 install -r requirements.txt
|
| 15 |
-
|
| 16 |
-
EXPOSE 8501
|
| 17 |
-
|
| 18 |
-
HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
|
| 19 |
-
|
| 20 |
ENTRYPOINT ["streamlit", "run", "src/streamlit_app.py", "--server.port=8501", "--server.address=0.0.0.0"]
|
|
|
|
| 1 |
+
FROM python:3.13.5-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
RUN apt-get update && apt-get install -y \
|
| 6 |
+
build-essential \
|
| 7 |
+
curl \
|
| 8 |
+
git \
|
| 9 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 10 |
+
|
| 11 |
+
COPY requirements.txt ./
|
| 12 |
+
COPY src/ ./src/
|
| 13 |
+
|
| 14 |
+
RUN pip3 install -r requirements.txt
|
| 15 |
+
|
| 16 |
+
EXPOSE 8501
|
| 17 |
+
|
| 18 |
+
HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
|
| 19 |
+
|
| 20 |
ENTRYPOINT ["streamlit", "run", "src/streamlit_app.py", "--server.port=8501", "--server.address=0.0.0.0"]
|
README.md
CHANGED
|
@@ -1,19 +1,19 @@
|
|
| 1 |
-
---
|
| 2 |
-
title: ParentCloseTesting
|
| 3 |
-
emoji: π
|
| 4 |
-
colorFrom: red
|
| 5 |
-
colorTo: red
|
| 6 |
-
sdk: docker
|
| 7 |
-
app_port: 8501
|
| 8 |
-
tags:
|
| 9 |
-
- streamlit
|
| 10 |
-
pinned: false
|
| 11 |
-
short_description: Streamlit template space
|
| 12 |
-
---
|
| 13 |
-
|
| 14 |
-
# Welcome to Streamlit!
|
| 15 |
-
|
| 16 |
-
Edit `/src/streamlit_app.py` to customize this app to your heart's desire. :heart:
|
| 17 |
-
|
| 18 |
-
If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
|
| 19 |
-
forums](https://discuss.streamlit.io).
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: ParentCloseTesting
|
| 3 |
+
emoji: π
|
| 4 |
+
colorFrom: red
|
| 5 |
+
colorTo: red
|
| 6 |
+
sdk: docker
|
| 7 |
+
app_port: 8501
|
| 8 |
+
tags:
|
| 9 |
+
- streamlit
|
| 10 |
+
pinned: false
|
| 11 |
+
short_description: Streamlit template space
|
| 12 |
+
---
|
| 13 |
+
|
| 14 |
+
# Welcome to Streamlit!
|
| 15 |
+
|
| 16 |
+
Edit `/src/streamlit_app.py` to customize this app to your heart's desire. :heart:
|
| 17 |
+
|
| 18 |
+
If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
|
| 19 |
+
forums](https://discuss.streamlit.io).
|
requirements.txt
CHANGED
|
@@ -1,3 +1,19 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
streamlit
|
| 2 |
+
|
| 3 |
+
# DeepFace backend
|
| 4 |
+
deepface
|
| 5 |
+
tf-keras
|
| 6 |
+
|
| 7 |
+
# InsightFace backend
|
| 8 |
+
insightface
|
| 9 |
+
onnxruntime
|
| 10 |
+
|
| 11 |
+
# HF Model backend
|
| 12 |
+
transformers
|
| 13 |
+
torch
|
| 14 |
+
torchvision
|
| 15 |
+
|
| 16 |
+
# Shared
|
| 17 |
+
Pillow
|
| 18 |
+
numpy
|
| 19 |
+
opencv-python-headless
|
src/backends/__init__.py
ADDED
|
File without changes
|
src/backends/deepface_backend.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
from deepface import DeepFace
|
| 3 |
+
|
| 4 |
+
_MODEL = "VGG-Face"
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def _cosine_sim(a, b):
|
| 8 |
+
a, b = np.array(a, dtype=float), np.array(b, dtype=float)
|
| 9 |
+
return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)))
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def _to_pct(sim):
|
| 13 |
+
return round(max(0.0, min(1.0, (sim + 1) / 2)) * 100, 1)
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def analyze(father_path, mother_path, child_path):
|
| 17 |
+
child_info = DeepFace.analyze(
|
| 18 |
+
child_path, actions=["age"], enforce_detection=False, silent=True
|
| 19 |
+
)
|
| 20 |
+
age = int(child_info[0]["age"])
|
| 21 |
+
|
| 22 |
+
child_emb = DeepFace.represent(
|
| 23 |
+
child_path, model_name=_MODEL, enforce_detection=False
|
| 24 |
+
)[0]["embedding"]
|
| 25 |
+
father_emb = DeepFace.represent(
|
| 26 |
+
father_path, model_name=_MODEL, enforce_detection=False
|
| 27 |
+
)[0]["embedding"]
|
| 28 |
+
mother_emb = DeepFace.represent(
|
| 29 |
+
mother_path, model_name=_MODEL, enforce_detection=False
|
| 30 |
+
)[0]["embedding"]
|
| 31 |
+
|
| 32 |
+
return {
|
| 33 |
+
"age": age,
|
| 34 |
+
"father_score": _to_pct(_cosine_sim(child_emb, father_emb)),
|
| 35 |
+
"mother_score": _to_pct(_cosine_sim(child_emb, mother_emb)),
|
| 36 |
+
}
|
src/backends/hf_backend.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import torch
|
| 3 |
+
from PIL import Image
|
| 4 |
+
from transformers import CLIPModel, CLIPProcessor, ViTFeatureExtractor, ViTForImageClassification
|
| 5 |
+
|
| 6 |
+
_clip_model = None
|
| 7 |
+
_clip_processor = None
|
| 8 |
+
_age_model = None
|
| 9 |
+
_age_extractor = None
|
| 10 |
+
|
| 11 |
+
# nateraw/vit-age-classifier outputs probabilities over these age range buckets
|
| 12 |
+
_AGE_LABELS = ["0-2", "3-9", "10-19", "20-29", "30-39", "40-49", "50-59", "60-69", "70+"]
|
| 13 |
+
_AGE_MIDPOINTS = [1, 6, 14, 24, 34, 44, 54, 64, 74]
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def _get_clip():
|
| 17 |
+
global _clip_model, _clip_processor
|
| 18 |
+
if _clip_model is None:
|
| 19 |
+
_clip_model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32")
|
| 20 |
+
_clip_processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
|
| 21 |
+
return _clip_model, _clip_processor
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def _get_age_model():
|
| 25 |
+
global _age_model, _age_extractor
|
| 26 |
+
if _age_model is None:
|
| 27 |
+
_age_model = ViTForImageClassification.from_pretrained("nateraw/vit-age-classifier")
|
| 28 |
+
_age_extractor = ViTFeatureExtractor.from_pretrained("nateraw/vit-age-classifier")
|
| 29 |
+
return _age_model, _age_extractor
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def _embed(image_path):
|
| 33 |
+
model, processor = _get_clip()
|
| 34 |
+
image = Image.open(image_path).convert("RGB")
|
| 35 |
+
inputs = processor(images=image, return_tensors="pt")
|
| 36 |
+
with torch.no_grad():
|
| 37 |
+
features = model.get_image_features(**inputs)
|
| 38 |
+
features = features / features.norm(dim=-1, keepdim=True)
|
| 39 |
+
return features[0].numpy()
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def _estimate_age(image_path):
|
| 43 |
+
model, extractor = _get_age_model()
|
| 44 |
+
image = Image.open(image_path).convert("RGB")
|
| 45 |
+
inputs = extractor(images=image, return_tensors="pt")
|
| 46 |
+
with torch.no_grad():
|
| 47 |
+
outputs = model(**inputs)
|
| 48 |
+
probs = torch.softmax(outputs.logits, dim=-1)[0].numpy()
|
| 49 |
+
return int(round(float(np.dot(probs, _AGE_MIDPOINTS))))
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def _to_pct(sim):
|
| 53 |
+
# CLIP features are already L2-normalized so dot product == cosine similarity (-1 to 1)
|
| 54 |
+
return round(max(0.0, min(1.0, (float(sim) + 1) / 2)) * 100, 1)
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def analyze(father_path, mother_path, child_path):
|
| 58 |
+
child_emb = _embed(child_path)
|
| 59 |
+
father_emb = _embed(father_path)
|
| 60 |
+
mother_emb = _embed(mother_path)
|
| 61 |
+
|
| 62 |
+
return {
|
| 63 |
+
"age": _estimate_age(child_path),
|
| 64 |
+
"father_score": _to_pct(np.dot(child_emb, father_emb)),
|
| 65 |
+
"mother_score": _to_pct(np.dot(child_emb, mother_emb)),
|
| 66 |
+
}
|
src/backends/insightface_backend.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import cv2
|
| 3 |
+
from insightface.app import FaceAnalysis
|
| 4 |
+
|
| 5 |
+
_app = None
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def _get_app():
|
| 9 |
+
global _app
|
| 10 |
+
if _app is None:
|
| 11 |
+
_app = FaceAnalysis(providers=["CPUExecutionProvider"])
|
| 12 |
+
_app.prepare(ctx_id=0, det_size=(640, 640))
|
| 13 |
+
return _app
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def _cosine_sim(a, b):
|
| 17 |
+
a, b = np.array(a, dtype=float), np.array(b, dtype=float)
|
| 18 |
+
return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)))
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def _to_pct(sim):
|
| 22 |
+
return round(max(0.0, min(1.0, (sim + 1) / 2)) * 100, 1)
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def _get_face(app, path):
|
| 26 |
+
img = cv2.imread(path)
|
| 27 |
+
if img is None:
|
| 28 |
+
raise ValueError(f"Could not read image: {path}")
|
| 29 |
+
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
| 30 |
+
faces = app.get(img_rgb)
|
| 31 |
+
if not faces:
|
| 32 |
+
raise ValueError(f"No face detected in image: {path}")
|
| 33 |
+
return faces[0]
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def analyze(father_path, mother_path, child_path):
|
| 37 |
+
app = _get_app()
|
| 38 |
+
|
| 39 |
+
child_face = _get_face(app, child_path)
|
| 40 |
+
father_face = _get_face(app, father_path)
|
| 41 |
+
mother_face = _get_face(app, mother_path)
|
| 42 |
+
|
| 43 |
+
return {
|
| 44 |
+
"age": int(child_face.age),
|
| 45 |
+
"father_score": _to_pct(_cosine_sim(child_face.embedding, father_face.embedding)),
|
| 46 |
+
"mother_score": _to_pct(_cosine_sim(child_face.embedding, mother_face.embedding)),
|
| 47 |
+
}
|
src/streamlit_app.py
CHANGED
|
@@ -1,40 +1,110 @@
|
|
| 1 |
-
import
|
| 2 |
-
import
|
| 3 |
-
import
|
|
|
|
| 4 |
import streamlit as st
|
| 5 |
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
""
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
import os
|
| 3 |
+
import tempfile
|
| 4 |
+
|
| 5 |
import streamlit as st
|
| 6 |
|
| 7 |
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
| 8 |
+
|
| 9 |
+
st.set_page_config(
|
| 10 |
+
page_title="More Close To Whom?",
|
| 11 |
+
page_icon="π¨βπ©βπ§",
|
| 12 |
+
layout="wide",
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
st.title("π¨βπ©βπ§ More Close To Whom?")
|
| 16 |
+
st.caption("Upload photos of father, mother, and child to discover who the child resembles more")
|
| 17 |
+
|
| 18 |
+
# Backend selector
|
| 19 |
+
backend_choice = st.radio(
|
| 20 |
+
"ML Backend",
|
| 21 |
+
["π§ DeepFace", "β‘ InsightFace", "π€ HF Model"],
|
| 22 |
+
horizontal=True,
|
| 23 |
+
help="Select the AI model to use for face analysis",
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
st.divider()
|
| 27 |
+
|
| 28 |
+
# Upload row: Father | Child (center, wider) | Mother
|
| 29 |
+
col_f, col_c, col_m = st.columns([1, 1.3, 1])
|
| 30 |
+
|
| 31 |
+
with col_f:
|
| 32 |
+
st.markdown("#### π¨ Father")
|
| 33 |
+
father_file = st.file_uploader(
|
| 34 |
+
"", type=["jpg", "jpeg", "png"], key="father", label_visibility="collapsed"
|
| 35 |
+
)
|
| 36 |
+
if father_file:
|
| 37 |
+
st.image(father_file, use_container_width=True)
|
| 38 |
+
|
| 39 |
+
with col_c:
|
| 40 |
+
st.markdown("#### π§ Child")
|
| 41 |
+
child_file = st.file_uploader(
|
| 42 |
+
"", type=["jpg", "jpeg", "png"], key="child", label_visibility="collapsed"
|
| 43 |
+
)
|
| 44 |
+
if child_file:
|
| 45 |
+
st.image(child_file, use_container_width=True)
|
| 46 |
+
|
| 47 |
+
with col_m:
|
| 48 |
+
st.markdown("#### π© Mother")
|
| 49 |
+
mother_file = st.file_uploader(
|
| 50 |
+
"", type=["jpg", "jpeg", "png"], key="mother", label_visibility="collapsed"
|
| 51 |
+
)
|
| 52 |
+
if mother_file:
|
| 53 |
+
st.image(mother_file, use_container_width=True)
|
| 54 |
+
|
| 55 |
+
st.divider()
|
| 56 |
+
|
| 57 |
+
all_uploaded = all([father_file, child_file, mother_file])
|
| 58 |
+
|
| 59 |
+
if st.button("βΆ Analyze Resemblance", type="primary", disabled=not all_uploaded):
|
| 60 |
+
with st.spinner("Analyzing faces β this may take a moment on first run..."):
|
| 61 |
+
with tempfile.TemporaryDirectory() as tmpdir:
|
| 62 |
+
def save(uploaded, name):
|
| 63 |
+
path = os.path.join(tmpdir, name)
|
| 64 |
+
with open(path, "wb") as f:
|
| 65 |
+
f.write(uploaded.getvalue())
|
| 66 |
+
return path
|
| 67 |
+
|
| 68 |
+
father_path = save(father_file, "father.jpg")
|
| 69 |
+
mother_path = save(mother_file, "mother.jpg")
|
| 70 |
+
child_path = save(child_file, "child.jpg")
|
| 71 |
+
|
| 72 |
+
try:
|
| 73 |
+
if "DeepFace" in backend_choice:
|
| 74 |
+
from backends.deepface_backend import analyze
|
| 75 |
+
elif "InsightFace" in backend_choice:
|
| 76 |
+
from backends.insightface_backend import analyze
|
| 77 |
+
else:
|
| 78 |
+
from backends.hf_backend import analyze
|
| 79 |
+
|
| 80 |
+
result = analyze(father_path, mother_path, child_path)
|
| 81 |
+
|
| 82 |
+
st.success("Analysis complete!")
|
| 83 |
+
|
| 84 |
+
r1, r2, r3 = st.columns(3)
|
| 85 |
+
with r1:
|
| 86 |
+
st.metric("π¨ Father resemblance", f"{result['father_score']}%")
|
| 87 |
+
st.progress(result["father_score"] / 100)
|
| 88 |
+
with r2:
|
| 89 |
+
st.metric("π© Mother resemblance", f"{result['mother_score']}%")
|
| 90 |
+
st.progress(result["mother_score"] / 100)
|
| 91 |
+
with r3:
|
| 92 |
+
st.metric("π§ Estimated child age", f"{result['age']} yrs")
|
| 93 |
+
|
| 94 |
+
st.divider()
|
| 95 |
+
|
| 96 |
+
diff = abs(result["father_score"] - result["mother_score"])
|
| 97 |
+
if diff < 2:
|
| 98 |
+
st.info("βοΈ Child resembles both parents almost equally!")
|
| 99 |
+
elif result["father_score"] >= result["mother_score"]:
|
| 100 |
+
st.success(f"β
Child looks more like **Father π¨** ({diff:.1f}% difference)")
|
| 101 |
+
else:
|
| 102 |
+
st.success(f"β
Child looks more like **Mother π©** ({diff:.1f}% difference)")
|
| 103 |
+
|
| 104 |
+
except ValueError as e:
|
| 105 |
+
st.error(f"β οΈ {e}")
|
| 106 |
+
except Exception as e:
|
| 107 |
+
st.error(f"β Analysis failed: {e}")
|
| 108 |
+
|
| 109 |
+
elif not all_uploaded:
|
| 110 |
+
st.info("π Please upload photos of all three family members to begin")
|