planarian
This commit is contained in:
@@ -24,8 +24,10 @@ from typing import Optional, Callable, TYPE_CHECKING
|
||||
|
||||
from django.conf import settings
|
||||
from modules.planarian_tracker import PlanarianTracker
|
||||
from modules.planarian_metrics import ExperimentParams
|
||||
from modules.tube_aligner import TubeAligner
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .circular_crop import CircularCrop # Evite l'import circulaire au runtime
|
||||
|
||||
@@ -58,6 +60,7 @@ class VideoCaptureInterface(abc.ABC):
|
||||
self._fps: float = fps
|
||||
self.display = display
|
||||
self.parent = parent
|
||||
self.use_tracking = use_tracking
|
||||
self.jpeg_quality = jpeg_quality
|
||||
self._interval: float = 1.0 / fps # Intervalle en secondes entre chaque capture
|
||||
self._running: bool = False # Indique si la capture est en cours
|
||||
@@ -70,6 +73,8 @@ class VideoCaptureInterface(abc.ABC):
|
||||
self._error_occured = False
|
||||
|
||||
self._tracker = None
|
||||
self._metrics = None
|
||||
self._paramss = None
|
||||
if use_tracking:
|
||||
self._tracker = PlanarianTracker(
|
||||
tube_axis = settings.TRACKER_TUBE_AXIS,
|
||||
@@ -77,8 +82,7 @@ class VideoCaptureInterface(abc.ABC):
|
||||
max_area_ratio = settings.TRACKER_MAX_AREA_RATIO,
|
||||
max_planarians = settings.TRACKER_MAX_PLANARIANS,
|
||||
)
|
||||
|
||||
|
||||
|
||||
self._aligner = TubeAligner(
|
||||
grbl_threshold_px = 20, # au-delà → correction GRBL
|
||||
dead_zone_px = 5, # en-dessous → rien à faire
|
||||
@@ -87,13 +91,22 @@ class VideoCaptureInterface(abc.ABC):
|
||||
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.
|
||||
Réinitialise le fond appris et l'état inter-frame du tracker.
|
||||
Construit les métriques aussi
|
||||
"""
|
||||
if self._tracker:
|
||||
self._tracker.reset()
|
||||
if self.use_tracking and self._tracker:
|
||||
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",),
|
||||
}),
|
||||
(_("Tracker"), {
|
||||
"fields": ("tube_axis", "min_area_px", "planarian_count"),
|
||||
"fields": ("tube_axis", "min_area_px", "max_area_ratio", "planarian_count"),
|
||||
"classes": ("collapse",),
|
||||
}),
|
||||
(_("Thigmotactisme"), {
|
||||
|
||||
@@ -63,6 +63,12 @@ class ExperimentConfig(models.Model):
|
||||
default=20,
|
||||
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(
|
||||
default=1,
|
||||
verbose_name=_("Nombre de planaires"),
|
||||
|
||||
@@ -172,6 +172,14 @@
|
||||
<span class="w3-text-red w3-small">{{ form.min_area_px.errors|join:", " }}</span>
|
||||
{% endif %}
|
||||
</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">
|
||||
<label class="w3-text-grey"><b>{{ form.planarian_count.label }}</b></label>
|
||||
|
||||
@@ -14,6 +14,7 @@ from threading import Thread, Event
|
||||
from django.utils import timezone
|
||||
from django.utils.html import mark_safe
|
||||
from django.conf import settings
|
||||
from planarian.models import ExperimentConfig
|
||||
from . import models
|
||||
|
||||
|
||||
@@ -120,22 +121,31 @@ class MultiWellManager:
|
||||
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.record = True
|
||||
|
||||
# reset PlanarianTracker => on_well_change
|
||||
self.process.cam.on_well_change()
|
||||
|
||||
start = time.monotonic()
|
||||
while not self.stop_playing.is_set():
|
||||
if time.monotonic() - start > duration:
|
||||
if time.monotonic() - start > multiwell.duration:
|
||||
break
|
||||
self.cnc_controller.wait_for(1.0)
|
||||
|
||||
logger.info(f"Arrêter l'enregistrement {uuid}")
|
||||
self.process.data.record = False
|
||||
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):
|
||||
@@ -150,15 +160,14 @@ class MultiWellManager:
|
||||
break
|
||||
self.cnc_controller.move_to(wl.x, wl.y, feed=wl.multiwell.feed)
|
||||
|
||||
uuid = f'{self.process.data.session}-{multiwell.position}-{wl.well.name}'
|
||||
self._grid_scanning_capture(uuid, multiwell.duration)
|
||||
#uuid = f'{self.process.data.session}-{multiwell.position}-{wl.well.name}'
|
||||
#self._grid_scanning_capture(uuid, multiwell.duration)
|
||||
self._grid_scanning_capture(experiment, wl)
|
||||
|
||||
## change file
|
||||
if self.process.conf.capture_type == 'file':
|
||||
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})")
|
||||
self.cnc_controller.move_to(xnext, ynext, feed=multiwell.feed*2)
|
||||
|
||||
|
||||
@@ -162,9 +162,7 @@ class ScannerProcess(Task):
|
||||
return CircularCrop(radius=radius, strategy=CropStrategy.CROP_JPEG, jpeg_quality=self.image_quality)
|
||||
|
||||
def start(self, *args, **kwargs):
|
||||
try:
|
||||
logger.warning("ScannerProcess==================", self.name)
|
||||
|
||||
try:
|
||||
self.conf = ScannerConstants().get()
|
||||
self.use_tracking = self.conf.tracking
|
||||
|
||||
|
||||
Reference in New Issue
Block a user