calibration
This commit is contained in:
@@ -396,9 +396,11 @@ EXPORT_DESTINATIONS = ["local", "remote"]
|
||||
|
||||
|
||||
TRACKER_TUBE_AXIS = "vertical"
|
||||
TRACKER_MIN_AREA = 8 # surface min planaire px x px
|
||||
TRACKER_MIN_AREA = 20 # surface min planaire px x px
|
||||
TRACKER_MAX_AREA_RATIO = 0.1 # 5% de la frame = surface max acceptable
|
||||
TRACKER_MAX_PLANARIANS = 3
|
||||
TRACKER_MAX_PLANARIANS = 1
|
||||
TRACKER_MERGE_KERNEL_SIZE = 15 # augmenter si fragments résiduels
|
||||
TRACKER_MIN_CONTOUR_DIST_PX = 40 # augmenter si IDs multiples persistent
|
||||
|
||||
|
||||
CALIBRATION_AUTO_DURATION = 45.0
|
||||
|
||||
@@ -51,6 +51,16 @@ class VideoCaptureInterface(abc.ABC):
|
||||
# Cadence par défaut en images par seconde
|
||||
DEFAULT_FPS: float = 5.0
|
||||
|
||||
DEFAULT_TRACKER_CONFIG = dict(
|
||||
tube_axis = settings.TRACKER_TUBE_AXIS,
|
||||
min_area_px = settings.TRACKER_MIN_AREA,
|
||||
max_area_ratio = settings.TRACKER_MAX_AREA_RATIO,
|
||||
max_planarians = settings.TRACKER_MAX_PLANARIANS,
|
||||
merge_kernel_size = settings.TRACKER_MERGE_KERNEL_SIZE,
|
||||
min_contour_dist_px = settings.TRACKER_MIN_CONTOUR_DIST_PX,
|
||||
)
|
||||
|
||||
|
||||
def __init__(self, fps: float = DEFAULT_FPS, use_tracking: bool = False, display=None, parent=None, jpeg_quality=85):
|
||||
"""
|
||||
Initialise l'interface de capture.
|
||||
@@ -75,13 +85,9 @@ class VideoCaptureInterface(abc.ABC):
|
||||
self._tracker = None
|
||||
self._metrics = None
|
||||
self._paramss = None
|
||||
if use_tracking:
|
||||
self._tracker = PlanarianTracker(
|
||||
tube_axis = settings.TRACKER_TUBE_AXIS,
|
||||
min_area_px = settings.TRACKER_MIN_AREA,
|
||||
max_area_ratio = settings.TRACKER_MAX_AREA_RATIO,
|
||||
max_planarians = settings.TRACKER_MAX_PLANARIANS,
|
||||
)
|
||||
|
||||
# Tracker générique, pour simulation
|
||||
self.on_test_well_change(**self.DEFAULT_TRACKER_CONFIG)
|
||||
|
||||
self._aligner = TubeAligner(
|
||||
grbl_threshold_px = 20, # au-delà → correction GRBL
|
||||
@@ -91,14 +97,21 @@ class VideoCaptureInterface(abc.ABC):
|
||||
self.align_detection = None # résultat du test
|
||||
|
||||
|
||||
def on_test_well_change(self, **cfg):
|
||||
if self.use_tracking and cfg:
|
||||
logger.warning(f"tracking conf: {cfg}")
|
||||
self._tracker = PlanarianTracker(**cfg)
|
||||
|
||||
|
||||
def on_well_change(self, cfg):
|
||||
"""
|
||||
Appelé par le CNC lors du changement de puits.
|
||||
Réinitialise le fond appris et l'état inter-frame du tracker.
|
||||
Construit les métriques aussi
|
||||
"""
|
||||
if self.use_tracking and self._tracker:
|
||||
self._tracker.reset()
|
||||
if not self.use_tracking:
|
||||
return
|
||||
|
||||
self._params = ExperimentParams(cfg.to_params_dict())
|
||||
self._metrics = self._params.build_metrics()
|
||||
self._tracker = PlanarianTracker(
|
||||
@@ -106,6 +119,8 @@ class VideoCaptureInterface(abc.ABC):
|
||||
min_area_px = self._params.min_area_px,
|
||||
max_area_ratio = self._params.max_area_ratio,
|
||||
max_planarians = self._params.planarian_count,
|
||||
merge_kernel_size = settings.TRACKER_MERGE_KERNEL_SIZE,
|
||||
min_contour_dist_px = settings.TRACKER_MIN_CONTOUR_DIST_PX,
|
||||
)
|
||||
|
||||
|
||||
@@ -262,7 +277,7 @@ class VideoCaptureInterface(abc.ABC):
|
||||
frame = annotated if annotated is not None else frame
|
||||
##
|
||||
# mode racking
|
||||
if self._tracker is not None:
|
||||
if self.use_tracking:
|
||||
ts = datetime.now(timezone.utc).timestamp()
|
||||
frame, metrics = self._tracker.process(frame, ts)
|
||||
##
|
||||
|
||||
@@ -4,22 +4,13 @@ modules/planarian_tracker.py
|
||||
Détection et suivi multi-individus de planaires dans un tube.
|
||||
Supporte de 1 à MAX_PLANARIANS planaires par tube.
|
||||
|
||||
Etat inter-frame indépendant par individu : position, timestamp, compteur de perte (lost), flag active.
|
||||
Quand un individu n'est pas détecté pendant MAX_LOST_FRAMES (5) frames consécutives, il est marqué perdu et son slot se libère.
|
||||
|
||||
Algorithme hongrois (scipy.optimize.linear_sum_assignment) dans _hungarian_assign()
|
||||
— construit une matrice de coût distance euclidienne entre les slots actifs et les nouvelles détections, puis trouve l'association de coût minimal.
|
||||
Une association est rejetée si la distance dépasse MAX_ASSOC_DIST_PX (80px)
|
||||
— évite les sauts aberrants entre planaires proches.
|
||||
|
||||
|
||||
Stratégie :
|
||||
- Soustraction de fond MOG2 (léger sur Raspberry Pi 4)
|
||||
- Détection de tous les contours valides (surface >= min_area_px)
|
||||
- Association frame-à-frame par distance euclidienne minimale
|
||||
via algorithme hongrois (scipy.optimize.linear_sum_assignment)
|
||||
- Un état inter-frame indépendant par individu (PlanarianState)
|
||||
- Retourne une liste de résultats, un par individu suivi: champ planarian_id (index 0-based).
|
||||
- Retourne une liste de résultats, un par individu suivi
|
||||
|
||||
Created on 25 avr. 2026
|
||||
@author: denis
|
||||
@@ -175,6 +166,8 @@ class PlanarianTracker:
|
||||
min_area_px: int = 20,
|
||||
max_area_ratio: float = 0.10,
|
||||
max_planarians: int = 1,
|
||||
merge_kernel_size: int = 15,
|
||||
min_contour_dist_px:int = 40,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
@@ -183,6 +176,10 @@ class PlanarianTracker:
|
||||
max_area_ratio : surface maximale d'un contour en fraction de la frame (défaut 10%)
|
||||
filtre les faux positifs du fond non encore appris par MOG2
|
||||
max_planarians : nombre maximum de planaires à suivre simultanément (1-10)
|
||||
merge_kernel_size : taille du kernel elliptique de fusion des fragments (px).
|
||||
Régler ≈ largeur du planaire en pixels. Défaut : 15.
|
||||
min_contour_dist_px : distance min entre deux contours pour les considérer
|
||||
comme individus distincts. Défaut : 40px.
|
||||
"""
|
||||
self.tube_axis = tube_axis
|
||||
self.min_area_px = min_area_px
|
||||
@@ -192,6 +189,16 @@ class PlanarianTracker:
|
||||
# Un état inter-frame par slot individu
|
||||
self._states = [PlanarianState(i) for i in range(self.max_planarians)]
|
||||
|
||||
# Taille du kernel de fusion morphologique (pixels) —
|
||||
# doit être proche de la largeur du planaire en pixels.
|
||||
# Trop petit : fragments non fusionnés → IDs multiples.
|
||||
# Trop grand : deux planaires proches fusionnés en un seul.
|
||||
self.merge_kernel_size = merge_kernel_size
|
||||
|
||||
# Distance minimale en pixels entre deux contours distincts.
|
||||
# En-dessous : le plus petit est considéré comme fragment du plus grand.
|
||||
self.min_contour_dist_px = min_contour_dist_px
|
||||
|
||||
# Soustracteur de fond adaptatif MOG2
|
||||
self._bg_sub = self._make_bg_sub()
|
||||
|
||||
@@ -217,6 +224,8 @@ class PlanarianTracker:
|
||||
s.reset()
|
||||
self._bg_sub = self._make_bg_sub()
|
||||
self._warmup_count = 0
|
||||
# Les paramètres morphologiques (merge_kernel_size, min_contour_dist_px)
|
||||
# sont conservés — ils ne dépendent pas du puits
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Interface principale
|
||||
@@ -253,9 +262,19 @@ class PlanarianTracker:
|
||||
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
||||
fg_mask = self._bg_sub.apply(gray)
|
||||
|
||||
kernel = np.ones((3, 3), np.uint8)
|
||||
fg_mask = cv2.morphologyEx(fg_mask, cv2.MORPH_OPEN, kernel)
|
||||
fg_mask = cv2.morphologyEx(fg_mask, cv2.MORPH_CLOSE, kernel)
|
||||
# --- Morphologie : fusion des fragments du corps ---
|
||||
# Un planaire ondulant est souvent segmenté en plusieurs contours
|
||||
# (tête, milieu, queue). La dilatation fusionne les fragments proches
|
||||
# avant la détection des contours.
|
||||
# Kernel 3×3 : supprime le bruit fin (OPEN)
|
||||
# Kernel merge_kernel : fusionne les fragments du corps (CLOSE + DILATE)
|
||||
noise_kernel = np.ones((3, 3), np.uint8)
|
||||
merge_kernel = cv2.getStructuringElement(
|
||||
cv2.MORPH_ELLIPSE, (self.merge_kernel_size, self.merge_kernel_size)
|
||||
)
|
||||
fg_mask = cv2.morphologyEx(fg_mask, cv2.MORPH_OPEN, noise_kernel)
|
||||
fg_mask = cv2.morphologyEx(fg_mask, cv2.MORPH_CLOSE, merge_kernel)
|
||||
fg_mask = cv2.dilate(fg_mask, merge_kernel, iterations=1)
|
||||
|
||||
# Warmup MOG2 : les premières WARMUP_FRAMES frames retournent du bruit
|
||||
# (fond non encore appris) — on les alimente mais on ne détecte rien
|
||||
@@ -279,8 +298,33 @@ class PlanarianTracker:
|
||||
reverse=True,
|
||||
)
|
||||
|
||||
# --- Suppression des fragments résiduels trop proches ---
|
||||
# Si plusieurs contours valides ont leur centre à moins de
|
||||
# min_contour_dist_px les uns des autres, seul le plus grand est conservé.
|
||||
# Évite les cas où la fusion morphologique est incomplète.
|
||||
filtered = []
|
||||
for c in valid:
|
||||
M = cv2.moments(c)
|
||||
if M["m00"] == 0:
|
||||
continue
|
||||
cx_c = int(M["m10"] / M["m00"])
|
||||
cy_c = int(M["m01"] / M["m00"])
|
||||
too_close = False
|
||||
for kept in filtered:
|
||||
Mk = cv2.moments(kept)
|
||||
if Mk["m00"] == 0:
|
||||
continue
|
||||
cx_k = int(Mk["m10"] / Mk["m00"])
|
||||
cy_k = int(Mk["m01"] / Mk["m00"])
|
||||
dist = np.sqrt((cx_c - cx_k)**2 + (cy_c - cy_k)**2)
|
||||
if dist < self.min_contour_dist_px:
|
||||
too_close = True
|
||||
break
|
||||
if not too_close:
|
||||
filtered.append(c)
|
||||
|
||||
# Limiter au nombre maximum de planaires attendus
|
||||
valid = valid[:self.max_planarians]
|
||||
valid = filtered[:self.max_planarians]
|
||||
|
||||
# --- Calcul des centres de masse des contours détectés ---
|
||||
detections = [] # liste de (cx, cy, area, contour)
|
||||
@@ -489,4 +533,3 @@ class PlanarianTracker:
|
||||
"axial_pos": 0.0,
|
||||
"timestamp": ts,
|
||||
}
|
||||
|
||||
@@ -65,8 +65,9 @@ class ExperimentConfig(models.Model):
|
||||
)
|
||||
|
||||
max_area_ratio = models.FloatField(
|
||||
default=0.05,
|
||||
verbose_name=_("Filtre surface max acceptable"),
|
||||
default=0.10,
|
||||
verbose_name=_("Surface max contour (fraction de la frame)"),
|
||||
help_text=_("Ratio de la surface du puits, ex: 0.10 pour 10%"),
|
||||
)
|
||||
|
||||
planarian_count = models.IntegerField(
|
||||
@@ -131,6 +132,7 @@ class ExperimentConfig(models.Model):
|
||||
"thresh_mobile": self.thresh_mobile,
|
||||
"tube_axis": self.tube_axis,
|
||||
"min_area_px": self.min_area_px,
|
||||
"max_area_ratio": self.max_area_ratio,
|
||||
"planarian_count": self.planarian_count,
|
||||
"thigmotaxis_wall_dist_mm": self.thigmotaxis_wall_dist_mm,
|
||||
"photo_mode": self.photo_mode,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.contrib import admin
|
||||
from django.db.models import Q
|
||||
from . import models
|
||||
|
||||
|
||||
class WellAdmin(admin.ModelAdmin):
|
||||
model = models.Well
|
||||
list_display = ('name', 'author',)
|
||||
@@ -10,10 +10,57 @@ class WellAdmin(admin.ModelAdmin):
|
||||
class ConfigurationAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'author', 'capture_type', 'video_width_capture', 'video_height_capture', 'video_frame_rate', 'active',)
|
||||
|
||||
|
||||
fieldsets = (
|
||||
(_("Identification"), {
|
||||
"fields": ("name", "author", "active"),
|
||||
}),
|
||||
(_("Dashboard"), {
|
||||
"fields": ("sidebar_width", "default_grid_columns",),"classes": ("collapse",),
|
||||
}),
|
||||
(_("opencv"), {
|
||||
"fields": ("opencv_fourcc_format", "opencv_video_type"),"classes": ("collapse",),
|
||||
}),
|
||||
(_("Grbl"), {
|
||||
"fields": ("grbl_xmax", "grbl_ymax"),
|
||||
"classes": ("collapse",),
|
||||
}),
|
||||
(_("Camera"), {
|
||||
"fields": ("capture_type", "webcam_device_index", "image_quality", "video_jpeg_quality", "video_frame_rate", "video_width_capture", "video_height_capture"),
|
||||
"classes": ("collapse",),
|
||||
}),
|
||||
(_("Calibration"), {
|
||||
"fields": ("calibration_crop_radius", "calibration_default_multiwell", "calibration_default_feed", "calibration_default_step", "calibration_default_duration"),
|
||||
"classes": ("collapse",),
|
||||
}),
|
||||
(_("Tracking"), {
|
||||
"fields": ("tracking", "min_area_px", "max_area_ratio", "max_planarians", "merge_kernel_size", "min_contour_dist_px"),
|
||||
"classes": ("collapse",),
|
||||
}),
|
||||
|
||||
|
||||
)
|
||||
|
||||
class MultiWellAdmin(admin.ModelAdmin):
|
||||
list_filter = ('author', )
|
||||
list_display = ('label', 'position', 'author', 'order', 'xbase', 'ybase', 'duration', 'feed', 'default', 'well_position', 'active',)
|
||||
|
||||
fieldsets = (
|
||||
(_("Identification"), {
|
||||
"fields": ("label", "author", "position", "default", "active"),
|
||||
}),
|
||||
(_("Géométrie"), {
|
||||
"fields": ("cols", "rows", "diameter", "row_def", "row_order"),"classes": ("collapse",),
|
||||
}),
|
||||
(_("Déplacement"), {
|
||||
"fields": ("order", "duration", "xbase", "ybase", "dx", "dy", "feed"),"classes": ("collapse",),
|
||||
}),
|
||||
(_("Positions générées"), {
|
||||
"fields": ("well_position",),
|
||||
}),
|
||||
|
||||
)
|
||||
|
||||
class WellPositionAdmin(admin.ModelAdmin):
|
||||
list_filter = ('author', 'multiwell')
|
||||
list_display = ('multiwell__position', 'well__name', 'order', 'x', 'y', 'px_per_mm', 'author',)
|
||||
@@ -29,6 +76,7 @@ class ExperimentAdmin(admin.ModelAdmin):
|
||||
list_display = ('title', 'author', 'multiwell', 'created', 'started', 'finished')
|
||||
readonly_fields = ('created', 'started', 'finished', )
|
||||
|
||||
|
||||
class SessionExperimentInlineAdmin(admin.TabularInline):
|
||||
model = models.SessionExperiment
|
||||
fk_name = 'session'
|
||||
|
||||
@@ -28,6 +28,11 @@ class DefaultConfig:
|
||||
calibration_default_step: float = 1.0
|
||||
calibration_default_duration: float = 3.0
|
||||
tracking: bool = False
|
||||
min_area_px: int = 20
|
||||
max_area_ratio: float = 0.10
|
||||
max_planarians: int = 1
|
||||
merge_kernel_size: int = 15
|
||||
min_contour_dist_px: int = 40
|
||||
|
||||
|
||||
class ScannerConstants:
|
||||
|
||||
@@ -66,6 +66,13 @@ 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)
|
||||
# tracking
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
|
||||
#
|
||||
active = models.BooleanField(_("Actif"), default=False)
|
||||
|
||||
@@ -97,18 +104,18 @@ class Well(models.Model):
|
||||
|
||||
|
||||
class MultiWell(models.Model):
|
||||
# Identification
|
||||
label = models.CharField(_("Label"), help_text=_("Label du multi-puit"), max_length=100, null=True, blank=True)
|
||||
author = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="Auteur", null=True, blank=True)
|
||||
position = models.CharField(_("Position"), help_text=_('Position du multi-puits sur la table'), unique=True, max_length=8, choices=MULTIWELL_POSITION, null=True, blank=False)
|
||||
default = models.BooleanField(_("Par défaut"), help_text=_('Multi-puit par défaut'), default=False)
|
||||
|
||||
# Configuration
|
||||
cols = models.PositiveSmallIntegerField(_("Colonnes"), help_text=_('Nombre de colonnes'), blank=False, default=6)
|
||||
rows = models.PositiveSmallIntegerField(_("Lignes"), help_text=_('Nombre de lignes'), blank=False, default=4)
|
||||
diameter = models.FloatField(_("Diamètre"), help_text=_('Diamètre des tubes en mm'), blank=False, default=16.0)
|
||||
|
||||
row_def = models.CharField(_("Définition"), help_text=_('Définition des lignes'), max_length=16, null=True, blank=False, default="A,B,C,D")
|
||||
row_order = models.CharField(_("Ordre ligne"), help_text=_('Ordre ligne de puit. Lecture en serpentin dans le sens des +- X'), max_length=16, null=True, blank=False, default="D,C,B,A")
|
||||
|
||||
# Balayage
|
||||
order = models.PositiveSmallIntegerField(_("Ordre"), help_text=_('Ordre de lecture du multi-puit'), blank=False, default=0)
|
||||
duration = models.PositiveIntegerField(_("Durée"), help_text=_('Durée du film en secondes'), blank=False, default=120)
|
||||
xbase = models.FloatField(_("Origine X"), help_text=_('Base origine X en mm'), blank=False, default=50.0)
|
||||
|
||||
@@ -78,6 +78,7 @@ class MultiWellManager:
|
||||
def __init__(self, process):
|
||||
self.process = process
|
||||
self.cnc_controller = process.grbl
|
||||
|
||||
self.stop_playing = Event()
|
||||
self.well_iterator = None
|
||||
self.multiwel = None
|
||||
@@ -85,6 +86,15 @@ class MultiWellManager:
|
||||
self.set_multiwell()
|
||||
self.scan_thread = None
|
||||
self.test_thread = None
|
||||
self.tracker_config = dict(
|
||||
tube_axis = settings.TRACKER_TUBE_AXIS,
|
||||
min_area_px = self.process.conf.min_area_px,
|
||||
max_area_ratio = self.process.conf.max_area_ratio,
|
||||
max_planarians = self.process.conf.max_planarians,
|
||||
merge_kernel_size = self.process.conf.merge_kernel_size,
|
||||
min_contour_dist_px = self.process.conf.min_contour_dist_px,
|
||||
)
|
||||
|
||||
|
||||
def set_default_values(self, feed=None, step=None, duration=None):
|
||||
self._feed = feed or self.process.conf.calibration_default_feed
|
||||
|
||||
@@ -468,6 +468,12 @@ class ScannerProcess(Task):
|
||||
msg = self.manager.set_well_position()
|
||||
self._send(**msg)
|
||||
|
||||
elif topic in ['min_area_px', 'max_area_ratio', 'max_planarians', 'merge_kernel_size', 'min_contour_dist_px']:
|
||||
value = int(value) if topic in ['min_area_px', 'max_planarians', 'merge_kernel_size', 'min_contour_dist_px'] else float(value)
|
||||
self.manager.tracker_config[topic] = value
|
||||
self.cam.on_test_well_change(**self.manager.tracker_config)
|
||||
self._send(state=topic, msg=f"Value changed {value}")
|
||||
|
||||
self._send(
|
||||
xbase=self.manager.xbase,
|
||||
ybase=self.manager.ybase,
|
||||
|
||||
@@ -10,5 +10,3 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -42,6 +42,13 @@ class ScannerManager {
|
||||
this.crop = options.crop;
|
||||
this.crop_radius = options.crop_radius;
|
||||
this.calib_auto = options.calib_auto;
|
||||
|
||||
this.min_area_px = options.min_area_px;
|
||||
this.max_area_ratio = options.max_area_ratio;
|
||||
this.max_planarians = options.max_planarians;
|
||||
this.merge_kernel_size = options.merge_kernel_size;
|
||||
this.min_contour_dist_px = options.min_contour_dist_px;
|
||||
|
||||
}
|
||||
|
||||
init_controls() {
|
||||
@@ -71,6 +78,13 @@ class ScannerManager {
|
||||
this.calib_center.addEventListener('click', (e) => { this._send({ type: 'calibrate', topic: "center" }); });
|
||||
this.calib_auto.addEventListener('click', (e) => { this._send({ type: 'calibrate', topic: "auto" }); });
|
||||
this.halt.addEventListener('click', (e) => { this._send({ type: 'calibrate', topic: "halt" }); });
|
||||
|
||||
this.min_area_px.addEventListener('change', (e) => { this._send({ type: 'calibrate', topic: "min_area_px", value: e.target.value }); });
|
||||
this.max_area_ratio.addEventListener('change', (e) => { this._send({ type: 'calibrate', topic: "max_area_ratio", value: e.target.value }); });
|
||||
this.max_planarians.addEventListener('change', (e) => { this._send({ type: 'calibrate', topic: "max_planarians", value: e.target.value}); });
|
||||
this.merge_kernel_size.addEventListener('change', (e) => { this._send({ type: 'calibrate', topic: "merge_kernel_size", value: e.target.value}); });
|
||||
this.min_contour_dist_px.addEventListener('change', (e) => { this._send({ type: 'calibrate', topic: "min_contour_dist_px", value: e.target.value }); });
|
||||
|
||||
}
|
||||
|
||||
registerSocket(socket) {
|
||||
|
||||
@@ -133,12 +133,12 @@
|
||||
</div>
|
||||
<div class="w3-row w3-row-padding">
|
||||
<div class="w3-half">
|
||||
<button id="_calib_debug" class="w3-button w3-dark-xlight w3-round-large w3-padding-1" title="{% trans 'Ce mode permet les réglages graphiques' %}">
|
||||
<button id="_calib_debug" class="w3-button w3-dark-xlight w3-round-large w3-padding-1 w3-block" title="{% trans 'Ce mode permet les réglages graphiques' %}">
|
||||
{% trans 'Debug' %}<br><i class="fa-solid fa-triangle-exclamation w3-text-amber w3-xlarge"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="w3-half">
|
||||
<button id="_median" class="w3-button w3-dark-xlight w3-round-large w3-padding-1">
|
||||
<button id="_median" class="w3-button w3-dark-xlight w3-round-large w3-padding-1 w3-block">
|
||||
{% trans 'Axes' %}<br><i class="fa-solid fa-crosshairs w3-text-amber w3-xlarge"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -148,7 +148,32 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w3-row w3-row-padding ">
|
||||
{% if conf.tracking %}
|
||||
<div class="w3-row w3-row-padding w3-light-grey w3-margin-1 w3-round w3-round-large">
|
||||
<div class="w3-col">{% trans 'Configuration du tracking' %}</div>
|
||||
<div class="w3-half">
|
||||
<input id="_min_area_px" type="number" min="5" max="100" step="1" class="w3-round" value="{{ conf.min_area_px }}"
|
||||
title="{% trans 'surface minimale de contour pour être considéré valide (px carré)' %}"><br>{% trans 'Min contour' %}
|
||||
</div>
|
||||
<div class="w3-half">
|
||||
<input id="_max_area_ratio" type="number" min="0.05" max="1.0" step="0.05" class="w3-round" value="{{ conf.max_area_ratio|stringformat:"s" }}"
|
||||
title="{% trans 'surface maximale de contour en fraction de la frame (défaut 10%)' %}"><br>{% trans 'Max contour' %}
|
||||
</div>
|
||||
<div class="w3-half">
|
||||
<input id="_merge_kernel_size" type="number" min="5" max="320" step="1" class="w3-round" value="{{ conf.merge_kernel_size }}"
|
||||
title="{% trans 'Taille du kernel elliptique de fusion des fragments (px)' %}"><br>{% trans 'Taille kernel' %}
|
||||
</div>
|
||||
<div class="w3-half">
|
||||
<input id="_min_contour_dist_px" type="number" min="10" max="320" step="1" class="w3-round" value="{{ conf.min_contour_dist_px }}"
|
||||
title="{% trans 'Distance min entre deux contours pour les considérer comme individus distincts. Défaut: 40px' %}"><br>{% trans 'Distance min' %}
|
||||
</div>
|
||||
<div class="w3-half">
|
||||
<input id="_max_planarians" type="number" min="1" max="10" step="1" class="w3-round" value="{{ conf.max_planarians }}"
|
||||
title="{% trans 'Nombre maximum de planaires à suivre simultanément (1-10)' %}"><br>{% trans 'Planaires' %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="w3-row w3-row-padding">
|
||||
<div class="w3-half w3-margin-top">
|
||||
<span>{% trans 'Rayon' %} px</span><br>
|
||||
<input id="_crop_radius" type="number" min="100" max="1200" step="1" class="w3-round w3-input" title="{% trans 'Rayon du recadrement' %}"
|
||||
@@ -227,7 +252,12 @@
|
||||
well_btn : sId("_well_btn"),
|
||||
median : sId("_median"),
|
||||
crop : sId("_crop"),
|
||||
crop_radius : sId("_crop_radius")
|
||||
crop_radius : sId("_crop_radius"),
|
||||
min_area_px : sId("_min_area_px"),
|
||||
max_area_ratio : sId("_max_area_ratio"),
|
||||
max_planarians : sId("_max_planarians"),
|
||||
merge_kernel_size : sId("_merge_kernel_size"),
|
||||
min_contour_dist_px : sId("_min_contour_dist_px")
|
||||
};
|
||||
</script>
|
||||
<script src="/static/scanner/js/calibration.js"></script>
|
||||
|
||||
Reference in New Issue
Block a user