mirror of
https://github.com/tomasriveral/Hairloss-report.git
synced 2026-08-11 18:38:37 +02:00
upload the project
This commit is contained in:
@@ -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,
|
||||
)
|
||||
@@ -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}")
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user