planarian
This commit is contained in:
@@ -24,8 +24,10 @@ from typing import Optional, Callable, TYPE_CHECKING
|
|||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from modules.planarian_tracker import PlanarianTracker
|
from modules.planarian_tracker import PlanarianTracker
|
||||||
|
from modules.planarian_metrics import ExperimentParams
|
||||||
from modules.tube_aligner import TubeAligner
|
from modules.tube_aligner import TubeAligner
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .circular_crop import CircularCrop # Evite l'import circulaire au runtime
|
from .circular_crop import CircularCrop # Evite l'import circulaire au runtime
|
||||||
|
|
||||||
@@ -58,6 +60,7 @@ class VideoCaptureInterface(abc.ABC):
|
|||||||
self._fps: float = fps
|
self._fps: float = fps
|
||||||
self.display = display
|
self.display = display
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
|
self.use_tracking = use_tracking
|
||||||
self.jpeg_quality = jpeg_quality
|
self.jpeg_quality = jpeg_quality
|
||||||
self._interval: float = 1.0 / fps # Intervalle en secondes entre chaque capture
|
self._interval: float = 1.0 / fps # Intervalle en secondes entre chaque capture
|
||||||
self._running: bool = False # Indique si la capture est en cours
|
self._running: bool = False # Indique si la capture est en cours
|
||||||
@@ -70,6 +73,8 @@ class VideoCaptureInterface(abc.ABC):
|
|||||||
self._error_occured = False
|
self._error_occured = False
|
||||||
|
|
||||||
self._tracker = None
|
self._tracker = None
|
||||||
|
self._metrics = None
|
||||||
|
self._paramss = None
|
||||||
if use_tracking:
|
if use_tracking:
|
||||||
self._tracker = PlanarianTracker(
|
self._tracker = PlanarianTracker(
|
||||||
tube_axis = settings.TRACKER_TUBE_AXIS,
|
tube_axis = settings.TRACKER_TUBE_AXIS,
|
||||||
@@ -78,7 +83,6 @@ class VideoCaptureInterface(abc.ABC):
|
|||||||
max_planarians = settings.TRACKER_MAX_PLANARIANS,
|
max_planarians = settings.TRACKER_MAX_PLANARIANS,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
self._aligner = TubeAligner(
|
self._aligner = TubeAligner(
|
||||||
grbl_threshold_px = 20, # au-delà → correction GRBL
|
grbl_threshold_px = 20, # au-delà → correction GRBL
|
||||||
dead_zone_px = 5, # en-dessous → rien à faire
|
dead_zone_px = 5, # en-dessous → rien à faire
|
||||||
@@ -87,13 +91,22 @@ class VideoCaptureInterface(abc.ABC):
|
|||||||
self.align_detection = None # résultat du test
|
self.align_detection = None # résultat du test
|
||||||
|
|
||||||
|
|
||||||
def on_well_change(self):
|
def on_well_change(self, cfg):
|
||||||
"""
|
"""
|
||||||
Appelé par le CNC lors du changement de puits.
|
Appelé par le 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
|
||||||
"""
|
"""
|
||||||
if self._tracker:
|
if self.use_tracking and self._tracker:
|
||||||
self._tracker.reset()
|
self._tracker.reset()
|
||||||
|
self._params = ExperimentParams(cfg.to_params_dict())
|
||||||
|
self._metrics = self._params.build_metrics()
|
||||||
|
self._tracker = PlanarianTracker(
|
||||||
|
tube_axis = self._params.tube_axis,
|
||||||
|
min_area_px = self._params.min_area_px,
|
||||||
|
max_area_ratio = self._params.max_area_ratio,
|
||||||
|
max_planarians = self._params.planarian_count,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class ExperimentConfigAdmin(admin.ModelAdmin):
|
|||||||
"classes": ("collapse",),
|
"classes": ("collapse",),
|
||||||
}),
|
}),
|
||||||
(_("Tracker"), {
|
(_("Tracker"), {
|
||||||
"fields": ("tube_axis", "min_area_px", "planarian_count"),
|
"fields": ("tube_axis", "min_area_px", "max_area_ratio", "planarian_count"),
|
||||||
"classes": ("collapse",),
|
"classes": ("collapse",),
|
||||||
}),
|
}),
|
||||||
(_("Thigmotactisme"), {
|
(_("Thigmotactisme"), {
|
||||||
|
|||||||
@@ -63,6 +63,12 @@ class ExperimentConfig(models.Model):
|
|||||||
default=20,
|
default=20,
|
||||||
verbose_name=_("Surface min détection (px²)"),
|
verbose_name=_("Surface min détection (px²)"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
max_area_ratio = models.FloatField(
|
||||||
|
default=0.05,
|
||||||
|
verbose_name=_("Filtre surface max acceptable"),
|
||||||
|
)
|
||||||
|
|
||||||
planarian_count = models.IntegerField(
|
planarian_count = models.IntegerField(
|
||||||
default=1,
|
default=1,
|
||||||
verbose_name=_("Nombre de planaires"),
|
verbose_name=_("Nombre de planaires"),
|
||||||
|
|||||||
@@ -173,6 +173,14 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="w3-col m4 s12 w3-margin-bottom">
|
||||||
|
<label class="w3-text-grey"><b>{{ form.max_area_ratio.label }}</b></label>
|
||||||
|
{{ form.max_area_ratio }}
|
||||||
|
{% if form.max_area_ratio.errors %}
|
||||||
|
<span class="w3-text-red w3-small">{{ form.max_area_ratio.errors|join:", " }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="w3-col m4 s12 w3-margin-bottom">
|
<div class="w3-col m4 s12 w3-margin-bottom">
|
||||||
<label class="w3-text-grey"><b>{{ form.planarian_count.label }}</b></label>
|
<label class="w3-text-grey"><b>{{ form.planarian_count.label }}</b></label>
|
||||||
{{ form.planarian_count }}
|
{{ form.planarian_count }}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from threading import Thread, Event
|
|||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.html import mark_safe
|
from django.utils.html import mark_safe
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from planarian.models import ExperimentConfig
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
|
|
||||||
@@ -120,23 +121,32 @@ class MultiWellManager:
|
|||||||
return mark_safe("\n".join(multiwells))
|
return mark_safe("\n".join(multiwells))
|
||||||
|
|
||||||
|
|
||||||
def _grid_scanning_capture(self, uuid, duration):
|
#def _grid_scanning_capture(self, uuid, duration):
|
||||||
|
def _grid_scanning_capture(self, experiment, well_position):
|
||||||
|
well = well_position.well
|
||||||
|
multiwell = experiment.multiwell
|
||||||
|
|
||||||
|
# Paramètres d'une expérience PlanarianScanner
|
||||||
|
cfg = ExperimentConfig.objects.get(experiment_id=experiment.id, well_id=well.id)
|
||||||
|
# reset PlanarianTracker => on_well_change
|
||||||
|
self.process.cam.on_well_change(cfg)
|
||||||
|
|
||||||
|
uuid = f'{self.process.data.session}-{multiwell.position}-{well.name}'
|
||||||
|
## start recording
|
||||||
self.process.data.uuid = uuid
|
self.process.data.uuid = uuid
|
||||||
self.process.data.record = True
|
self.process.data.record = True
|
||||||
|
|
||||||
# reset PlanarianTracker => on_well_change
|
|
||||||
self.process.cam.on_well_change()
|
|
||||||
|
|
||||||
start = time.monotonic()
|
start = time.monotonic()
|
||||||
while not self.stop_playing.is_set():
|
while not self.stop_playing.is_set():
|
||||||
if time.monotonic() - start > duration:
|
if time.monotonic() - start > multiwell.duration:
|
||||||
break
|
break
|
||||||
self.cnc_controller.wait_for(1.0)
|
self.cnc_controller.wait_for(1.0)
|
||||||
|
|
||||||
logger.info(f"Arrêter l'enregistrement {uuid}")
|
|
||||||
self.process.data.record = False
|
self.process.data.record = False
|
||||||
self.process.data.uuid = None
|
self.process.data.uuid = None
|
||||||
|
|
||||||
|
msg = f"{uuid}: capture done"
|
||||||
|
logger.info(msg)
|
||||||
|
self.process._send(scan_state=msg)
|
||||||
|
|
||||||
|
|
||||||
def _grid_scanning(self, experiment, xnext=0, ynext=0):
|
def _grid_scanning(self, experiment, xnext=0, ynext=0):
|
||||||
multiwell = experiment.multiwell
|
multiwell = experiment.multiwell
|
||||||
@@ -150,15 +160,14 @@ class MultiWellManager:
|
|||||||
break
|
break
|
||||||
self.cnc_controller.move_to(wl.x, wl.y, feed=wl.multiwell.feed)
|
self.cnc_controller.move_to(wl.x, wl.y, feed=wl.multiwell.feed)
|
||||||
|
|
||||||
uuid = f'{self.process.data.session}-{multiwell.position}-{wl.well.name}'
|
#uuid = f'{self.process.data.session}-{multiwell.position}-{wl.well.name}'
|
||||||
self._grid_scanning_capture(uuid, multiwell.duration)
|
#self._grid_scanning_capture(uuid, multiwell.duration)
|
||||||
|
self._grid_scanning_capture(experiment, wl)
|
||||||
|
|
||||||
## change file
|
## change file
|
||||||
if self.process.conf.capture_type == 'file':
|
if self.process.conf.capture_type == 'file':
|
||||||
self.process.cam._error_occured = True
|
self.process.cam._error_occured = True
|
||||||
|
|
||||||
self.process._send(scan_state=f"{uuid}: capture")
|
|
||||||
|
|
||||||
logger.info(f"Scan terminé — retour à l'origine (X={xnext:.1f} Y={ynext:.1f})")
|
logger.info(f"Scan terminé — retour à l'origine (X={xnext:.1f} Y={ynext:.1f})")
|
||||||
self.cnc_controller.move_to(xnext, ynext, feed=multiwell.feed*2)
|
self.cnc_controller.move_to(xnext, ynext, feed=multiwell.feed*2)
|
||||||
|
|
||||||
|
|||||||
@@ -163,8 +163,6 @@ class ScannerProcess(Task):
|
|||||||
|
|
||||||
def start(self, *args, **kwargs):
|
def start(self, *args, **kwargs):
|
||||||
try:
|
try:
|
||||||
logger.warning("ScannerProcess==================", self.name)
|
|
||||||
|
|
||||||
self.conf = ScannerConstants().get()
|
self.conf = ScannerConstants().get()
|
||||||
self.use_tracking = self.conf.tracking
|
self.use_tracking = self.conf.tracking
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user