metrics
This commit is contained in:
@@ -11,12 +11,37 @@ default_diameter=16.0 # mm
|
|||||||
|
|
||||||
declare -A arguments=(
|
declare -A arguments=(
|
||||||
# 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
|
# 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"
|
["A1"]="4 0.40 0.30 15 600 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 arguments_all=(
|
||||||
|
# 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 15 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 15 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 15 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"
|
||||||
|
["A4"]="3 0.60 0.35 15 60 192 #EBEBEB #FAFAFA #8C8C8C #C8C8C8 #A5A5A5 #373737 #D2D2D2 #828282 0.2 1.5 0.70 none 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.5 0.95"
|
||||||
|
["A5"]="1 0.30 0.32 15 60 250 #EBEBEB #FAFAFA #8C8C8C #C8C8C8 #A5A5A5 #373737 #D2D2D2 #828282 0.2 1.5 0.70 none 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"
|
||||||
|
["A6"]="2 0.55 0.38 15 60 32 #EBEBEB #FAFAFA #8C8C8C #C8C8C8 #A5A5A5 #373737 #D2D2D2 #828282 0.2 1.5 0.70 none 0.50 0.50 0.50 0.10 0.30 1.0 0.70 0.70 2.0 0.0 3.0 0.0 6.0 0.0 0.95"
|
||||||
|
["B1"]="3 0.40 0.30 15 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"
|
||||||
|
["B2"]="1 0.42 0.32 15 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"
|
||||||
|
["B3"]="1 0.50 0.40 15 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"
|
||||||
|
["B4"]="3 0.60 0.35 15 60 192 #EBEBEB #FAFAFA #8C8C8C #C8C8C8 #A5A5A5 #373737 #D2D2D2 #828282 0.2 1.5 0.70 none 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.5 0.95"
|
||||||
|
["B5"]="1 0.30 0.32 15 60 250 #EBEBEB #FAFAFA #8C8C8C #C8C8C8 #A5A5A5 #373737 #D2D2D2 #828282 0.2 1.5 0.70 none 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"
|
||||||
|
["B6"]="2 0.55 0.38 15 60 32 #EBEBEB #FAFAFA #8C8C8C #C8C8C8 #A5A5A5 #373737 #D2D2D2 #828282 0.2 1.5 0.70 none 0.50 0.50 0.50 0.10 0.30 1.0 0.70 0.70 2.0 0.0 3.0 0.0 6.0 0.0 0.95"
|
||||||
|
["C1"]="3 0.40 0.30 15 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"
|
||||||
|
["C2"]="1 0.42 0.32 15 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"
|
||||||
|
["C3"]="1 0.50 0.40 15 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"
|
||||||
|
["C4"]="3 0.60 0.35 15 60 192 #EBEBEB #FAFAFA #8C8C8C #C8C8C8 #A5A5A5 #373737 #D2D2D2 #828282 0.2 1.5 0.70 none 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.5 0.95"
|
||||||
|
["C5"]="1 0.30 0.32 15 60 250 #EBEBEB #FAFAFA #8C8C8C #C8C8C8 #A5A5A5 #373737 #D2D2D2 #828282 0.2 1.5 0.70 none 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"
|
||||||
|
["C6"]="2 0.55 0.38 15 60 32 #EBEBEB #FAFAFA #8C8C8C #C8C8C8 #A5A5A5 #373737 #D2D2D2 #828282 0.2 1.5 0.70 none 0.50 0.50 0.50 0.10 0.30 1.0 0.70 0.70 2.0 0.0 3.0 0.0 6.0 0.0 0.95"
|
||||||
|
["D1"]="3 0.40 0.30 15 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"
|
||||||
|
["D2"]="1 0.42 0.32 15 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"
|
||||||
|
["D3"]="1 0.50 0.40 15 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"
|
||||||
|
["D4"]="3 0.60 0.35 15 60 192 #EBEBEB #FAFAFA #8C8C8C #C8C8C8 #A5A5A5 #373737 #D2D2D2 #828282 0.2 1.5 0.70 none 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.5 0.95"
|
||||||
|
["D5"]="1 0.30 0.32 15 60 250 #EBEBEB #FAFAFA #8C8C8C #C8C8C8 #A5A5A5 #373737 #D2D2D2 #828282 0.2 1.5 0.70 none 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"
|
||||||
|
["D6"]="2 0.55 0.38 15 60 32 #EBEBEB #FAFAFA #8C8C8C #C8C8C8 #A5A5A5 #373737 #D2D2D2 #828282 0.2 1.5 0.70 none 0.50 0.50 0.50 0.10 0.30 1.0 0.70 0.70 2.0 0.0 3.0 0.0 6.0 0.0 0.95"
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
for key in "${!arguments[@]}"; do
|
for key in "${!arguments[@]}"; do
|
||||||
args="${arguments[$key]}"
|
args="${arguments[$key]}"
|
||||||
|
|||||||
@@ -99,13 +99,13 @@ class VideoCaptureInterface(abc.ABC):
|
|||||||
|
|
||||||
def on_test_well_change(self, **cfg):
|
def on_test_well_change(self, **cfg):
|
||||||
if self.use_tracking and cfg:
|
if self.use_tracking and cfg:
|
||||||
logger.warning(f"tracking conf: {cfg}")
|
|
||||||
self._tracker = PlanarianTracker(**cfg)
|
self._tracker = PlanarianTracker(**cfg)
|
||||||
|
logger.warning(f"New tracker conf: {cfg}")
|
||||||
|
|
||||||
|
|
||||||
def on_well_change(self, cfg):
|
def on_well_change(self, cfg):
|
||||||
"""
|
"""
|
||||||
Appelé par le CNC lors du changement de puits.
|
Appelé par la CNC lors du changement de puits.
|
||||||
Réinitialise le fond appris et l'état inter-frame du tracker.
|
Réinitialise le fond appris et l'état inter-frame du tracker.
|
||||||
Construit les métriques aussi
|
Construit les métriques aussi
|
||||||
"""
|
"""
|
||||||
@@ -119,8 +119,8 @@ class VideoCaptureInterface(abc.ABC):
|
|||||||
min_area_px = self._params.min_area_px,
|
min_area_px = self._params.min_area_px,
|
||||||
max_area_ratio = self._params.max_area_ratio,
|
max_area_ratio = self._params.max_area_ratio,
|
||||||
max_planarians = self._params.planarian_count,
|
max_planarians = self._params.planarian_count,
|
||||||
merge_kernel_size = settings.TRACKER_MERGE_KERNEL_SIZE,
|
merge_kernel_size = self._params.merge_kernel_size,
|
||||||
min_contour_dist_px = settings.TRACKER_MIN_CONTOUR_DIST_PX,
|
min_contour_dist_px = self._params.min_contour_dist_px,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ Métriques résumé (summary) :
|
|||||||
Created on 25 avr. 2026
|
Created on 25 avr. 2026
|
||||||
@author: denis
|
@author: denis
|
||||||
"""
|
"""
|
||||||
|
import asyncio
|
||||||
import csv
|
import csv
|
||||||
import io
|
import io
|
||||||
import json
|
import json
|
||||||
@@ -659,13 +659,30 @@ class ReductStoreClient:
|
|||||||
url: str = "http://localhost:8383",
|
url: str = "http://localhost:8383",
|
||||||
token: str = "",
|
token: str = "",
|
||||||
bucket: str = "planarian_metrics",
|
bucket: str = "planarian_metrics",
|
||||||
|
quota_type=None,
|
||||||
|
quota_size=1000_000_000
|
||||||
):
|
):
|
||||||
self.url = url
|
self.url = url
|
||||||
self.token = token
|
self.token = token
|
||||||
self.bucket_name = bucket
|
self.bucket_name = bucket
|
||||||
|
self.quota_type = quota_type
|
||||||
|
self.quota_size = quota_size
|
||||||
self._client = None
|
self._client = None
|
||||||
self._bucket = None
|
self._bucket = asyncio.run(self.create_bucket())
|
||||||
|
|
||||||
|
|
||||||
|
async def create_bucket(self):
|
||||||
|
from reduct import Client, BucketSettings
|
||||||
|
self._client = Client(self.url, api_token=self.token)
|
||||||
|
|
||||||
|
settings = BucketSettings(
|
||||||
|
quota_type=self.quota_type,
|
||||||
|
quota_size=self.quota_size,
|
||||||
|
exist_ok=True,
|
||||||
|
)
|
||||||
|
return await self._client.create_bucket(self.bucket_name, settings, exist_ok=True)
|
||||||
|
|
||||||
|
'''
|
||||||
async def connect(self):
|
async def connect(self):
|
||||||
"""Initialise la connexion et crée le bucket si nécessaire."""
|
"""Initialise la connexion et crée le bucket si nécessaire."""
|
||||||
from reduct import Client, BucketSettings, QuotaType
|
from reduct import Client, BucketSettings, QuotaType
|
||||||
@@ -677,13 +694,16 @@ class ReductStoreClient:
|
|||||||
)
|
)
|
||||||
logger.info(f"ReductStore connecté : {self.url} / {self.bucket_name}")
|
logger.info(f"ReductStore connecté : {self.url} / {self.bucket_name}")
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
async def store_metric(
|
async def store_metric(
|
||||||
self,
|
self,
|
||||||
record: dict,
|
record: dict,
|
||||||
experiment: str,
|
experiment: str,
|
||||||
well: str,
|
well: str,
|
||||||
|
entry_name: str = "metrics",
|
||||||
planarian: int = 0,
|
planarian: int = 0,
|
||||||
record_type: str = "frame",
|
record_type: str = "metrics",
|
||||||
ts_us: Optional[int] = None,
|
ts_us: Optional[int] = None,
|
||||||
):
|
):
|
||||||
"""Stocke un enregistrement dans ReductStore."""
|
"""Stocke un enregistrement dans ReductStore."""
|
||||||
@@ -691,7 +711,7 @@ class ReductStoreClient:
|
|||||||
await self.connect()
|
await self.connect()
|
||||||
ts_us = ts_us or int(time.time() * 1_000_000)
|
ts_us = ts_us or int(time.time() * 1_000_000)
|
||||||
await self._bucket.write(
|
await self._bucket.write(
|
||||||
entry_name = "metrics",
|
entry_name = entry_name,
|
||||||
data = json.dumps(record).encode("utf-8"),
|
data = json.dumps(record).encode("utf-8"),
|
||||||
timestamp = ts_us,
|
timestamp = ts_us,
|
||||||
labels = {
|
labels = {
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ class ReductStoreBase(ABC):
|
|||||||
self.bucket: Bucket = asyncio.run(self.create_bucket())
|
self.bucket: Bucket = asyncio.run(self.create_bucket())
|
||||||
logger.info(f"==== {url} token:{api_token}")
|
logger.info(f"==== {url} token:{api_token}")
|
||||||
|
|
||||||
|
|
||||||
async def create_bucket(self):
|
async def create_bucket(self):
|
||||||
|
|
||||||
settings = BucketSettings(
|
settings = BucketSettings(
|
||||||
quota_type=self.quota_type,
|
quota_type=self.quota_type,
|
||||||
quota_size=self.quota_size,
|
quota_size=self.quota_size,
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class ExperimentConfigAdmin(admin.ModelAdmin):
|
|||||||
"classes": ("collapse",),
|
"classes": ("collapse",),
|
||||||
}),
|
}),
|
||||||
(_("Tracker"), {
|
(_("Tracker"), {
|
||||||
"fields": ("tube_axis", "min_area_px", "max_area_ratio", "planarian_count"),
|
"fields": ("tube_axis", "min_area_px", "max_area_ratio", "planarian_count", "merge_kernel_size", "min_contour_dist_px"),
|
||||||
"classes": ("collapse",),
|
"classes": ("collapse",),
|
||||||
}),
|
}),
|
||||||
(_("Thigmotactisme"), {
|
(_("Thigmotactisme"), {
|
||||||
|
|||||||
@@ -75,6 +75,18 @@ class ExperimentConfig(models.Model):
|
|||||||
verbose_name=_("Nombre de planaires"),
|
verbose_name=_("Nombre de planaires"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
merge_kernel_size = models.PositiveIntegerField(
|
||||||
|
_("Taille du kernel"),
|
||||||
|
help_text=_("taille du kernel elliptique de fusion des fragments (px)"),
|
||||||
|
default=15
|
||||||
|
)
|
||||||
|
|
||||||
|
min_contour_dist_px = models.PositiveIntegerField(
|
||||||
|
_("Distance <contour>"),
|
||||||
|
help_text=_("Distance min entre deux contours pour les considérer comme individus distincts. Défaut : 40px."),
|
||||||
|
default=40
|
||||||
|
)
|
||||||
|
|
||||||
# --- Thigmotactisme ---
|
# --- Thigmotactisme ---
|
||||||
thigmotaxis_wall_dist_mm = models.FloatField(
|
thigmotaxis_wall_dist_mm = models.FloatField(
|
||||||
default=1.0,
|
default=1.0,
|
||||||
@@ -133,6 +145,8 @@ class ExperimentConfig(models.Model):
|
|||||||
"tube_axis": self.tube_axis,
|
"tube_axis": self.tube_axis,
|
||||||
"min_area_px": self.min_area_px,
|
"min_area_px": self.min_area_px,
|
||||||
"max_area_ratio": self.max_area_ratio,
|
"max_area_ratio": self.max_area_ratio,
|
||||||
|
"merge_kernel_size": self.merge_kernel_size,
|
||||||
|
"min_contour_dist_px": self.min_contour_dist_px,
|
||||||
"planarian_count": self.planarian_count,
|
"planarian_count": self.planarian_count,
|
||||||
"thigmotaxis_wall_dist_mm": self.thigmotaxis_wall_dist_mm,
|
"thigmotaxis_wall_dist_mm": self.thigmotaxis_wall_dist_mm,
|
||||||
"photo_mode": self.photo_mode,
|
"photo_mode": self.photo_mode,
|
||||||
|
|||||||
@@ -189,6 +189,21 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="w3-col m4 s12 w3-margin-bottom">
|
||||||
|
<label class="w3-text-grey"><b>{{ form.min_contour_dist_px.label }}</b></label>
|
||||||
|
{{ form.min_contour_dist_px }}
|
||||||
|
{% if form.min_contour_dist_px.errors %}
|
||||||
|
<span class="w3-text-red w3-small">{{ form.min_contour_dist_px.errors|join:", " }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w3-col m4 s12 w3-margin-bottom">
|
||||||
|
<label class="w3-text-grey"><b>{{ form.merge_kernel_size.label }}</b></label>
|
||||||
|
{{ form.merge_kernel_size }}
|
||||||
|
{% if form.merge_kernel_size.errors %}
|
||||||
|
<span class="w3-text-red w3-small">{{ form.merge_kernel_size.errors|join:", " }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class ConfigurationAdmin(admin.ModelAdmin):
|
|||||||
"fields": ("calibration_crop_radius", "calibration_default_multiwell", "calibration_default_feed", "calibration_default_step", "calibration_default_duration"),
|
"fields": ("calibration_crop_radius", "calibration_default_multiwell", "calibration_default_feed", "calibration_default_step", "calibration_default_duration"),
|
||||||
"classes": ("collapse",),
|
"classes": ("collapse",),
|
||||||
}),
|
}),
|
||||||
(_("Tracking"), {
|
(_("Tracking: valeurs par défaut"), {
|
||||||
"fields": ("tracking", "min_area_px", "max_area_ratio", "max_planarians", "merge_kernel_size", "min_contour_dist_px"),
|
"fields": ("tracking", "min_area_px", "max_area_ratio", "max_planarians", "merge_kernel_size", "min_contour_dist_px"),
|
||||||
"classes": ("collapse",),
|
"classes": ("collapse",),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -66,13 +66,11 @@ class Configuration(models.Model):
|
|||||||
calibration_default_duration = models.FloatField(_("Duruée calibration"), help_text=_("Durée de pose entre chaque puits en s"), default=3.0)
|
calibration_default_duration = models.FloatField(_("Duruée calibration"), help_text=_("Durée de pose entre chaque puits en s"), default=3.0)
|
||||||
# tracking
|
# tracking
|
||||||
tracking = models.BooleanField(_("Suivi"), help_text=_("Suivi et analyse des planaires"), default=False)
|
tracking = models.BooleanField(_("Suivi"), help_text=_("Suivi et analyse des planaires"), default=False)
|
||||||
|
|
||||||
min_area_px = models.PositiveIntegerField(_("Surface minimale"), help_text=_("surface minimale d'un contour pour être considéré valide (px²)"), default=20)
|
min_area_px = models.PositiveIntegerField(_("Surface minimale"), help_text=_("surface minimale d'un contour pour être considéré valide (px²)"), default=20)
|
||||||
max_area_ratio = models.FloatField(_("surface maximale "), help_text=_("surface maximale d'un contour en fraction de la frame (défaut 10%)"), default=0.10)
|
max_area_ratio = models.FloatField(_("surface maximale "), help_text=_("surface maximale d'un contour en fraction de la frame (défaut 10%)"), default=0.10)
|
||||||
max_planarians = models.PositiveIntegerField(_("Max planaire"), help_text=_("nombre maximum de planaires à suivre simultanément (1-10)"), default=1)
|
max_planarians = models.PositiveIntegerField(_("Max planaire"), help_text=_("nombre maximum de planaires à suivre simultanément (1-10)"), default=1)
|
||||||
merge_kernel_size = models.PositiveIntegerField(_("Taille du kernel"), help_text=_("taille du kernel elliptique de fusion des fragments (px)"), default=15)
|
merge_kernel_size = models.PositiveIntegerField(_("Taille du kernel"), help_text=_("taille du kernel elliptique de fusion des fragments (px)"), default=15)
|
||||||
min_contour_dist_px = models.PositiveIntegerField(_("Distance <contour>"), help_text=_("Distance min entre deux contours pour les considérer comme individus distincts. Défaut : 40px."), default=40)
|
min_contour_dist_px = models.PositiveIntegerField(_("Distance <contour>"), help_text=_("Distance min entre deux contours pour les considérer comme individus distincts. Défaut : 40px."), default=40)
|
||||||
|
|
||||||
#
|
#
|
||||||
active = models.BooleanField(_("Actif"), default=False)
|
active = models.BooleanField(_("Actif"), default=False)
|
||||||
|
|
||||||
|
|||||||
@@ -135,11 +135,11 @@ class MultiWellManager:
|
|||||||
def _grid_scanning_capture(self, experiment, well_position, simulate=False):
|
def _grid_scanning_capture(self, experiment, well_position, simulate=False):
|
||||||
well = well_position.well
|
well = well_position.well
|
||||||
multiwell = experiment.multiwell
|
multiwell = experiment.multiwell
|
||||||
|
if self.process.use_tracking:
|
||||||
# Paramètres d'une expérience PlanarianScanner
|
# Paramètres d'une expérience PlanarianScanner
|
||||||
cfg = ExperimentConfig.objects.get(experiment_id=experiment.id, well_id=well.id)
|
cfg = ExperimentConfig.objects.get(experiment_id=experiment.id, well_id=well.id)
|
||||||
# reset PlanarianTracker => on_well_change
|
# reset PlanarianTracker => on_well_change
|
||||||
self.process.cam.on_well_change(cfg)
|
self.process.cam.on_well_change(cfg)
|
||||||
|
|
||||||
uuid = f'{self.process.data.session}-{multiwell.position}-{well.name}'
|
uuid = f'{self.process.data.session}-{multiwell.position}-{well.name}'
|
||||||
## start recording
|
## start recording
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ from celery.exceptions import Ignore
|
|||||||
from celery.utils.log import get_task_logger
|
from celery.utils.log import get_task_logger
|
||||||
from redis import Redis
|
from redis import Redis
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from modules import reductstore, grbl, utils
|
from modules import reductstore, grbl, utils, planarian_metrics
|
||||||
|
|
||||||
## camera devices
|
## camera devices
|
||||||
from modules.circular_crop import CircularCrop, CropStrategy
|
from modules.circular_crop import CircularCrop, CropStrategy
|
||||||
@@ -42,6 +42,7 @@ class ProcessData:
|
|||||||
logger = get_task_logger(__name__)
|
logger = get_task_logger(__name__)
|
||||||
redisDB = Redis(host=settings.REDIS_HOST, port=settings.REDIS_PORT, db=0, decode_responses=True)
|
redisDB = Redis(host=settings.REDIS_HOST, port=settings.REDIS_PORT, db=0, decode_responses=True)
|
||||||
cameraDB = reductstore.ReductStore(name='camera')
|
cameraDB = reductstore.ReductStore(name='camera')
|
||||||
|
planarianDB = planarian_metrics.ReductStoreClient(url=settings.REDUCTSTORE_URL, token=settings.REDUCTSTORE_TOKEN)
|
||||||
|
|
||||||
|
|
||||||
class CameraRecordManager():
|
class CameraRecordManager():
|
||||||
@@ -183,14 +184,16 @@ class ScannerProcess(Task):
|
|||||||
capture_type = self.conf.capture_type
|
capture_type = self.conf.capture_type
|
||||||
if capture_type == 'file':
|
if capture_type == 'file':
|
||||||
video_lists = []
|
video_lists = []
|
||||||
|
|
||||||
wells = models.Well.objects.all()
|
wells = models.Well.objects.all()
|
||||||
for wl in wells:
|
for wl in wells:
|
||||||
video_lists.append(str( settings.MEDIA_ROOT / 'simulation' / f'{wl.name}.mp4') )
|
if exists := (settings.MEDIA_ROOT / 'simulation' / f'{wl.name}.mp4').exists():
|
||||||
|
video_lists.append(str( settings.MEDIA_ROOT / 'simulation' / f'{wl.name}.mp4') )
|
||||||
|
|
||||||
|
|
||||||
from modules.videofile_capture import VideoFileCapture
|
from modules.videofile_capture import VideoFileCapture
|
||||||
self.cam = VideoFileCapture(
|
self.cam = VideoFileCapture(
|
||||||
video_file=settings.MEDIA_ROOT / 'simulation' / 'D6.mp4',
|
video_file=settings.MEDIA_ROOT / 'simulation' / 'A1.mp4',
|
||||||
fps=self.video_fps,
|
fps=self.video_fps,
|
||||||
width=self.video_width,
|
width=self.video_width,
|
||||||
height=self.video_height,
|
height=self.video_height,
|
||||||
@@ -272,13 +275,38 @@ class ScannerProcess(Task):
|
|||||||
if self.grbl:
|
if self.grbl:
|
||||||
self._send(**msg)
|
self._send(**msg)
|
||||||
|
|
||||||
|
|
||||||
|
def _store_metrics(self, uuid, metrics, ts):
|
||||||
|
for r in metrics:
|
||||||
|
pid = r["planarian_id"]
|
||||||
|
record = self.cam._metrics[pid].update(r, well_radius_mm=self.cam._params.well_radius_mm)
|
||||||
|
logger.warning(f"{record}")
|
||||||
|
|
||||||
|
async_to_sync(planarianDB.store_metrics)(
|
||||||
|
record,
|
||||||
|
self.cam._params.experiment,
|
||||||
|
self.cam._params.well_name,
|
||||||
|
entry_name=uuid,
|
||||||
|
planarian=pid,
|
||||||
|
record_type='metrics',
|
||||||
|
ts_us=ts,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _store_frame(self, uuid, frame, ts, frame_count):
|
||||||
|
labels = {
|
||||||
|
"fps": self.video_fps,
|
||||||
|
"record_type": 'frame',
|
||||||
|
"frame_count": frame_count,
|
||||||
|
}
|
||||||
|
self.recordDB.write(uuid, frame, ts=ts, labels=labels)
|
||||||
|
|
||||||
def _on_frame(self, jpeg_bytes: bytes, ts: datetime, metrics: dict, frame_count: int = 0) -> None:
|
def _on_frame(self, jpeg_bytes: bytes, ts: datetime, metrics: dict, frame_count: int = 0) -> None:
|
||||||
|
|
||||||
if self.data.record:
|
if self.data.record:
|
||||||
self.record_queue.put((self.data.uuid, ts, jpeg_bytes, metrics, frame_count))
|
self.record_queue.put((self.data.uuid, ts, jpeg_bytes, metrics, frame_count))
|
||||||
if self.data.play:
|
if self.data.play:
|
||||||
try:
|
try:
|
||||||
jpeg=base64.b64encode(jpeg_bytes).decode()
|
jpeg=base64.b64encode(jpeg_bytes).decode()
|
||||||
#logger.warning(f"{jpeg[:100]}")
|
|
||||||
self._send(ts=ts.timestamp(), jpeg=jpeg, frame_count=frame_count)
|
self._send(ts=ts.timestamp(), jpeg=jpeg, frame_count=frame_count)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"----_on_frame: {e}")
|
logger.error(f"----_on_frame: {e}")
|
||||||
@@ -289,21 +317,8 @@ class ScannerProcess(Task):
|
|||||||
while not self.stop_event.is_set():
|
while not self.stop_event.is_set():
|
||||||
try:
|
try:
|
||||||
(uuid, ts, frame, metrics, frame_count) = self.record_queue.get()
|
(uuid, ts, frame, metrics, frame_count) = self.record_queue.get()
|
||||||
|
self._store_metrics(uuid, metrics, ts)
|
||||||
|
self._frame_store(uuid, frame, ts, frame_count)
|
||||||
labels = dict(fps=self.video_fps, session=self.data.session, detected="1" if metrics.get("detected") else "0")
|
|
||||||
|
|
||||||
if metrics.get("detected"):
|
|
||||||
labels.update({
|
|
||||||
"cx" : str(metrics["cx"]),
|
|
||||||
"cy" : str(metrics["cy"]),
|
|
||||||
"area_px" : str(metrics["area_px"]),
|
|
||||||
"speed_px_s" : str(metrics["speed_px_s"]),
|
|
||||||
"axial_pos" : str(metrics["axial_pos"]),
|
|
||||||
"axial_speed" : str(metrics["axial_speed"]),
|
|
||||||
})
|
|
||||||
|
|
||||||
self.recordDB.write(uuid, frame, labels, ts=ts)
|
|
||||||
self.record_queue.task_done()
|
self.record_queue.task_done()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f'recorder: {e}')
|
logger.error(f'recorder: {e}')
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ class ScannerManager {
|
|||||||
this.merge_kernel_size = options.merge_kernel_size;
|
this.merge_kernel_size = options.merge_kernel_size;
|
||||||
this.min_contour_dist_px = options.min_contour_dist_px;
|
this.min_contour_dist_px = options.min_contour_dist_px;
|
||||||
this.track = options.track;
|
this.track = options.track;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
init_controls() {
|
init_controls() {
|
||||||
|
|||||||
@@ -150,7 +150,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% if conf.tracking %}
|
{% if conf.tracking %}
|
||||||
<div class="w3-row w3-row-padding w3-light-grey w3-margin-1 w3-round w3-round-large">
|
<div class="w3-row w3-row-padding w3-light-grey w3-margin-1 w3-round w3-round-large">
|
||||||
<div class="w3-col"><input id="_track" type="checkbox" value="0" onclick="if (this.checked){this.value='1';}else{ this.value='0';}">
|
<div class="w3-col"><input id="_track_it" type="checkbox" value="0" onclick="if (this.checked){this.value='1';}else{ this.value='0';}">
|
||||||
{% trans 'Configuration du tracking' %}
|
{% trans 'Configuration du tracking' %}
|
||||||
</div>
|
</div>
|
||||||
<div class="w3-half">
|
<div class="w3-half">
|
||||||
@@ -260,7 +260,7 @@
|
|||||||
max_planarians : sId("_max_planarians"),
|
max_planarians : sId("_max_planarians"),
|
||||||
merge_kernel_size : sId("_merge_kernel_size"),
|
merge_kernel_size : sId("_merge_kernel_size"),
|
||||||
min_contour_dist_px : sId("_min_contour_dist_px"),
|
min_contour_dist_px : sId("_min_contour_dist_px"),
|
||||||
track : sId("_track")
|
track : sId("_track_it")
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<script src="/static/scanner/js/calibration.js"></script>
|
<script src="/static/scanner/js/calibration.js"></script>
|
||||||
|
|||||||
Reference in New Issue
Block a user