simulation
This commit is contained in:
@@ -4,53 +4,36 @@
|
|||||||
# A1..A6, B1..B6, C1..C6, D1..D6
|
# A1..A6, B1..B6, C1..C6, D1..D6
|
||||||
#
|
#
|
||||||
|
|
||||||
PATH="data"
|
PATH="./media/simulation"
|
||||||
default_width=1000 # px
|
default_width=1000 # px
|
||||||
default_height=1000 # px
|
default_height=1000 # px
|
||||||
default_diameter=16.0 # mm
|
default_diameter=16.0 # mm
|
||||||
|
|
||||||
declare -A arguments=(
|
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
|
# 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
|
||||||
["F0"]="1 0.90 0.30 60 5 64 0.45 #D4DADC #BDBDF0 #B4AFA8 #BEBCB6 #5A7896 #3C5570 #8CA0B4 #46645F"
|
["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
|
for key in "${!arguments[@]}"; do
|
||||||
args="${arguments[$key]}"
|
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"
|
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" \
|
./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" --thigmotaxis "$thigmotaxis" \
|
--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" \
|
--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
|
--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
|
done
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,7 @@
|
|||||||
#!../.venv/bin/python
|
#!../.venv/bin/python
|
||||||
"""
|
"""
|
||||||
Planaire simulation de mouvement aléatoire
|
Planaria random movement simulation - top view
|
||||||
==========================================
|
Espace circulaire de 16mm de diamètre, 500x500px
|
||||||
|
|
||||||
Espace circulaire de 16mm de diamètre, 500x500px paramètrable
|
|
||||||
Supporte plusieurs planaires avec paramètres configurables via arguments CLI.
|
Supporte plusieurs planaires avec paramètres configurables via arguments CLI.
|
||||||
Export CSV par planaire compatible EthoVision XT.
|
Export CSV par planaire compatible EthoVision XT.
|
||||||
|
|
||||||
@@ -13,55 +11,17 @@ Comportements simulés :
|
|||||||
- Chimiotactisme : attraction vers une source de nourriture (--chemo-strength)
|
- Chimiotactisme : attraction vers une source de nourriture (--chemo-strength)
|
||||||
- Inter-individus : évitement de contact, agrégation, répulsion chimique
|
- 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:
|
Usage:
|
||||||
python3 planarian_sim.py [options]
|
python3 planaire_sim.py [options]
|
||||||
|
|
||||||
Exemples:
|
Exemples:
|
||||||
python3 planarian_sim.py
|
python3 planaire_sim.py --count 5 --thigmotaxis 0.4
|
||||||
python3 planarian_sim.py --count 5 --fps 25 --duration 20
|
python3 planaire_sim.py --count 5 --photo-mode fixed --photo-x 0.2 --photo-y 0.2 --photo-strength 0.6
|
||||||
python3 planarian_sim.py --count 3 --length 8.0 --width 1.2
|
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
|
||||||
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
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
import os
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'home.settings')
|
||||||
|
|
||||||
import csv
|
import csv
|
||||||
import cv2
|
import cv2
|
||||||
@@ -72,11 +32,15 @@ except ImportError:
|
|||||||
HAS_METRICS = False
|
HAS_METRICS = False
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import math
|
import math
|
||||||
import os
|
|
||||||
import random
|
import random
|
||||||
import argparse
|
import argparse
|
||||||
import re
|
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
|
# Noms CSS courants → BGR
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -154,20 +118,19 @@ def parse_args():
|
|||||||
|
|
||||||
# --- Paramètres vidéo ---
|
# --- Paramètres vidéo ---
|
||||||
vg = parser.add_argument_group("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_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_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("--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 ---
|
# --- Morphologie ---
|
||||||
pg = parser.add_argument_group("Morphologie du planaire")
|
pg = parser.add_argument_group("Morphologie du planaire")
|
||||||
pg.add_argument("--length", type=float, default=6.0, help="Longueur en mm")
|
pg.add_argument("--length", type=float, default=1.0, help="Longueur en mm")
|
||||||
pg.add_argument("--width", type=float, default=0.8, help="Largeur max 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)")
|
pg.add_argument("--count", type=int, default=1, help="Nombre de planaires (1-20)")
|
||||||
|
|
||||||
# --- Thigmotactisme ---
|
# --- Thigmotactisme ---
|
||||||
@@ -239,7 +202,7 @@ def parse_args():
|
|||||||
|
|
||||||
# --- Export CSV ---
|
# --- Export CSV ---
|
||||||
eg = parser.add_argument_group("Export métriques")
|
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")
|
eg.add_argument("--no-csv", action="store_true", help="Désactiver l'export CSV")
|
||||||
|
|
||||||
# --- Couleurs ---
|
# --- Couleurs ---
|
||||||
@@ -248,21 +211,29 @@ def parse_args():
|
|||||||
"Formats : #RRGGBB | R G B (RGB) | nom CSS (beige, tan, white…)"
|
"Formats : #RRGGBB | R G B (RGB) | nom CSS (beige, tan, white…)"
|
||||||
)
|
)
|
||||||
kg.add_argument("--bg-color", nargs='+', action=ColorAction,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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()
|
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)))
|
self.width_px = max(3, int(cfg.planaire_width_px * random.uniform(0.75, 1.25)))
|
||||||
|
|
||||||
# --- Palette de couleur individuelle (5 familles naturalistes) ---
|
# --- 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 = [
|
PALETTES = [
|
||||||
{"body": ( 90, 120, 150), "dark": (55, 80, 105), "light": (140, 160, 180), "head": ( 65, 95, 125)},
|
{"body": (165, 165, 165), "dark": (50, 50, 50), "light": (210, 210, 210), "head": (130, 130, 130)},
|
||||||
{"body": ( 70, 110, 160), "dark": (45, 75, 120), "light": (120, 150, 185), "head": ( 50, 85, 140)},
|
{"body": (150, 150, 150), "dark": (45, 45, 45), "light": (200, 200, 200), "head": (118, 118, 118)},
|
||||||
{"body": ( 55, 80, 110), "dark": (35, 55, 80), "light": (100, 130, 155), "head": ( 40, 60, 90)},
|
{"body": (178, 178, 178), "dark": (58, 58, 58), "light": (218, 218, 218), "head": (142, 142, 142)},
|
||||||
{"body": (105, 118, 132), "dark": (70, 85, 98), "light": (150, 162, 172), "head": ( 85, 100, 115)},
|
{"body": (158, 158, 158), "dark": (48, 48, 48), "light": (205, 205, 205), "head": (125, 125, 125)},
|
||||||
{"body": ( 60, 115, 155), "dark": (40, 80, 115), "light": (110, 155, 185), "head": ( 45, 90, 135)},
|
{"body": (172, 172, 172), "dark": (55, 55, 55), "light": (215, 215, 215), "head": (138, 138, 138)},
|
||||||
]
|
]
|
||||||
palette = PALETTES[random.randint(0, len(PALETTES) - 1)]
|
palette = PALETTES[random.randint(0, len(PALETTES) - 1)]
|
||||||
|
|
||||||
def jitter(color, amount=12):
|
def jitter(color, amount=5):
|
||||||
"""Ajoute une légère variation aléatoire à une couleur BGR."""
|
"""Variation individuelle minimale — teinte grise très uniforme."""
|
||||||
return tuple(max(0, min(255, c + random.randint(-amount, amount))) for c in color)
|
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_color = jitter(palette["body"])
|
||||||
self.body_dark = jitter(palette["dark"], 8)
|
self.body_dark = jitter(palette["dark"], 3)
|
||||||
self.body_light = jitter(palette["light"], 8)
|
self.body_light = jitter(palette["light"], 3)
|
||||||
self.head_color = jitter(palette["head"], 8)
|
self.head_color = jitter(palette["head"], 3)
|
||||||
self.shadow_color = tuple(cfg.shadow_color)
|
self.shadow_color = tuple(cfg.shadow_color)
|
||||||
|
|
||||||
# --- Sensibilités individuelles (variation ±30% autour des valeurs globales) ---
|
# --- Sensibilités individuelles (variation ±30% autour des valeurs globales) ---
|
||||||
@@ -927,39 +902,43 @@ class Planaire:
|
|||||||
if n < 2:
|
if n < 2:
|
||||||
return
|
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):
|
for i in range(n - 1):
|
||||||
t = i / max(n - 1, 1)
|
t = i / max(n - 1, 1)
|
||||||
w = max(1, int(self._body_width_at(t) * 0.85))
|
w = max(1, int(self._body_width_at(t)))
|
||||||
p1 = (int(self.body_history[i][0]) + shadow_offset[0],
|
p1 = (int(self.body_history[i][0]) + 1,
|
||||||
int(self.body_history[i][1]) + shadow_offset[1])
|
int(self.body_history[i][1]) + 1)
|
||||||
p2 = (int(self.body_history[i+1][0]) + shadow_offset[0],
|
p2 = (int(self.body_history[i+1][0]) + 1,
|
||||||
int(self.body_history[i+1][1]) + shadow_offset[1])
|
int(self.body_history[i+1][1]) + 1)
|
||||||
cv2.line(frame, p1, p2, self.shadow_color, w)
|
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):
|
for i in range(n - 1):
|
||||||
t = i / max(n - 1, 1)
|
t = i / max(n - 1, 1)
|
||||||
w = max(1, int(self._body_width_at(t)))
|
w = max(1, int(self._body_width_at(t)))
|
||||||
p1 = (int(self.body_history[i][0]), int(self.body_history[i][1]))
|
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]))
|
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, self.body_color, w)
|
||||||
cv2.line(frame, p1, p2, 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):
|
for i in range(n - 1):
|
||||||
t = i / max(n - 1, 1)
|
t = i / max(n - 1, 1)
|
||||||
if 0.08 < t < 0.85:
|
if 0.10 < t < 0.90:
|
||||||
w = max(1, int(self._body_width_at(t) * 0.28))
|
w = max(1, int(self._body_width_at(t) * 0.35))
|
||||||
p1 = (int(self.body_history[i][0]), int(self.body_history[i][1]))
|
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]))
|
p2 = (int(self.body_history[i+1][0]), int(self.body_history[i+1][1]))
|
||||||
cv2.line(frame, p1, p2, self.body_dark, w)
|
cv2.line(frame, p1, p2, self.body_light, 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)
|
|
||||||
|
|
||||||
head = self.body_history[0]
|
head = self.body_history[0]
|
||||||
neck = self.body_history[min(3, n - 1)]
|
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),
|
right_ear = (int(head[0] + math.cos(head_angle - 1.8) * lw),
|
||||||
int(head[1] + math.sin(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)
|
pts = np.array([tip, left_ear, right_ear], dtype=np.int32)
|
||||||
cv2.fillPoly(frame, [pts], self.head_color)
|
# Contour sombre net puis remplissage gris uniforme
|
||||||
cv2.polylines(frame, [pts], True, self.body_dark, 1)
|
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]:
|
for side in [1.3, -1.3]:
|
||||||
ex = int(head[0] + math.cos(head_angle + side) * eye_d * 0.7)
|
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.7)
|
ey = int(head[1] + math.sin(head_angle + side) * eye_d * 0.65)
|
||||||
cv2.circle(frame, (ex, ey), max(1, self.width_px // 5), (30, 40, 50), -1)
|
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)
|
np.random.seed(args.seed)
|
||||||
|
|
||||||
# --- Constantes dérivées ---
|
# --- Constantes dérivées ---
|
||||||
#WIDTH, HEIGHT = 500, 500
|
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
|
|
||||||
TOTAL_FRAMES = args.fps * args.duration
|
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_RADIUS_PX = int(8 * MM_TO_PX)
|
||||||
ARENA_CENTER = (WIDTH // 2, HEIGHT // 2)
|
ARENA_CENTER = (WIDTH // 2, HEIGHT // 2)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user