simulation
This commit is contained in:
@@ -4,53 +4,36 @@
|
||||
# A1..A6, B1..B6, C1..C6, D1..D6
|
||||
#
|
||||
|
||||
PATH="data"
|
||||
PATH="./media/simulation"
|
||||
default_width=1000 # px
|
||||
default_height=1000 # px
|
||||
default_diameter=16.0 # mm
|
||||
|
||||
declare -A arguments=(
|
||||
# key count len width sec fps seed thigmotaxis bg-color arena-color arena-border shadow-color body-color body-dark body-light head-color
|
||||
["F0"]="1 0.90 0.30 60 5 64 0.45 #D4DADC #BDBDF0 #B4AFA8 #BEBCB6 #5A7896 #3C5570 #8CA0B4 #46645F"
|
||||
)
|
||||
# key count length width fps duration seed bg-color arena-color arena-border shadow-color body-color body-dark body-light head-color thresh-immobile thresh-mobile thigmotaxis photo-mode photo-strength photo-x photo-y photo-sine-freq photo-radius chemo-strength chemo-x chemo-y chemo-radius avoid-strength avoid-radius aggreg-strength aggreg-radius chem-repulsion chem-decay
|
||||
["A1"]="3 0.40 0.30 5 60 64 #EBEBEB #FAFAFA #8C8C8C #C8C8C8 #A5A5A5 #373737 #D2D2D2 #828282 0.2 1.5 0.45 none 0.50 0.50 0.50 0.10 0.30 0.0 0.70 0.70 2.0 1.0 3.0 0.0 6.0 0.0 0.95"
|
||||
["A2"]="1 0.42 0.32 5 60 96 #EBEBEB #FAFAFA #8C8C8C #C8C8C8 #A5A5A5 #373737 #D2D2D2 #828282 0.2 1.5 0.70 fixed 0.50 0.50 0.50 0.10 0.30 0.0 0.70 0.70 2.0 0.0 3.0 0.0 6.0 0.0 0.95"
|
||||
["A3"]="1 0.50 0.40 5 60 128 #EBEBEB #FAFAFA #8C8C8C #C8C8C8 #A5A5A5 #373737 #D2D2D2 #828282 0.2 1.5 0.70 radial 0.50 0.50 0.50 0.10 0.30 0.5 0.70 0.70 2.0 0.0 3.0 0.0 6.0 0.0 0.95"
|
||||
|
||||
declare -A arguments2=(
|
||||
# key count len width sec fps seed thigmotaxis bg-color arena-color arena-border shadow-color body-color body-dark body-light head-color
|
||||
["D1"]="3 0.90 0.30 60 5 64 0.45 #D4DADC #BDBDF0 #B4AFA8 #BEBCB6 #5A7896 #3C5570 #8CA0B4 #46645F"
|
||||
["D2"]="2 0.75 0.40 60 5 96 0.50 #D4DADC #BDBDF0 #B4AFA8 #BEBCB6 #5B7896 #3D5570 #8DA0B4 #47645F"
|
||||
["D3"]="1 0.80 0.50 60 5 42 0.60 #D4DADC #BDBDF0 #B4AFA8 #BEBCB6 #5C7896 #3E5570 #8EA0B4 #48645F"
|
||||
["D4"]="1 0.85 0.40 60 5 28 0.70 #D4DADC #BDBDF0 #B4AFA8 #BEBCB6 #5C7896 #3E5570 #8EA0B4 #48645F"
|
||||
["D5"]="3 0.60 0.35 60 5 132 0.65 #D4DADC #BDBDF0 #B4AFA8 #BEBCB6 #5C7896 #3E5570 #8EA0B4 #48645F"
|
||||
["D6"]="2 0.65 0.35 60 5 256 0.85 #D4DADC #BDBDF0 #B4AFA8 #BEBCB6 #5C7896 #3E5570 #8EA0B4 #48645F"
|
||||
["C6"]="1 0.90 0.30 60 5 64 0.45 #D4DADC #BDBDF0 #B4AFA8 #BEBCB6 #5A7896 #3C5570 #8CA0B4 #46645F"
|
||||
["C5"]="3 0.75 0.40 60 5 96 0.50 #D4DADC #BDBDF0 #B4AFA8 #BEBCB6 #5B7896 #3D5570 #8DA0B4 #47645F"
|
||||
["C4"]="2 0.80 0.50 60 5 42 0.60 #D4DADC #BDBDF0 #B4AFA8 #BEBCB6 #5C7896 #3E5570 #8EA0B4 #48645F"
|
||||
["C3"]="1 0.85 0.40 60 5 28 0.70 #D4DADC #BDBDF0 #B4AFA8 #BEBCB6 #5C7896 #3E5570 #8EA0B4 #48645F"
|
||||
["C2"]="2 0.60 0.35 60 5 132 0.65 #D4DADC #BDBDF0 #B4AFA8 #BEBCB6 #5C7896 #3E5570 #8EA0B4 #48645F"
|
||||
["C1"]="3 0.65 0.35 60 5 256 0.85 #D4DADC #BDBDF0 #B4AFA8 #BEBCB6 #5C7896 #3E5570 #8EA0B4 #48645F"
|
||||
["B1"]="2 0.90 0.30 60 5 64 0.45 #D4DADC #BDBDF0 #B4AFA8 #BEBCB6 #5A7896 #3C5570 #8CA0B4 #46645F"
|
||||
["B2"]="1 0.75 0.40 60 5 96 0.50 #D4DADC #BDBDF0 #B4AFA8 #BEBCB6 #5B7896 #3D5570 #8DA0B4 #47645F"
|
||||
["B3"]="1 0.80 0.50 60 5 42 0.60 #D4DADC #BDBDF0 #B4AFA8 #BEBCB6 #5C7896 #3E5570 #8EA0B4 #48645F"
|
||||
["B4"]="3 0.85 0.40 60 5 28 0.70 #D4DADC #BDBDF0 #B4AFA8 #BEBCB6 #5C7896 #3E5570 #8EA0B4 #48645F"
|
||||
["B5"]="1 0.60 0.35 60 5 132 0.65 #D4DADC #BDBDF0 #B4AFA8 #BEBCB6 #5C7896 #3E5570 #8EA0B4 #48645F"
|
||||
["B6"]="2 0.65 0.35 60 5 256 0.85 #D4DADC #BDBDF0 #B4AFA8 #BEBCB6 #5C7896 #3E5570 #8EA0B4 #48645F"
|
||||
["A6"]="1 0.90 0.30 60 5 64 0.45 #D4DADC #BDBDF0 #B4AFA8 #BEBCB6 #5A7896 #3C5570 #8CA0B4 #46645F"
|
||||
["A5"]="1 0.75 0.40 60 5 96 0.50 #D4DADC #BDBDF0 #B4AFA8 #BEBCB6 #5B7896 #3D5570 #8DA0B4 #47645F"
|
||||
["A4"]="3 0.80 0.50 60 5 42 0.60 #D4DADC #BDBDF0 #B4AFA8 #BEBCB6 #5C7896 #3E5570 #8EA0B4 #48645F"
|
||||
["A3"]="1 0.85 0.40 60 5 28 0.70 #D4DADC #BDBDF0 #B4AFA8 #BEBCB6 #5C7896 #3E5570 #8EA0B4 #48645F"
|
||||
["A2"]="1 0.60 0.35 60 5 132 0.65 #D4DADC #BDBDF0 #B4AFA8 #BEBCB6 #5C7896 #3E5570 #8EA0B4 #48645F"
|
||||
["A1"]="4 0.65 0.35 60 5 256 0.85 #D4DADC #BDBDF0 #B4AFA8 #BEBCB6 #5C7896 #3E5570 #8EA0B4 #48645F"
|
||||
)
|
||||
|
||||
|
||||
for key in "${!arguments[@]}"; do
|
||||
args="${arguments[$key]}"
|
||||
read -r count length width duration fps seed thigmotaxis bg_color arena_color arena_border shadow_color body_color body_dark body_light head_color <<< "$args"
|
||||
read -r count length width fps duration seed bg_color arena_color arena_border shadow_color \
|
||||
body_color body_dark body_light head_color thresh_immobile thresh_mobile thigmotaxis \
|
||||
photo_mode photo_strength photo_x photo_y photo_sine_freq photo_radius chemo_strength chemo_x chemo_y chemo_radius \
|
||||
avoid_strength avoid_radius aggreg_strength aggreg_radius chem_repulsion chem_decay <<< "$args"
|
||||
|
||||
echo "==== Exécution de $PATH/$key.mp4"
|
||||
|
||||
./planarian_sim.py --output "$PATH/$key.mp4" --default_width "$default_width" --default_height "$default_height" --default_diameter "$default_diameter" \
|
||||
--count "$count" --length "$length" --width "$width" --duration "$duration" --fps "$fps" --seed "$seed" --thigmotaxis "$thigmotaxis" \
|
||||
--bg-color "$bg_color" --arena-color "$arena_color" --arena-border "$arena_border" --shadow-color "$shadow_color" \
|
||||
--body-color "$body_color" --body-dark "$body_dark" --body-light "$body_light" --head-color "$head_color" --no-csv
|
||||
./planarian_sim.py --output "$PATH/$key.mp4" --default_width "$default_width" --default_height "$default_height" --default_diameter "$default_diameter" --no-csv \
|
||||
--count "$count" --length "$length" --width "$width" --duration "$duration" --fps "$fps" --seed "$seed" \
|
||||
--bg-color "$bg_color" --arena-color "$arena_color" --arena-border "$arena_border" --shadow-color "$shadow_color" \
|
||||
--body-color "$body_color" --body-dark "$body_dark" --body-light "$body_light" --head-color "$head_color" \
|
||||
--thresh-immobile "$thresh_immobile" --thresh-mobile "$thresh_mobile" --thigmotaxis "$thigmotaxis" \
|
||||
--photo-mode "$photo_mode" --photo-strength "$photo_strength" --photo-x "$photo_x" --photo-y "$photo_y" --photo-sine-freq "$photo_sine_freq" --photo-radius "$photo_radius" \
|
||||
--chemo-strength "$chemo_strength" --chemo-x "$chemo_x" --chemo-y "$chemo_y" --chemo-radius "$chemo_radius" \
|
||||
--avoid-strength "$avoid_strength" --avoid-radius "$avoid_radius" --aggreg-strength "$aggreg_strength" --aggreg-radius "$aggreg_radius" \
|
||||
--chem-repulsion "$chem_repulsion" --chem-decay "$chem_decay"
|
||||
done
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,7 @@
|
||||
#!../.venv/bin/python
|
||||
"""
|
||||
Planaire simulation de mouvement aléatoire
|
||||
==========================================
|
||||
|
||||
Espace circulaire de 16mm de diamètre, 500x500px paramètrable
|
||||
Planaria random movement simulation - top view
|
||||
Espace circulaire de 16mm de diamètre, 500x500px
|
||||
Supporte plusieurs planaires avec paramètres configurables via arguments CLI.
|
||||
Export CSV par planaire compatible EthoVision XT.
|
||||
|
||||
@@ -13,55 +11,17 @@ Comportements simulés :
|
||||
- Chimiotactisme : attraction vers une source de nourriture (--chemo-strength)
|
||||
- Inter-individus : évitement de contact, agrégation, répulsion chimique
|
||||
|
||||
Seuils EthoVision par défaut (configurables en arguments) :
|
||||
|
||||
Immobile : déplacement < 0.2 mm/s
|
||||
Mobile : 0.2 à 1.5 mm/s
|
||||
Très mobile : > 1.5 mm/s
|
||||
|
||||
EthoVision CSV frames CSV summary
|
||||
========== ========== ===========
|
||||
movedCenter-pointTotalmm total_distance_mm movedCenter_pointTotal_mm
|
||||
VelocityCenter-pointMeanmm/s velocity_mm_s velocity_mean_mm_s
|
||||
MovementMoving moving, duration_moving_s movement_moving_duration_s
|
||||
MovementNot Moving duration_stopped_s movement_not_moving_duration_s
|
||||
ImmobileFrequency / Duration mobility_state mobility_immobile_frequency/duration_s
|
||||
MobileFrequency / Duration mobility_state mobility_mobile_frequency/duration_s
|
||||
Highly mobileFrequency / Duration mobility_state mobility_highly_mobile_frequency/duration_s
|
||||
|
||||
|
||||
Métriques calculées :
|
||||
- Distance totale parcourue (mm) → movedCenter-pointTotalmm
|
||||
- Vitesse instantanée (mm/s) → VelocityCenter-pointMeanmm/s
|
||||
- Durée cumulée en mouvement (s) → MovementMoving
|
||||
- Durée cumulée à l'arrêt (s) → MovementNot Moving
|
||||
- Fréquence et durée par état de mobilité → Mobility state (EthoVision)
|
||||
- Distance à la paroi (mm) → thigmotactisme
|
||||
|
||||
Comportements simulés :
|
||||
- Thigmotactisme : attraction vers la paroi (--thigmotaxis)
|
||||
- Phototactisme : fuite de la lumière (--photo-mode, --photo-strength)
|
||||
- Chimiotactisme : attraction vers une source de nourriture (--chemo-strength)
|
||||
- Inter-individus : évitement de contact, agrégation, répulsion chimique
|
||||
|
||||
Usage:
|
||||
python3 planarian_sim.py [options]
|
||||
python3 planaire_sim.py [options]
|
||||
|
||||
Exemples:
|
||||
python3 planarian_sim.py
|
||||
python3 planarian_sim.py --count 5 --fps 25 --duration 20
|
||||
python3 planarian_sim.py --count 3 --length 8.0 --width 1.2
|
||||
|
||||
python3 planarian_sim.py --bg-color "#E0DAD4" --arena-color "#F0EBE0"- -thigmotaxis 0.7
|
||||
python3 planarian_sim.py --bg-color beige --arena-color ivory --shadow-color lightgray
|
||||
python3 planarian_sim.py --bg-color beige --arena-color "#FAF0E0" --shadow-color "160 155 148"
|
||||
|
||||
python3 planarian_sim.py --count 5 --thigmotaxis 0.4
|
||||
python3 planarian_sim.py --count 5 --photo-mode fixed --photo-x 0.2 --photo-y 0.2 --photo-strength 0.6
|
||||
python3 planarian_sim.py --count 5 --chemo-x 0.7 --chemo-y 0.5 --chemo-strength 0.5
|
||||
python3 planarian_sim .py --count 5 --avoid-strength 0.6 --aggreg-strength 0.2
|
||||
|
||||
python3 planaire_sim.py --count 5 --thigmotaxis 0.4
|
||||
python3 planaire_sim.py --count 5 --photo-mode fixed --photo-x 0.2 --photo-y 0.2 --photo-strength 0.6
|
||||
python3 planaire_sim.py --count 5 --chemo-x 0.7 --chemo-y 0.5 --chemo-strength 0.5
|
||||
python3 planaire_sim.py --count 5 --avoid-strength 0.6 --aggreg-strength 0.2
|
||||
"""
|
||||
import os
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'home.settings')
|
||||
|
||||
import csv
|
||||
import cv2
|
||||
@@ -72,11 +32,15 @@ except ImportError:
|
||||
HAS_METRICS = False
|
||||
import numpy as np
|
||||
import math
|
||||
import os
|
||||
import random
|
||||
import argparse
|
||||
import re
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
CSV_DIR = str(settings.MEDIA_ROOT / "simulation" / "planarian_sim_csv")
|
||||
VIDEO_PATH = str(settings.MEDIA_ROOT / "simulation" / "planarian_simulation.mp4")
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Noms CSS courants → BGR
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -154,20 +118,19 @@ def parse_args():
|
||||
|
||||
# --- Paramètres vidéo ---
|
||||
vg = parser.add_argument_group("Paramètres vidéo")
|
||||
vg.add_argument("--fps", type=int, default=10, help="Images par seconde")
|
||||
vg.add_argument("--duration", type=int, default=10, help="Durée en secondes")
|
||||
vg.add_argument("--output", type=str, default="planaire_simulation.mp4",
|
||||
help="Fichier vidéo de sortie")
|
||||
vg.add_argument("--seed", type=int, default=42, help="Graine aléatoire")
|
||||
|
||||
vg.add_argument("--default_width", type=int, default=500, help="Image: largeur par défaut px")
|
||||
vg.add_argument("--default_height", type=int, default=500, help="Image: hauteur par défautpx")
|
||||
vg.add_argument("--default_diameter", type=float, default=16.0, help="Diamètre tube par défaut mm")
|
||||
|
||||
vg.add_argument("--fps", type=int, default=10, help="Images par seconde")
|
||||
vg.add_argument("--duration", type=int, default=10, help="Durée en secondes")
|
||||
vg.add_argument("--output", type=str, default=VIDEO_PATH, help="Fichier vidéo de sortie")
|
||||
vg.add_argument("--seed", type=int, default=42, help="Graine aléatoire")
|
||||
|
||||
# --- Morphologie ---
|
||||
pg = parser.add_argument_group("Morphologie du planaire")
|
||||
pg.add_argument("--length", type=float, default=6.0, help="Longueur en mm")
|
||||
pg.add_argument("--width", type=float, default=0.8, help="Largeur max en mm")
|
||||
pg.add_argument("--length", type=float, default=1.0, help="Longueur en mm")
|
||||
pg.add_argument("--width", type=float, default=0.35, help="Largeur max en mm")
|
||||
pg.add_argument("--count", type=int, default=1, help="Nombre de planaires (1-20)")
|
||||
|
||||
# --- Thigmotactisme ---
|
||||
@@ -239,7 +202,7 @@ def parse_args():
|
||||
|
||||
# --- Export CSV ---
|
||||
eg = parser.add_argument_group("Export métriques")
|
||||
eg.add_argument("--csv-dir", type=str, default=".", help="Répertoire de sortie CSV")
|
||||
eg.add_argument("--csv-dir", type=str, default=CSV_DIR, help="Répertoire de sortie CSV")
|
||||
eg.add_argument("--no-csv", action="store_true", help="Désactiver l'export CSV")
|
||||
|
||||
# --- Couleurs ---
|
||||
@@ -248,21 +211,29 @@ def parse_args():
|
||||
"Formats : #RRGGBB | R G B (RGB) | nom CSS (beige, tan, white…)"
|
||||
)
|
||||
kg.add_argument("--bg-color", nargs='+', action=ColorAction,
|
||||
default=(212, 218, 220), metavar="COULEUR", help="Fond extérieur")
|
||||
default=(235, 235, 235), metavar="COULEUR",
|
||||
help="Fond extérieur (vue dessous, lumière transmise) $EBEBEB")
|
||||
kg.add_argument("--arena-color", nargs='+', action=ColorAction,
|
||||
default=(222, 228, 230), metavar="COULEUR", help="Intérieur de l'arène")
|
||||
default=(250, 250, 250), metavar="COULEUR",
|
||||
help="Intérieur arène — blanc éclairé par transmission $FAFAFA")
|
||||
kg.add_argument("--arena-border", nargs='+', action=ColorAction,
|
||||
default=(168, 175, 180), metavar="COULEUR", help="Bordure de l'arène")
|
||||
default=(140, 140, 140), metavar="COULEUR",
|
||||
help="Bordure arène $8C8C8C — légèrement plus sombre que l'arène")
|
||||
kg.add_argument("--shadow-color", nargs='+', action=ColorAction,
|
||||
default=(182, 188, 190), metavar="COULEUR", help="Ombre portée")
|
||||
default=(200, 200, 200), metavar="COULEUR",
|
||||
help="Ombre portée — très légère sous lumière transmise $C8C8C8")
|
||||
kg.add_argument("--body-color", nargs='+', action=ColorAction,
|
||||
default=(150, 120, 90), metavar="COULEUR", help="Corps principal")
|
||||
default=(165, 165, 165), metavar="COULEUR",
|
||||
help="Corps — gris translucide moyen $A5A5A5")
|
||||
kg.add_argument("--body-dark", nargs='+', action=ColorAction,
|
||||
default=(110, 85, 60), metavar="COULEUR", help="Pigmentation sombre")
|
||||
default=(55, 55, 55), metavar="COULEUR",
|
||||
help="Contour sombre net du corps $373737 — pour le contraste et la lisibilité")
|
||||
kg.add_argument("--body-light", nargs='+', action=ColorAction,
|
||||
default=(180, 160, 140), metavar="COULEUR", help="Reflet ventral")
|
||||
default=(210, 210, 210), metavar="COULEUR",
|
||||
help="Centre du corps — plus clair par transparence $D2D2D2")
|
||||
kg.add_argument("--head-color", nargs='+', action=ColorAction,
|
||||
default=(130, 100, 70), metavar="COULEUR", help="Tête")
|
||||
default=(130, 130, 130), metavar="COULEUR",
|
||||
help="Tête — légèrement plus sombre que le corps $828282 — pour la différencier du reste du corps")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
@@ -544,23 +515,27 @@ class Planaire:
|
||||
self.width_px = max(3, int(cfg.planaire_width_px * random.uniform(0.75, 1.25)))
|
||||
|
||||
# --- Palette de couleur individuelle (5 familles naturalistes) ---
|
||||
# Palettes grises — vue de dessous, lumière transmise par le dessus.
|
||||
# Teinte uniforme gris moyen, seul le niveau de gris varie légèrement
|
||||
# entre individus pour les distinguer visuellement.
|
||||
PALETTES = [
|
||||
{"body": ( 90, 120, 150), "dark": (55, 80, 105), "light": (140, 160, 180), "head": ( 65, 95, 125)},
|
||||
{"body": ( 70, 110, 160), "dark": (45, 75, 120), "light": (120, 150, 185), "head": ( 50, 85, 140)},
|
||||
{"body": ( 55, 80, 110), "dark": (35, 55, 80), "light": (100, 130, 155), "head": ( 40, 60, 90)},
|
||||
{"body": (105, 118, 132), "dark": (70, 85, 98), "light": (150, 162, 172), "head": ( 85, 100, 115)},
|
||||
{"body": ( 60, 115, 155), "dark": (40, 80, 115), "light": (110, 155, 185), "head": ( 45, 90, 135)},
|
||||
{"body": (165, 165, 165), "dark": (50, 50, 50), "light": (210, 210, 210), "head": (130, 130, 130)},
|
||||
{"body": (150, 150, 150), "dark": (45, 45, 45), "light": (200, 200, 200), "head": (118, 118, 118)},
|
||||
{"body": (178, 178, 178), "dark": (58, 58, 58), "light": (218, 218, 218), "head": (142, 142, 142)},
|
||||
{"body": (158, 158, 158), "dark": (48, 48, 48), "light": (205, 205, 205), "head": (125, 125, 125)},
|
||||
{"body": (172, 172, 172), "dark": (55, 55, 55), "light": (215, 215, 215), "head": (138, 138, 138)},
|
||||
]
|
||||
palette = PALETTES[random.randint(0, len(PALETTES) - 1)]
|
||||
|
||||
def jitter(color, amount=12):
|
||||
"""Ajoute une légère variation aléatoire à une couleur BGR."""
|
||||
return tuple(max(0, min(255, c + random.randint(-amount, amount))) for c in color)
|
||||
def jitter(color, amount=5):
|
||||
"""Variation individuelle minimale — teinte grise très uniforme."""
|
||||
v = random.randint(-amount, amount)
|
||||
return tuple(max(0, min(255, c + v)) for c in color)
|
||||
|
||||
self.body_color = jitter(palette["body"])
|
||||
self.body_dark = jitter(palette["dark"], 8)
|
||||
self.body_light = jitter(palette["light"], 8)
|
||||
self.head_color = jitter(palette["head"], 8)
|
||||
self.body_dark = jitter(palette["dark"], 3)
|
||||
self.body_light = jitter(palette["light"], 3)
|
||||
self.head_color = jitter(palette["head"], 3)
|
||||
self.shadow_color = tuple(cfg.shadow_color)
|
||||
|
||||
# --- Sensibilités individuelles (variation ±30% autour des valeurs globales) ---
|
||||
@@ -927,39 +902,43 @@ class Planaire:
|
||||
if n < 2:
|
||||
return
|
||||
|
||||
shadow_offset = (2, 2)
|
||||
# --- Vue de dessous, lumière transmise par le dessus ---
|
||||
# Couche 1 : ombre très légère (décalée 1px) — lumière quasi-uniforme
|
||||
for i in range(n - 1):
|
||||
t = i / max(n - 1, 1)
|
||||
w = max(1, int(self._body_width_at(t) * 0.85))
|
||||
p1 = (int(self.body_history[i][0]) + shadow_offset[0],
|
||||
int(self.body_history[i][1]) + shadow_offset[1])
|
||||
p2 = (int(self.body_history[i+1][0]) + shadow_offset[0],
|
||||
int(self.body_history[i+1][1]) + shadow_offset[1])
|
||||
w = max(1, int(self._body_width_at(t)))
|
||||
p1 = (int(self.body_history[i][0]) + 1,
|
||||
int(self.body_history[i][1]) + 1)
|
||||
p2 = (int(self.body_history[i+1][0]) + 1,
|
||||
int(self.body_history[i+1][1]) + 1)
|
||||
cv2.line(frame, p1, p2, self.shadow_color, w)
|
||||
|
||||
# Couche 2 : corps gris uniforme (teinte de base, sans gradient)
|
||||
for i in range(n - 1):
|
||||
t = i / max(n - 1, 1)
|
||||
w = max(1, int(self._body_width_at(t)))
|
||||
p1 = (int(self.body_history[i][0]), int(self.body_history[i][1]))
|
||||
p2 = (int(self.body_history[i+1][0]), int(self.body_history[i+1][1]))
|
||||
color = tuple(int(self.head_color[c] * (1-t) + self.body_light[c] * t) for c in range(3))
|
||||
cv2.line(frame, p1, p2, color, w)
|
||||
cv2.line(frame, p1, p2, self.body_color, w)
|
||||
|
||||
# Couche 3 : contour sombre net (liseré caractéristique vue de dessous)
|
||||
# Dessiné en 2 passes : largeur w+2 (contour) puis w-2 (remplissage corps)
|
||||
for i in range(n - 1):
|
||||
t = i / max(n - 1, 1)
|
||||
w = max(1, int(self._body_width_at(t)))
|
||||
p1 = (int(self.body_history[i][0]), int(self.body_history[i][1]))
|
||||
p2 = (int(self.body_history[i+1][0]), int(self.body_history[i+1][1]))
|
||||
cv2.line(frame, p1, p2, self.body_dark, w + 2) # contour
|
||||
cv2.line(frame, p1, p2, self.body_color, max(1, w - 1)) # remplissage
|
||||
|
||||
# Couche 4 : centre clair — lumière transmise au travers du corps
|
||||
for i in range(n - 1):
|
||||
t = i / max(n - 1, 1)
|
||||
if 0.08 < t < 0.85:
|
||||
w = max(1, int(self._body_width_at(t) * 0.28))
|
||||
if 0.10 < t < 0.90:
|
||||
w = max(1, int(self._body_width_at(t) * 0.35))
|
||||
p1 = (int(self.body_history[i][0]), int(self.body_history[i][1]))
|
||||
p2 = (int(self.body_history[i+1][0]), int(self.body_history[i+1][1]))
|
||||
cv2.line(frame, p1, p2, self.body_dark, w)
|
||||
|
||||
for i in range(n - 1):
|
||||
t = i / max(n - 1, 1)
|
||||
if 0.15 < t < 0.75:
|
||||
w = max(1, int(self._body_width_at(t) * 0.18))
|
||||
p1 = (int(self.body_history[i][0]), int(self.body_history[i][1]))
|
||||
p2 = (int(self.body_history[i+1][0]), int(self.body_history[i+1][1]))
|
||||
cv2.line(frame, p1, p2, (160, 175, 190), w)
|
||||
cv2.line(frame, p1, p2, self.body_light, w)
|
||||
|
||||
head = self.body_history[0]
|
||||
neck = self.body_history[min(3, n - 1)]
|
||||
@@ -972,14 +951,27 @@ class Planaire:
|
||||
right_ear = (int(head[0] + math.cos(head_angle - 1.8) * lw),
|
||||
int(head[1] + math.sin(head_angle - 1.8) * lw))
|
||||
pts = np.array([tip, left_ear, right_ear], dtype=np.int32)
|
||||
cv2.fillPoly(frame, [pts], self.head_color)
|
||||
cv2.polylines(frame, [pts], True, self.body_dark, 1)
|
||||
# Contour sombre net puis remplissage gris uniforme
|
||||
cv2.fillPoly(frame, [pts], self.body_dark)
|
||||
# Remplissage légèrement rétréci pour laisser le contour visible
|
||||
inner_tip = (
|
||||
int(head[0] + math.cos(head_angle) * (self.width_px * 0.3)),
|
||||
int(head[1] + math.sin(head_angle) * (self.width_px * 0.3))
|
||||
)
|
||||
ilw = lw * 0.6
|
||||
inner_l = (int(head[0] + math.cos(head_angle + 1.8) * ilw),
|
||||
int(head[1] + math.sin(head_angle + 1.8) * ilw))
|
||||
inner_r = (int(head[0] + math.cos(head_angle - 1.8) * ilw),
|
||||
int(head[1] + math.sin(head_angle - 1.8) * ilw))
|
||||
pts_inner = np.array([inner_tip, inner_l, inner_r], dtype=np.int32)
|
||||
cv2.fillPoly(frame, [pts_inner], self.body_color)
|
||||
|
||||
eye_d = lw * 0.6
|
||||
# Yeux (photorécepteurs) : points sombres nets
|
||||
eye_d = lw * 0.55
|
||||
for side in [1.3, -1.3]:
|
||||
ex = int(head[0] + math.cos(head_angle + side) * eye_d * 0.7)
|
||||
ey = int(head[1] + math.sin(head_angle + side) * eye_d * 0.7)
|
||||
cv2.circle(frame, (ex, ey), max(1, self.width_px // 5), (30, 40, 50), -1)
|
||||
ex = int(head[0] + math.cos(head_angle + side) * eye_d * 0.65)
|
||||
ey = int(head[1] + math.sin(head_angle + side) * eye_d * 0.65)
|
||||
cv2.circle(frame, (ex, ey), max(1, self.width_px // 6), self.body_dark, -1)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -1152,14 +1144,9 @@ def main():
|
||||
np.random.seed(args.seed)
|
||||
|
||||
# --- Constantes dérivées ---
|
||||
#WIDTH, HEIGHT = 500, 500
|
||||
#TOTAL_FRAMES = args.fps * args.duration
|
||||
#MM_TO_PX = 420 / 16.0 # ~26.25 px/mm
|
||||
|
||||
WIDTH, HEIGHT = args.default_width, args.default_height
|
||||
WIDTH, HEIGHT = 500, 500
|
||||
TOTAL_FRAMES = args.fps * args.duration
|
||||
MM_TO_PX = (args.default_width - 80) / args.default_diameter # ~26.25 px/mm
|
||||
|
||||
MM_TO_PX = 420 / 16.0 # ~26.25 px/mm
|
||||
ARENA_RADIUS_PX = int(8 * MM_TO_PX)
|
||||
ARENA_CENTER = (WIDTH // 2, HEIGHT // 2)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user