upload the project

This commit is contained in:
2026-06-21 21:24:47 +02:00
parent 8cf419a907
commit d94790cbe5
113 changed files with 1327 additions and 2 deletions
View File
+163
View File
@@ -0,0 +1,163 @@
from tinydb import Query, TinyDB
from logging import getLogger
from base64 import b64encode
from requests import post, get, RequestException
import subprocess
import time
from os import listdir, path
from re import search
logger = getLogger("hairloss")
OLLAMA_URL = "http://localhost:11434"
PROMPT = """
Think
You are a visual estimator of scalp hair density.
You will be given:
an image
an angle label: "top", "lateral", or "face"
Angle meaning:
top: evaluate crown/vertex only
lateral: evaluate temple recession and side density
face: evaluate frontal hairline and symmetry
Task:
Estimate hair loss severity as a continuous value between 0.0 and 1.0.
Scoring meaning:
0.0 → full dense hair
0.5 → moderate thinning / visible scalp
1.0 → severe hair loss
Guidelines:
Use only visible evidence in the image.
Be robust to lighting and hairstyle, but consider scalp visibility.
Only evaluate regions that are visible from the given angle.
If a region is not visible, do not infer it.
If image quality is unclear:
use a neutral estimate based on visible areas (do not guess extremes)
Output:
Return ONLY a single float between 0.0 and 1.0.
No text, no explanation, no punctuation.
"""
def prepareModel(timeout: int = 15):
try:
get(f"{OLLAMA_URL}/api/tags", timeout=2)
logger.info("Ollama already running")
return
except RequestException:
logger.info("Ollama not running, starting server...")
subprocess.Popen(
["ollama", "serve"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
start = time.time()
while time.time() - start < timeout:
try:
get(f"{OLLAMA_URL}/api/tags", timeout=2)
logger.info("Ollama server started")
return
except RequestException:
time.sleep(1)
raise RuntimeError("Failed to start Ollama server")
def evaluate(image_path: str, model: str, repetitions: int = 10, timeOut: int = 10):
db = TinyDB(path.abspath(path.join(image_path, "../hairlineResults.json")))
q = Query()
prepareModel()
for image in listdir(image_path):
oldInfo = db.get(q.filename == image)
if model in oldInfo.keys():
logger.info(f"Skipping evaluation {image} with {model}...")
continue
logger.info(f"Evaluating {image} with model {model} {repetitions} times.")
with open(path.join(image_path, image), "rb") as f:
imageb64 = b64encode(f.read()).decode("utf-8")
average = 0
averageWithoutUnsure = 0
averageWithoutUnsureCount = 0
if "f" in image:
angle = "face"
elif "l" in image:
angle = "lateral"
elif "t" in image:
angle = "top"
else:
raise ValueError(f"no angle information in filename {image}")
imagePrompt = PROMPT + f"\nlabel:\"{angle}\""
for i in range(repetitions): # we ask multiple times and get the average response
response = post(
f"{OLLAMA_URL}/api/generate",
json={
"model": model,
"prompt": imagePrompt,
"images": [imageb64],
"stream": False,
"thinking": model != "qwen3-vl:8b" # for some reason this model thinkgs so much, that it spends all it's tokens on thinking and None in output...
},
timeout=timeOut,
)
response.raise_for_status()
logger.debug(response.json())
result = search(r"^(0(?:\.\d+)?|1(?:\.0+)?)$", response.json()["response"].strip())
logger.debug(result)
if result == None:
result = 0.5
else:
data = float(search(r"^(0(?:\.\d+)?|1(?:\.0+)?)$", response.json()["response"].strip()).group(1)) # extract the float in case it outputed some text
if data != 0.5: # if the model is uncertain it should return 0.5
averageWithoutUnsure += data
averageWithoutUnsureCount += 1
average += data
average /= repetitions
if averageWithoutUnsureCount != 0: # avoids case where all repetitions are unsure
averageWithoutUnsure /= averageWithoutUnsureCount
else:
averageWithoutUnsure = 0.5
if db.contains(q.filename == image): # we add the result to a database. It allows to do everything in multiple runs and combine results from multiple models
imageResult = db.get(q.filename == image)
imageResult[model] = average
imageResult[model+"WithoutUnsure"] = averageWithoutUnsure
db.upsert(imageResult, q.filename == image)
else:
imageResult = {
"filename": image,
model: average,
model + "WithoutUnsure": averageWithoutUnsure
}
db.upsert(imageResult, q.filename == image)
logger.info("Normalizing values ...")
# normalise values
maxValue = max(image[model + "WithoutUnsure"] for image in db)
minValue = min(image[model + "WithoutUnsure"] for image in db)
for image in listdir(image_path):
unnormalizedValues = db.get(q.filename == image)
unnormalizedValues[model+"Normalized"] = (unnormalizedValues[model+"WithoutUnsure"] - minValue)/(maxValue - minValue)
db.upsert(unnormalizedValues, q.filename == image)
logger.info("Stopping model...")
subprocess.Popen(
["ollama", "stop", model],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
+101
View File
@@ -0,0 +1,101 @@
from tinydb import TinyDB
from pathlib import Path
def gallery(image_path):
DB_FILE = Path(image_path).parent / "hairlineResults.json"
OUTPUT_TEX = "gallery.tex"
IMAGE_DIR = Path(image_path)
IMAGES_PER_ROW = 3
IMAGE_HEIGHT = "5cm"
db = TinyDB(DB_FILE)
entries = sorted(db.all(), key=lambda x: x["filename"])
def latex_escape(s):
return (
str(s)
.replace("\\", "\\textbackslash{}")
.replace("_", "\\_")
.replace("&", "\\&")
.replace("%", "\\%")
.replace("#", "\\#")
.replace("{", "\\{")
.replace("}", "\\}")
)
with open(OUTPUT_TEX, "w", encoding="utf8") as f:
f.write(r"""\documentclass[a4paper]{article}
\usepackage[a4paper,margin=1cm]{geometry}
\usepackage{graphicx}
\usepackage{array}
\usepackage{longtable}
\usepackage{float}
\usepackage{grffile}
\usepackage[T1]{fontenc}
\pagestyle{empty}
\setlength{\parindent}{0pt}
\begin{document}
""")
for i in range(0, len(entries), IMAGES_PER_ROW):
row = entries[i:i + IMAGES_PER_ROW]
cols = "c" * len(row)
f.write(r"\begin{tabular}{" + cols + "}\n")
#
# Images
#
image_cells = []
for e in row:
img_path = Path(IMAGE_DIR) / e["filename"]
image_cells.append(
rf"\includegraphics[height={IMAGE_HEIGHT}]{{{img_path.as_posix()}}}"
)
f.write(" & ".join(image_cells) + r"\\[2mm]" + "\n")
# LaTeX comment with filenames
f.write("% " + ", ".join(e["filename"] for e in row) + "\n")
#
# Text cells (FIXED)
#
text_cells = []
for e in row:
lines = []
lines.append(rf"\texttt{{\tiny {latex_escape(e['filename'])}}}")
for k, v in e.items():
if k == "filename":
continue
if isinstance(v, float):
lines.append(f"{latex_escape(k)}: {v:.4f}")
else:
lines.append(f"{latex_escape(k)}: {latex_escape(v)}")
cell = (
r"\begin{minipage}[t]{4cm}\ttfamily\tiny "
+ r" \\ ".join(lines)
+ r" \end{minipage}"
)
text_cells.append(cell)
f.write(" & ".join(text_cells) + r"\\" + "\n")
f.write(r"\end{tabular}")
f.write("\n\n\\vspace{5mm}\n\n")
f.write(r"\end{document}")
+157
View File
@@ -0,0 +1,157 @@
from tinydb import Query, TinyDB
from pathlib import Path
import os
import argparse
import logging
logger = logging.getLogger("hairloss")
from datetime import date
from .evaluate import *
from .visuals import *
from .gallery import *
models = ["llava:7b", "gemma4:12b", "qwen3-vl:8b", "ministral-3:8b"]
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--verbose", action="store_true", help="Enable debug logging")
parser.add_argument("--generate_with_llava", action="store_true", help="Generate values with llava:7b")
parser.add_argument("--generate_with_gemma", action="store_true", help="Generate values with gemma4:12b")
parser.add_argument("--generate_with_qwen", action="store_true", help="Generate values with qwen3-vl:8b")
parser.add_argument("--generate_with_ministral", action="store_true", help="Generate values with ministral-3:8b")
parser.add_argument("--generate_averages", action="store_true", help="Generate averages values")
parser.add_argument("--generate_gallery", action="store_true", help="Generate gallery")
parser.add_argument("--generate_visuals", action="store_true", help="Graph the values in multiple plots.")
args = parser.parse_args()
logging.basicConfig(
level=logging.DEBUG if args.verbose else logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s"
)
IMAGES = Path(os.environ.get("HAIRLOSS_IMAGES", "Images")) or Path("./Images")
if args.generate_with_llava:
evaluate(IMAGES, "llava:7b", 10, 10)
if args.generate_with_gemma:
evaluate(IMAGES, "gemma4:12b", 3, 90) # gemma takes much more time as it use thinking, so we reduce the repetitions
if args.generate_with_qwen:
evaluate(IMAGES, "qwen3-vl:8b", 1, 180)
if args.generate_with_ministral:
evaluate(IMAGES, "ministral-3:8b", 10, 10)
if args.generate_averages:
db = TinyDB(path.abspath(path.join(IMAGES, "../hairlineResults.json")))
q = Query()
# average
for image in db:
sumModels = 0
sumModelsNormalized = 0
for key in image.keys():
if key in models:
sumModels += image[key + "WithoutUnsure"]
sumModelsNormalized += image[key + "Normalized"]
image["average"] = sumModels/len(models)
image["averageNormalized"] = sumModelsNormalized/len(models)
logger.info(f"Image {image["filename"]}: average = {str(image["average"])} averageNormalized = {str(image["averageNormalized"])}")
db.upsert(image, q.filename == image)
if args.generate_gallery:
gallery(IMAGES)
if args.generate_visuals:
# args are models, withClean, withNormalized, withRaw, image_path, filepath, doSave, doShow, doRegression, doCorrelation, angle (array of "t", "f" and "l")
# all angles
plotHairline(["llava:7b", "gemma4:12b", "qwen3-vl:8b", "ministral-3:8b"], False, False, True, IMAGES, "rawDataAllModelsAllAngles.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["t", "f", "l"], False, False)
plotHairline(["llava:7b", "gemma4:12b", "qwen3-vl:8b", "ministral-3:8b"], True, False, False, IMAGES, "withCleanDataAllModelsAllAngles.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["t", "f", "l"], False, False)
plotHairline(["llava:7b", "gemma4:12b", "qwen3-vl:8b", "ministral-3:8b"], False, True, False, IMAGES, "withNormalizedAllModelsAllAngles.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["t", "f", "l"], False, False)
plotHairline(["llava:7b"], True, False, True, IMAGES, "dataLlavaAllAngles.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["t", "f", "l"], False, False)
plotHairline(["gemma4:12b"], True, False, True, IMAGES, "dataGemmaAllAngles.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["t", "f", "l"], False, False)
plotHairline(["qwen3-vl:8b"], True, False, True, IMAGES, "dataQwenAllAngles.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["t", "f", "l"], False, False)
plotHairline(["ministral-3:8b"], True, False, True, IMAGES, "dataMinistralAllAngles.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["t", "f", "l"], False, False)
plotHairline(["ministral-3:8b"], True, True, False, IMAGES, "dataMinistralNormalizedAllAngles.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["t", "f", "l"], False, False)
plotHairline(["llava:7b"], True, True, False, IMAGES, "dataLLavaNormalizedAllAngles.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["t", "f", "l"], False, False)
plotHairline(["gemma4:12b"], True, True, False, IMAGES, "dataGemmaNormalizedAllAngles.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["t", "f", "l"], False, False)
plotHairline(["qwen3-vl:8b"], True, True, False, IMAGES, "dataQwenNormalizedAllAngles.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["t", "f", "l"], False, False)
plotHairline(["average"], False, True, False, IMAGES, "dataAverageNormalizedAllAngles.pdf", True, False, True, True, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["t", "f", "l"], False, False)
plotHairline(["average"], False, False, True, IMAGES, "dataAverageAllAngles.pdf", True, False, True, True, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["t", "f", "l"], False, False)
# top angle
plotHairline(["llava:7b", "gemma4:12b", "qwen3-vl:8b", "ministral-3:8b"], False, False, True, IMAGES, "rawDataAllModelsTopAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["t"], False, False)
plotHairline(["llava:7b", "gemma4:12b", "qwen3-vl:8b", "ministral-3:8b"], True, False, False, IMAGES, "withCleanDataAllModelsTopAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["t"], False, False)
plotHairline(["llava:7b", "gemma4:12b", "qwen3-vl:8b", "ministral-3:8b"], False, True, False, IMAGES, "withNormalizedAllModelsTopAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["t"], False, False)
plotHairline(["llava:7b"], True, False, True, IMAGES, "dataLlavaTopAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["t"], False, False)
plotHairline(["gemma4:12b"], True, False, True, IMAGES, "dataGemmaTopAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["t"], False, False)
plotHairline(["qwen3-vl:8b"], True, False, True, IMAGES, "dataQwenTopAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["t"], False, False)
plotHairline(["ministral-3:8b"], True, False, True, IMAGES, "dataMinistralTopAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["t"], False, False)
plotHairline(["ministral-3:8b"], True, True, False, IMAGES, "dataMinistralNormalizedTopAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["t"], False, False)
plotHairline(["llava:7b"], True, True, False, IMAGES, "dataLLavaNormalizedTopAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["t"], False, False)
plotHairline(["gemma4:12b"], True, True, False, IMAGES, "dataGemmaNormalizedTopAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["t"], False, False)
plotHairline(["qwen3-vl:8b"], True, True, False, IMAGES, "dataQwenNormalizedTopAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["t"], False, False)
plotHairline(["average"], False, True, False, IMAGES, "dataAverageNormalizedTopAngle.pdf", True, False, True, True, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["t"], False, False)
plotHairline(["average"], False, False, True, IMAGES, "dataAverageTopAngle.pdf", True, False, True, True, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["t"], False, False)
# face angle
plotHairline(["llava:7b", "gemma4:12b", "qwen3-vl:8b", "ministral-3:8b"], False, False, True, IMAGES, "rawDataAllModelsFaceAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["f"], False, False)
plotHairline(["llava:7b", "gemma4:12b", "qwen3-vl:8b", "ministral-3:8b"], True, False, False, IMAGES, "withCleanDataAllModelsFaceAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["f"], False, False)
plotHairline(["llava:7b", "gemma4:12b", "qwen3-vl:8b", "ministral-3:8b"], False, True, False, IMAGES, "withNormalizedAllModelsFaceAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["f"], False, False)
plotHairline(["llava:7b"], True, False, True, IMAGES, "dataLlavaFaceAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["f"], False, False)
plotHairline(["gemma4:12b"], True, False, True, IMAGES, "dataGemmaFaceAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["f"], False, False)
plotHairline(["qwen3-vl:8b"], True, False, True, IMAGES, "dataQwenFaceAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["f"], False, False)
plotHairline(["ministral-3:8b"], True, False, True, IMAGES, "dataMinistralFaceAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["f"], False, False)
plotHairline(["ministral-3:8b"], True, True, False, IMAGES, "dataMinistralNormalizedFaceAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["f"], False, False)
plotHairline(["llava:7b"], True, True, False, IMAGES, "dataLLavaNormalizedFaceAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["f"], False, False)
plotHairline(["gemma4:12b"], True, True, False, IMAGES, "dataGemmaNormalizedFaceAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["f"], False, False)
plotHairline(["qwen3-vl:8b"], True, True, False, IMAGES, "dataQwenNormalizedFaceAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["f"], False, False)
plotHairline(["average"], False, True, False, IMAGES, "dataAverageNormalizedFaceAngle.pdf", True, False, True, True, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["f"], False, False)
plotHairline(["average"], False, False, True, IMAGES, "dataAverageFaceAngle.pdf", True, False, True, True, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["f"], False, False)
# lateral angle
plotHairline(["llava:7b", "gemma4:12b", "qwen3-vl:8b", "ministral-3:8b"], False, False, True, IMAGES, "rawDataAllModelsLateralAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["l"], False, False)
plotHairline(["llava:7b", "gemma4:12b", "qwen3-vl:8b", "ministral-3:8b"], True, False, False, IMAGES, "withCleanDataAllModelsLateralAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["l"], False, False)
plotHairline(["llava:7b", "gemma4:12b", "qwen3-vl:8b", "ministral-3:8b"], False, True, False, IMAGES, "withNormalizedAllModelsLateralAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["l"], False, False)
plotHairline(["llava:7b"], True, False, True, IMAGES, "dataLlavaLateralAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["l"], False, False)
plotHairline(["gemma4:12b"], True, False, True, IMAGES, "dataGemmaLateralAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["l"], False, False)
plotHairline(["qwen3-vl:8b"], True, False, True, IMAGES, "dataQwenLateralAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["l"], False, False)
plotHairline(["ministral-3:8b"], True, False, True, IMAGES, "dataMinistralLateralAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["l"], False, False)
plotHairline(["ministral-3:8b"], True, True, False, IMAGES, "dataMinistralNormalizedLateralAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["l"], False, False)
plotHairline(["llava:7b"], True, True, False, IMAGES, "dataLLavaNormalizedLateralAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["l"], False, False)
plotHairline(["gemma4:12b"], True, True, False, IMAGES, "dataGemmaNormalizedLateralAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["l"], False, False)
plotHairline(["qwen3-vl:8b"], True, True, False, IMAGES, "dataQwenNormalizedLateralAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["l"], False, False)
plotHairline(["average"], False, True, False, IMAGES, "dataAverageNormalizedLateralAngle.pdf", True, False, True, True, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["l"], False, False)
plotHairline(["average"], False, False, True, IMAGES, "dataAverageLateralAngle.pdf", True, False, True, True, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["l"], False, False)
# stress inducing factor since 2023-09-11 (ex: sickness, work or in this case being near an anoying person) to see if there was an influence on the hair loss
plotHairline(["average"], False, True, False, IMAGES, "dataAverageNormalizedSince2023-09-11AllAngles.pdf", True, False, True, True, date.fromisoformat("2023-09-11"), date.fromisoformat("2050-01-01"), ["t", "f", "l"], False, False)
plotHairline(["average"], False, True, False, IMAGES, "dataAverageNormalizedSince2023-09-11TopAngles.pdf", True, False, True, True, date.fromisoformat("2023-09-11"), date.fromisoformat("2050-01-01"), ["t"], False, False)
plotHairline(["average"], False, True, False, IMAGES, "dataAverageNormalizedSince2023-09-11FaceAngles.pdf", True, False, True, True, date.fromisoformat("2023-09-11"), date.fromisoformat("2050-01-01"), ["f"], False, False)
plotHairline(["average"], False, True, False, IMAGES, "dataAverageNormalizedSince2023-09-11LateralAngles.pdf", True, False, True, True, date.fromisoformat("2023-09-11"), date.fromisoformat("2050-01-01"), ["l"], False, False)
# from our testing top is a less reliable angle
# top and lateral angle
plotHairline(["llava:7b", "gemma4:12b", "qwen3-vl:8b", "ministral-3:8b"], False, False, True, IMAGES, "rawDataAllModelsFaceAndLateralAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["f","l"], False, False)
plotHairline(["llava:7b", "gemma4:12b", "qwen3-vl:8b", "ministral-3:8b"], True, False, False, IMAGES, "withCleanDataAllModelsFaceAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["f","l"], False, False)
plotHairline(["llava:7b", "gemma4:12b", "qwen3-vl:8b", "ministral-3:8b"], False, True, False, IMAGES, "withNormalizedAllModelsFaceAndLateralAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["f","l"], False, False)
plotHairline(["llava:7b"], True, False, True, IMAGES, "dataLlavaFaceAndLateralAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["f","l"], False, False)
plotHairline(["gemma4:12b"], True, False, True, IMAGES, "dataGemmaFaceAndLateralAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["f","l"], False, False)
plotHairline(["qwen3-vl:8b"], True, False, True, IMAGES, "dataQwenFaceAndLateralAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["f","l"], False, False)
plotHairline(["ministral-3:8b"], True, False, True, IMAGES, "dataMinistralFaceAndLateralAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["f","l"], False, False)
plotHairline(["ministral-3:8b"], True, True, False, IMAGES, "dataMinistralNormalizedFaceAndLateralAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["f","l"], False, False)
plotHairline(["llava:7b"], True, True, False, IMAGES, "dataLLavaNormalizedFaceAndLateralAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["f","l"], False, False)
plotHairline(["gemma4:12b"], True, True, False, IMAGES, "dataGemmaNormalizedFaceAndLateralAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["f","l"], False, False)
plotHairline(["qwen3-vl:8b"], True, True, False, IMAGES, "dataQwenNormalizedFaceAndLateralAngle.pdf", True, False, False, False, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["f","l"], False, False)
plotHairline(["average"], False, True, False, IMAGES, "dataAverageNormalizedFaceAndLateralAngle.pdf", True, False, True, True, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["f","l"], False, False)
plotHairline(["average"], False, False, True, IMAGES, "dataAverageFaceAndLateralAngle.pdf", True, False, True, True, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["f","l"], False, False)
plotHairline(["average"], False, True, False, IMAGES, "dataAverageNormalizedSince2023-09-11FaceAndLateralAngles.pdf", True, False, True, True, date.fromisoformat("2023-09-11"), date.fromisoformat("2050-01-01"), ["f","l"], False, False)
# final plots
plotHairline(["average"], False, True, False, IMAGES, "averagedDataAverageNormalizedFaceAndLateralAngle.pdf", True, False, True, True, date.fromisoformat("1983-04-01"), date.fromisoformat("2050-01-01"), ["f","l"], True, False)
plotHairline(["average"], False, True, False, IMAGES, "averagedDataAverageNormalizedSince2023-09-11FaceAndLateralAngles.pdf", True, False, True, True, date.fromisoformat("2023-09-11"), date.fromisoformat("2050-01-01"), ["f","l"], True, False)
+100
View File
@@ -0,0 +1,100 @@
from tinydb import TinyDB
from datetime import datetime
import matplotlib.pyplot as plt
from os import path
from scipy.stats import spearmanr
import numpy as np
from datetime import date
from logging import getLogger
logger = getLogger("hairloss")
def averageWithDates(dates, values): # average points with same date
dates = np.array(dates)
values = np.array(values)
datesUnique = np.unique(dates)
valuesUnique = np.array([values[dates == datesCopy].mean() for datesCopy in datesUnique])
return (datesUnique, valuesUnique)
def plotHairline(models: [str], withClean: bool, withNormalized: bool, withRaw: bool, image_path: str, filepath: str, doSave: bool, doShow: bool, doRegression: bool, doCorrelation: bool, start: date, end: date, angles: [str], doAverage: bool, doPlot: bool):
db = TinyDB(path.abspath(path.join(image_path, "../hairlineResults.json")))
if doPlot and (doRegression or doCorrelation):
raise ValueError("You can't use regression or correlation wit doPlot set to true.")
if doPlot and not doAverage:
raise ValueError("doPlot needs doAverage set to True")
databaseEntry = db.all()
filteredDatabaseEntry = []
for entry in databaseEntry:
# we filter by date
if start <= date.fromisoformat(entry["filename"][:10]) <= end:
# and by angles
for angleType in angles:
if angleType in entry["filename"]:
filteredDatabaseEntry.append(entry)
dates = [datetime.strptime(d["filename"][:10], "%Y-%m-%d") for d in filteredDatabaseEntry]
plt.figure(figsize=(12, 6))
if (doRegression or doCorrelation) and withClean + withNormalized + withRaw != 1:
raise ValueError(f"With doRegression or doCorrelation exaclty one of withClean, withNormalized or withRaw must be set to true")
values = []
# plot raw values
if withRaw:
for model in models:
values = [d[model] for d in filteredDatabaseEntry]
if doAverage:
dates, values = averageWithDates(dates, values)
if doPlot:
plt.plot(dates, values, label=model)
else:
plt.scatter(dates, values, label=model)
# plot without uncertain
if withClean:
for model in models:
key = model + "WithoutUnsure"
values = [d[key] for d in filteredDatabaseEntry]
if doAverage:
dates, values = averageWithDates(dates, values)
if doPlot:
plt.plot(dates, values, label=key)
else:
plt.scatter(dates, values, label=key)
if withNormalized:
for model in models:
key = model + "Normalized"
values = [d[key] for d in filteredDatabaseEntry]
if doAverage:
dates, values = averageWithDates(dates, values)
if doPlot:
plt.plot(dates, values, label=key)
else:
plt.scatter(dates, values, label=key)
if doCorrelation:
rho, p = spearmanr([d.toordinal() for d in dates], values)
plt.plot([], [], ' ', label=f"Correlation factor of {rho:-3f} (with p-value of {p:.3g})")
if doRegression:
x = np.array([d.toordinal() for d in dates])
m, b = np.polyfit(x, values, 1)
plt.plot(dates, m*x + b, label=f"Regression line y={m}*x + {b}")
plt.xlabel("Dates")
plt.ylabel("Baldness score")
plt.title(filepath)
plt.grid(True, alpha=0.3)
plt.legend()
plt.tight_layout()
if doShow:
plt.show()
if doSave:
plt.savefig(filepath, bbox_inches="tight")
plt.close()