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_MIN_AREA = 20 # surface min planaire
TRACKER_MAX_AREA_RATIO = 0.05 # 5% de la frame = surface max acceptable
TRACKER_MIN_AREA = 8 # surface min planaire px x px
TRACKER_MAX_AREA_RATIO = 0.1 # 5% de la frame = surface max acceptable
TRACKER_MAX_PLANARIANS = 3
+17 -15
View File
@@ -122,7 +122,7 @@ class MultiWellManager:
#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
multiwell = experiment.multiwell
@@ -134,12 +134,15 @@ class MultiWellManager:
uuid = f'{self.process.data.session}-{multiwell.position}-{well.name}'
## start recording
self.process.data.uuid = uuid
self.process.data.record = True
if not simulate:
self.process.data.record = True
start = time.monotonic()
while not self.stop_playing.is_set():
if time.monotonic() - start > multiwell.duration:
break
self.cnc_controller.wait_for(1.0)
self.process.data.record = False
self.process.data.uuid = None
@@ -148,7 +151,7 @@ class MultiWellManager:
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
wells = models.WellPosition.objects.filter(multiwell_id=multiwell.id).order_by('order').all()
cam = self.process.cam
@@ -159,10 +162,7 @@ class MultiWellManager:
if self.stop_playing.is_set():
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)
self._grid_scanning_capture(experiment, wl)
self._grid_scanning_capture(experiment, wl, simulate=simulate)
## change 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)
def _start_scanning(self, session, experiments):
def _start_scanning(self, session, experiments, simulate=False):
self.process.cam._aligner.debug = False
xynext = []
for obs in experiments:
@@ -189,7 +189,7 @@ class MultiWellManager:
obs.save()
xnext, ynext = xynext[pos]
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.save()
@@ -197,10 +197,12 @@ class MultiWellManager:
if self.stop_playing.is_set():
msg = f"Session {session.name} abandonnée à {session.finished} après {session.finished - started} secondes."
else:
session.active = False
if session.scanning_task:
session.scanning_task.enabled = False
session.save()
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)
@@ -214,13 +216,13 @@ class MultiWellManager:
self.process.cam._aligner.debug = False
def scanning(self, sid):
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, ), daemon=True).start()
self.scan_thread = Thread(target=self._start_scanning, args=(session, experiments, simulate, ), daemon=True).start()
except Exception as e:
print("MultiWellManager::scan error", e)
+6 -4
View File
@@ -278,7 +278,7 @@ class ScannerProcess(Task):
if self.data.play:
try:
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)
except Exception as e:
logger.error(f"----_on_frame: {e}")
@@ -289,6 +289,8 @@ class ScannerProcess(Task):
while not self.stop_event.is_set():
try:
(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")
if metrics.get("detected"):
@@ -345,15 +347,15 @@ class ScannerProcess(Task):
self.cam._active_median = False
self.grbl.go_origin(feed=self.manager.feed)
elif topic == 'scan':
elif topic == 'scan' or topic == 'simulate':
logger.info(f"==== Scan {cmd}")
sid = cmd.get("session", '0')
if sid == "0":
self._send(state='error', msg=str(_('La session est nulle!...')))
else:
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":
topic = cmd.get("topic")
@@ -104,22 +104,24 @@
</a>
{% 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" %}
</a>
<a href="{% url 'planarian:experiment-list' %}" class="w3-bar-item w3-btn w3-hover-opacity">
{% if conf.tracking %}
<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" %}
</a>
{% endif %}
<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" %}
</a>
<a href="{% url 'scanner:replay' %}" class="w3-bar-item w3-btn w3-hover-opacity">
<i class="fa-solid fa-podcast w3-text-ambe w3-xlarge"r"></i> {% trans "Redifusion vidéos" %}
</a>
{% endblock %}
{% endblock %}
</div>
{% endblock %}
{% endblock %}
{% endblock %}
{% block content %}{% endblock %}
@@ -3,7 +3,7 @@
{% block styles %}
{{ block.super }}
<link href="/static/scanner/css/main.css" rel="stylesheet">
<link href="/static/scanner/css/scanning.css" rel="stylesheet">
{% endblock %}
{% block columns %}{% endblock %}
@@ -25,26 +25,34 @@
<div class="w3-col w3-padding"><span id="_uuid"></span></div>
</div>
</div>
<div class="w3-col">
<button id="_median" class="w3-button w3-dark-xlight w3-round-large w3-padding-small w3-block">
{% trans 'Axes' %}&nbsp;<br><i class="fa-solid fa-crosshairs w3-text-amber w3-xlarge"></i>
</button>
<div class="w3-row w3-row-padding">
<div class="w3-half">
<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>
</button>
</div>
<div class="w3-half">
<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>
</button>
</div>
</div>
<div class="w3-col">
<button id="_crop" class="w3-button w3-dark-xlight w3-round-large w3-padding-top w3-margin-top w3-block">
{% trans 'Recadrer' %}<br><i class="fa-solid fa-crop w3-text-amber w3-xlarge"></i>
</button>
</div>
<div class="w3-col">
<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>
</button>
</div>
<div class="w3-col">
<button id="_halt" class="w3-button w3-red w3-round-large w3-margin-top w3-block">
{% trans 'ARRET' %}<br><i class="fa-solid fa-hand w3-xlarge"></i>
</button>
<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">
{% trans 'Simuler' %}<br>{% trans 'le balayage' %}<br><i class="fa-solid fa-triangle-exclamation w3-text-orange w3-xlarge"></i>
</button>
</div>
<div class="w3-col">
<button id="_halt" class="w3-button w3-red w3-round-large w3-margin-top w3-block">
{% trans 'ARRET' %}<br><i class="fa-solid fa-hand w3-xlarge"></i>
</button>
</div>
</div>
<div class="w3-col">
<div class="w3-margin-top w3-padding w3-small">
@@ -85,10 +93,11 @@
axial_pos : sId("_axial_pos"),
area_px : sId("_area_px"),
frame_count : sId("_count"),
scan_state: sId("_scan_state")
scan_state : sId("_scan_state"),
simulate : sId("_simulate")
};
</script>
<script src="/static/scanner/js/main.js"></script>
<script src="/static/scanner/js/scanning.js"></script>
<script>
const manager = new ScannerManager(container, options);
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/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('replay/', views.replay_view, name='replay'),
path('api/stats/', views.stats_view, name='api_stats'),
+2 -2
View File
@@ -75,14 +75,14 @@ def supervisor_scheduler(request):
## Mainboard
@login_required
def main_view(request):
def scanning_view(request):
ctx = dict(
ws_route=settings.SCANNER_WEBSOCKET_ROUTE,
columns=1,
sessions=models.Session.objects.filter(active=True).all(),
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
@login_required