This commit is contained in:
2026-05-03 09:29:08 +02:00
parent 309e2d4f45
commit 0f22451516
9 changed files with 68 additions and 53 deletions
+2 -2
View File
@@ -396,8 +396,8 @@ EXPORT_DESTINATIONS = ["local", "remote"]
TRACKER_TUBE_AXIS = "vertical" TRACKER_TUBE_AXIS = "vertical"
TRACKER_MIN_AREA = 20 # surface min planaire TRACKER_MIN_AREA = 8 # surface min planaire px x px
TRACKER_MAX_AREA_RATIO = 0.05 # 5% de la frame = surface max acceptable TRACKER_MAX_AREA_RATIO = 0.1 # 5% de la frame = surface max acceptable
TRACKER_MAX_PLANARIANS = 3 TRACKER_MAX_PLANARIANS = 3
+12 -10
View File
@@ -122,7 +122,7 @@ class MultiWellManager:
#def _grid_scanning_capture(self, uuid, duration): #def _grid_scanning_capture(self, uuid, duration):
def _grid_scanning_capture(self, experiment, well_position): def _grid_scanning_capture(self, experiment, well_position, simulate=False):
well = well_position.well well = well_position.well
multiwell = experiment.multiwell multiwell = experiment.multiwell
@@ -134,12 +134,15 @@ class MultiWellManager:
uuid = f'{self.process.data.session}-{multiwell.position}-{well.name}' uuid = f'{self.process.data.session}-{multiwell.position}-{well.name}'
## start recording ## start recording
self.process.data.uuid = uuid self.process.data.uuid = uuid
if not simulate:
self.process.data.record = True self.process.data.record = True
start = time.monotonic() start = time.monotonic()
while not self.stop_playing.is_set(): while not self.stop_playing.is_set():
if time.monotonic() - start > multiwell.duration: if time.monotonic() - start > multiwell.duration:
break break
self.cnc_controller.wait_for(1.0) self.cnc_controller.wait_for(1.0)
self.process.data.record = False self.process.data.record = False
self.process.data.uuid = None self.process.data.uuid = None
@@ -148,7 +151,7 @@ class MultiWellManager:
self.process._send(scan_state=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, simulate=False):
multiwell = experiment.multiwell multiwell = experiment.multiwell
wells = models.WellPosition.objects.filter(multiwell_id=multiwell.id).order_by('order').all() wells = models.WellPosition.objects.filter(multiwell_id=multiwell.id).order_by('order').all()
cam = self.process.cam cam = self.process.cam
@@ -159,10 +162,7 @@ class MultiWellManager:
if self.stop_playing.is_set(): if self.stop_playing.is_set():
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)
self._grid_scanning_capture(experiment, wl, simulate=simulate)
#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 ## change file
if self.process.conf.capture_type == 'file': if self.process.conf.capture_type == 'file':
@@ -172,7 +172,7 @@ class MultiWellManager:
self.cnc_controller.move_to(xnext, ynext, feed=multiwell.feed*2) self.cnc_controller.move_to(xnext, ynext, feed=multiwell.feed*2)
def _start_scanning(self, session, experiments): def _start_scanning(self, session, experiments, simulate=False):
self.process.cam._aligner.debug = False self.process.cam._aligner.debug = False
xynext = [] xynext = []
for obs in experiments: for obs in experiments:
@@ -189,7 +189,7 @@ class MultiWellManager:
obs.save() obs.save()
xnext, ynext = xynext[pos] xnext, ynext = xynext[pos]
pos +=1 pos +=1
self._grid_scanning(obs, xnext=xnext, ynext=ynext) self._grid_scanning(obs, xnext=xnext, ynext=ynext, simulate=simulate)
obs.finished = timezone.now() obs.finished = timezone.now()
obs.save() obs.save()
@@ -197,10 +197,12 @@ class MultiWellManager:
if self.stop_playing.is_set(): if self.stop_playing.is_set():
msg = f"Session {session.name} abandonnée à {session.finished} après {session.finished - started} secondes." msg = f"Session {session.name} abandonnée à {session.finished} après {session.finished - started} secondes."
else: else:
if not simulate:
session.active = False session.active = False
if session.scanning_task: if session.scanning_task:
session.scanning_task.enabled = False session.scanning_task.enabled = False
session.save() session.save()
msg = f"Session {session.name} terminée à {session.finished} après {session.finished - started} secondes." msg = f"Session {session.name} terminée à {session.finished} après {session.finished - started} secondes."
logger.info(msg) logger.info(msg)
self.process._send(scan_state=msg) self.process._send(scan_state=msg)
@@ -214,13 +216,13 @@ class MultiWellManager:
self.process.cam._aligner.debug = False self.process.cam._aligner.debug = False
def scanning(self, sid): def scanning(self, sid, simulate=False):
try: try:
if self.scan_thread: if self.scan_thread:
return return
session = models.Session.objects.get(pk=sid) session = models.Session.objects.get(pk=sid)
experiments = models.SessionExperiment.experiment_by_session(sid) experiments = models.SessionExperiment.experiment_by_session(sid)
self.scan_thread = Thread(target=self._start_scanning, args=(session, experiments, ), daemon=True).start() self.scan_thread = Thread(target=self._start_scanning, args=(session, experiments, simulate, ), daemon=True).start()
except Exception as e: except Exception as e:
print("MultiWellManager::scan error", e) print("MultiWellManager::scan error", e)
+6 -4
View File
@@ -278,7 +278,7 @@ class ScannerProcess(Task):
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]}") #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,6 +289,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()
labels = dict(fps=self.video_fps, session=self.data.session, detected="1" if metrics.get("detected") else "0") labels = dict(fps=self.video_fps, session=self.data.session, detected="1" if metrics.get("detected") else "0")
if metrics.get("detected"): if metrics.get("detected"):
@@ -345,15 +347,15 @@ class ScannerProcess(Task):
self.cam._active_median = False self.cam._active_median = False
self.grbl.go_origin(feed=self.manager.feed) self.grbl.go_origin(feed=self.manager.feed)
elif topic == 'scan': elif topic == 'scan' or topic == 'simulate':
logger.info(f"==== Scan {cmd}") logger.info(f"==== Scan {cmd}")
sid = cmd.get("session", '0') sid = cmd.get("session", '0')
if sid == "0": if sid == "0":
self._send(state='error', msg=str(_('La session est nulle!...'))) self._send(state='error', msg=str(_('La session est nulle!...')))
else: else:
self.cam._active_median = False self.cam._active_median = False
self.manager.scanning(sid) self.manager.scanning(sid, simulate=(topic=='simulate'))
self._send(state=topic, msg=str(_('Balayage démarré...')))
elif cmd["type"]=="calibrate": elif cmd["type"]=="calibrate":
topic = cmd.get("topic") topic = cmd.get("topic")
@@ -104,12 +104,14 @@
</a> </a>
{% endif %} {% endif %}
<a href="{% url 'scanner:main' %}" class="w3-bar-item w3-btn w3-hover-opacity"> <a href="{% url 'scanner:scanning' %}" class="w3-bar-item w3-btn w3-hover-opacity">
<i class="fa-solid fa-film w3-text-green w3-xlarge""></i> {% trans "Balayage multi-puits" %} <i class="fa-solid fa-film w3-text-green w3-xlarge""></i> {% trans "Balayage multi-puits" %}
</a> </a>
{% if conf.tracking %}
<a href="{% url 'planarian:experiment-list' %}" class="w3-bar-item w3-btn w3-hover-opacity"> <a href="{% url 'planarian:experiment-list' %}" class="w3-bar-item w3-btn w3-hover-opacity">
<i class="fa-solid fa-vials w3-text-lime w3-xlarge"></i> {% trans "Préparation des expériences" %} <i class="fa-solid fa-vials w3-text-lime w3-xlarge"></i> {% trans "Préparation des expériences" %}
</a> </a>
{% endif %}
<a href="{% url 'scanner:images' %}" class="w3-bar-item w3-btn w3-hover-opacity"> <a href="{% url 'scanner:images' %}" class="w3-bar-item w3-btn w3-hover-opacity">
<i class="fa-regular fa-images w3-text-cyan w3-xlarge" w3-xlarge""></i> {% trans "Gestionnaire d'images" %} <i class="fa-regular fa-images w3-text-cyan w3-xlarge" w3-xlarge""></i> {% trans "Gestionnaire d'images" %}
</a> </a>
@@ -3,7 +3,7 @@
{% block styles %} {% block styles %}
{{ block.super }} {{ block.super }}
<link href="/static/scanner/css/main.css" rel="stylesheet"> <link href="/static/scanner/css/scanning.css" rel="stylesheet">
{% endblock %} {% endblock %}
{% block columns %}{% endblock %} {% block columns %}{% endblock %}
@@ -25,20 +25,27 @@
<div class="w3-col w3-padding"><span id="_uuid"></span></div> <div class="w3-col w3-padding"><span id="_uuid"></span></div>
</div> </div>
</div> </div>
<div class="w3-row w3-row-padding">
<div class="w3-col"> <div class="w3-half">
<button id="_median" class="w3-button w3-dark-xlight w3-round-large w3-padding-small w3-block"> <button id="_median" class="w3-button w3-dark-xlight w3-round-large w3-block">
{% trans 'Axes' %}&nbsp;<br><i class="fa-solid fa-crosshairs w3-text-amber w3-xlarge"></i> {% trans 'Axes' %}&nbsp;<br><i class="fa-solid fa-crosshairs w3-text-amber w3-xlarge"></i>
</button> </button>
</div> </div>
<div class="w3-col"> <div class="w3-half">
<button id="_crop" class="w3-button w3-dark-xlight w3-round-large w3-padding-top w3-margin-top w3-block"> <button id="_crop" class="w3-button w3-dark-xlight w3-round-large w3-block">
{% trans 'Recadrer' %}<br><i class="fa-solid fa-crop w3-text-amber w3-xlarge"></i> {% trans 'Recadrer' %}<br><i class="fa-solid fa-crop w3-text-amber w3-xlarge"></i>
</button> </button>
</div> </div>
<div class="w3-col"> </div>
<div class="w3-row w3-row-padding">
<div class="w3-twothird">
<button id="_scan" class="w3-button w3-light-blue w3-round-large w3-margin-top w3-block">
{% trans 'Lancer' %}<br>{% trans 'le balayage' %}<br><i class="fa-solid fa-circle-check w3-text-blue w3-xlarge"></i>
</button>
</div>
<div class="w3-third">
<button id="_scan" class="w3-button w3-dark-xlight w3-round-large w3-margin-top w3-block"> <button id="_scan" class="w3-button w3-dark-xlight w3-round-large w3-margin-top w3-block">
{% trans 'Lancer le balayage' %}<br><i class="fa-solid fa-circle-check w3-text-orange w3-xlarge"></i> {% trans 'Simuler' %}<br>{% trans 'le balayage' %}<br><i class="fa-solid fa-triangle-exclamation w3-text-orange w3-xlarge"></i>
</button> </button>
</div> </div>
<div class="w3-col"> <div class="w3-col">
@@ -46,6 +53,7 @@
{% trans 'ARRET' %}<br><i class="fa-solid fa-hand w3-xlarge"></i> {% trans 'ARRET' %}<br><i class="fa-solid fa-hand w3-xlarge"></i>
</button> </button>
</div> </div>
</div>
<div class="w3-col"> <div class="w3-col">
<div class="w3-margin-top w3-padding w3-small"> <div class="w3-margin-top w3-padding w3-small">
<span id="_ts"></span> <span id="_ts"></span>
@@ -85,10 +93,11 @@
axial_pos : sId("_axial_pos"), axial_pos : sId("_axial_pos"),
area_px : sId("_area_px"), area_px : sId("_area_px"),
frame_count : sId("_count"), frame_count : sId("_count"),
scan_state: sId("_scan_state") scan_state : sId("_scan_state"),
simulate : sId("_simulate")
}; };
</script> </script>
<script src="/static/scanner/js/main.js"></script> <script src="/static/scanner/js/scanning.js"></script>
<script> <script>
const manager = new ScannerManager(container, options); const manager = new ScannerManager(container, options);
const protocol = location.protocol === "https:" ? "wss" : "ws"; const protocol = location.protocol === "https:" ? "wss" : "ws";
+1 -1
View File
@@ -14,7 +14,7 @@ urlpatterns = [
path('logs/worker', views.supervisor_worker, name='logs_worker'), path('logs/worker', views.supervisor_worker, name='logs_worker'),
path('logs/scheduler', views.supervisor_scheduler, name='logs_scheduler'), path('logs/scheduler', views.supervisor_scheduler, name='logs_scheduler'),
path('main/', views.main_view, name='main'), path('scanning/', views.scanning_view, name='scanning'),
path('images/', views.images_view, name='images'), path('images/', views.images_view, name='images'),
path('replay/', views.replay_view, name='replay'), path('replay/', views.replay_view, name='replay'),
path('api/stats/', views.stats_view, name='api_stats'), path('api/stats/', views.stats_view, name='api_stats'),
+2 -2
View File
@@ -75,14 +75,14 @@ def supervisor_scheduler(request):
## Mainboard ## Mainboard
@login_required @login_required
def main_view(request): def scanning_view(request):
ctx = dict( ctx = dict(
ws_route=settings.SCANNER_WEBSOCKET_ROUTE, ws_route=settings.SCANNER_WEBSOCKET_ROUTE,
columns=1, columns=1,
sessions=models.Session.objects.filter(active=True).all(), sessions=models.Session.objects.filter(active=True).all(),
choice_title=_("Balayage multi-puits") choice_title=_("Balayage multi-puits")
) )
return render(request, "scanner/main.html", context=global_context(request, **ctx)) return render(request, "scanner/scanning.html", context=global_context(request, **ctx))
## Calibration ## Calibration
@login_required @login_required