simulation

This commit is contained in:
2026-05-06 23:35:31 +02:00
parent 22ec82c895
commit 1bc7e5eb9e
13 changed files with 232 additions and 105 deletions
+79 -56
View File
@@ -7,6 +7,8 @@ Created on 20 avr. 2026
@author: denis
'''
import logging
import time
from threading import Thread, Event
@@ -18,10 +20,8 @@ from planarian.models import ExperimentConfig
from . import models
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class WellIterator:
"""Itérateur personnalisé pour naviguer dans les Wells"""
@@ -76,9 +76,12 @@ class WellIterator:
class MultiWellManager:
def __init__(self, process):
self.process = process
self.cnc_controller = process.grbl
logger.info(f"MultiWellManager initialized with CNC controller: {self.cnc_controller}")
self.stop_playing = Event()
self.well_iterator = None
self.multiwel = None
@@ -86,6 +89,19 @@ 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_tracker_config(self):
self.tracker_config = dict(
tube_axis = settings.TRACKER_TUBE_AXIS,
min_area_px = self.process.conf.min_area_px,
@@ -93,8 +109,8 @@ class MultiWellManager:
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,
)
)
logger.info(f"Tracker config: {self.tracker_config}")
def set_default_values(self, feed=None, step=None, duration=None):
self._feed = feed or self.process.conf.calibration_default_feed
@@ -136,7 +152,8 @@ class MultiWellManager:
well = well_position.well
multiwell = experiment.multiwell
if self.process.use_tracking:
# Paramètres d'une expérience PlanarianScanner
logger.info("# 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)
@@ -151,7 +168,7 @@ class MultiWellManager:
while not self.stop_playing.is_set():
if time.monotonic() - start > multiwell.duration:
break
self.cnc_controller.wait_for(1.0)
self.cnc_controller.wait_for(0.1)
self.process.data.record = False
self.process.data.uuid = None
@@ -175,10 +192,12 @@ class MultiWellManager:
## change file
if self.process.conf.capture_type == 'file':
vf = settings.MEDIA_ROOT / 'simulation' / f'{wl.name}.mp4'
vf = settings.MEDIA_ROOT / 'simulation' / f'{wl.well.name}.mp4'
if vf.exists():
self.process.cam._video_file = str(vf)
self.process.cam._error_occured = True
self.process.cam._error_occured = True
logger.info(f"Simulating capture with file {vf}")
self._grid_scanning_capture(experiment, wl, simulate=simulate)
@@ -188,58 +207,60 @@ class MultiWellManager:
def _start_scanning(self, session, experiments, simulate=False):
self.process.cam._aligner.debug = False
xynext = []
for obs in experiments:
xynext.append((obs.multiwell.xbase, obs.multiwell.ybase))
xynext.append((0, 0))
pos = 1
self.process.data.session = session.id
started = timezone.now()
for obs in experiments:
try:
self.process.cam._aligner.debug = False
xynext = []
for obs in experiments:
xynext.append((obs.multiwell.xbase, obs.multiwell.ybase))
xynext.append((0, 0))
pos = 1
self.process.data.session = session.id
started = timezone.now()
for obs in experiments:
if self.stop_playing.is_set():
break
obs.started = timezone.now()
obs.save()
xnext, ynext = xynext[pos]
pos +=1
self._grid_scanning(obs, xnext=xnext, ynext=ynext, simulate=simulate)
obs.finished = timezone.now()
obs.save()
session.finished = timezone.now()
if self.stop_playing.is_set():
break
obs.started = timezone.now()
obs.save()
xnext, ynext = xynext[pos]
pos +=1
self._grid_scanning(obs, xnext=xnext, ynext=ynext, simulate=simulate)
obs.finished = timezone.now()
obs.save()
session.finished = timezone.now()
if self.stop_playing.is_set():
msg = f"Session {session.name} abandonnée à {session.finished} après {session.finished - started} secondes."
else:
if not simulate:
session.active = False
if session.scanning_task:
session.scanning_task.enabled = False
session.save()
msg = f"Session {session.name} terminée à {session.finished} après {session.finished - started} secondes."
logger.info(msg)
self.process._send(scan_state=msg)
self.scan_thread = None
msg = f"Session {session.name} abandonnée à {session.finished} après {session.finished - started} secondes."
else:
if not simulate:
session.active = False
if session.scanning_task:
session.scanning_task.enabled = False
session.save()
msg = f"Session {session.name} terminée à {session.finished} après {session.finished - started} secondes."
logger.info(msg)
self.process._send(scan_state=msg)
finally:
self.scan_thread = None
def halt_scanning(self):
self.process.data.record = False
self.stop_playing.set()
self.well_iterator.reset()
self.process.cam._aligner.debug = False
self.process.cam._aligner.debug = False
self.scan_thread = None
self.test_thread = None
def scanning(self, sid, simulate=False):
try:
if self.scan_thread:
return
session = models.Session.objects.get(pk=sid)
experiments = models.SessionExperiment.experiment_by_session(sid)
self.scan_thread = Thread(target=self._start_scanning, args=(session, experiments, simulate, ), daemon=True).start()
except Exception as e:
print("MultiWellManager::scan error", e)
def scan_process(self, sid, simulate=False):
if self.scan_thread:
return
session = models.Session.objects.get(pk=sid)
experiments = models.SessionExperiment.experiment_by_session(sid)
self.scan_thread = Thread(target=self._start_scanning, args=(session, experiments, simulate, ), daemon=True)
self.scan_thread.start()
def previous_well(self):
@@ -316,22 +337,24 @@ class MultiWellManager:
self.process._send(state='center', msg=msg)
self.cnc_controller.wait_for(0.1)
logger.info("Fin du centrage")
logger.info("Fin du test")
except Exception as e:
print(e)
logger.error("Error during scanning test", e)
finally:
self.test_thread = None
self.well_iterator.reset()
self.process.cam._aligner.debug = False
logger.info(f"Scan terminé — retour à l'origine (X=0, Y=0) en {int(time.monotonic()-start_test)} s")
self.cnc_controller.move_to(0, 0, feed=self.multiwell.feed*2)
self.test_thread = None
def scan_test(self, auto=False):
if self.test_thread:
return
self.test_thread = Thread(target=self._scanning_test, args=(auto, ), daemon=True).start()
self.test_thread = Thread(target=self._scanning_test, args=(auto, ), daemon=True)
self.test_thread.start()
@property