This commit is contained in:
2026-04-26 18:07:29 +02:00
parent 5bb5b29f33
commit 922d89bf8d
9 changed files with 79 additions and 30 deletions
+1
View File
@@ -30,6 +30,7 @@ echo "---- Tables ..."
./manage.py loaddata $ETC/db/configuration.json
./manage.py loaddata $ETC/db/well.json
./manage.py loaddata $ETC/db/multiwell.json
./manage.py reset_all_well_position --all True
echo "---- start test_tube:*"
echo
@@ -0,0 +1,36 @@
'''
Created on 19 janv. 2026
@author: denis
'''
from django.core.management.base import BaseCommand
#from django.conf import settings
from scanner.models import MultiWell
class Command(BaseCommand):
help = "Recalculer les positions de tous les puits d'un multi-puits"
def add_arguments(self, parser):
super(Command, self).add_arguments(parser)
parser.add_argument("--all", type=bool, help=f"Tous les puits de tous les multi-puits", default=False)
parser.add_argument("--position", type=str, help=f"Multiwel position: HD, HG, BD, BG", default="HD")
def handle(self, *args, **options): # @UnusedVariable
try:
position = options.get('position')
_all = options.get('all')
multiwells = [m for m in MultiWell.objects.filter(active=True).all() ]
for m in multiwells:
if m.position==position and not _all:
m.well_position=False
m.save()
print("Reset multi-puits position", position)
break
m.well_position=False
m.save()
print("Reset multi-puits position", position)
except Exception as e:
print(self.help, 'erreur', e)
+1 -2
View File
@@ -389,10 +389,9 @@ DATETIME_FORMAT = '%d-%m-%Y-%m %H:%M:%S'
EXPORTS_LOCAL_PATH = config("EXPORTS_LOCAL_PATH")
EXPORT_REMOTE_PATH = config("EXPORT_REMOTE_PATH")
EXPORT_DESTINATIONS = ["local", "remote"]
TEST_VIDEOFILE = True
TEST_VIDEOFILE = False
TRACKING = True
TRACKER_TUBE_AXIS = "vertical"
+18 -8
View File
@@ -14,12 +14,12 @@ from django.contrib.auth.models import User
# Multi-well positions on the table for calibration and observation
MULTIWELL_POSITION = [
('HG', _("Haut gauche")),
('HD', _("Haut droit")),
('BG', _("Bas gauche")),
('BD', _("Bas droit")),
('BM', _("Bas milieu")),
('HM', _("Haut milieu")),
('HG', _("HG-Haut gauche")),
('HD', _("HD-Haut droit")),
('BG', _("BG-Bas gauche")),
('BD', _("BD-Bas droit")),
('BM', _("BM-Bas milieu")),
('HM', _("HM-Haut milieu")),
]
FOURCC_FORMAT = [
@@ -32,6 +32,12 @@ VIDEO_TYPE = [
('avi', _("AVI")),
]
CAPTURE_TYPE = [
('rpi', _("Arducam")),
('webcam', _("Webcam")),
('file', _("mp4")),
]
class Configuration(models.Model):
name = models.CharField(_("Nom de la Configuration"), help_text=_("Nom de la configuration"), max_length=100, null=True, blank=False, default=_("Configuration par défaut"))
author = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="Auteur", null=True, blank=True)
@@ -44,10 +50,10 @@ class Configuration(models.Model):
# Grbl configuration
grbl_xmax = models.FloatField(_("Grbl Xmax"), help_text=_("CNC Grbl Xmax en mm"), blank=False, default=350.0)
grbl_ymax = models.FloatField(_("Grbl Ymax"), help_text=_("CNC Grbl Ymax en mm"), blank=False, default=250.0)
# camera configuration
use_rpicam = models.BooleanField(_("Utiliser rpicam"), help_text=_("Par défaaut. Sinon USB webcam"), default=True)
capture_type = models.CharField(_("Capture"), help_text=_("Type de capture"), default='rpi', max_length=8, choices=CAPTURE_TYPE, null=True, blank=False)
webcam_device_index = models.PositiveSmallIntegerField(_("Index de la webcam"), help_text=_("Index de la webcam (0, 1, ...) si présente"), default=2)
image_quality = models.PositiveSmallIntegerField(_("Qualité JPEG"), help_text=_("Qualité JPEG (1-100) pour les images exportées"), default=90)
video_jpeg_quality = models.PositiveSmallIntegerField(_("Qualité JPEG pour les vidéos"), help_text=_("Qualité JPEG (1-100) pour les images extraites des vidéos"), default=90)
@@ -60,6 +66,10 @@ class Configuration(models.Model):
calibration_default_feed = models.PositiveIntegerField(_("Vitesse de calibration"), help_text=_("Vitesse de déplacement pour la calibration en mm/mn"), default=1000)
calibration_default_step = models.FloatField(_("Pas de calibration"), help_text=_("Pas de déplacement pour la calibration en mm"), default=1.0)
calibration_default_duration = models.FloatField(_("Duruée calibration"), help_text=_("Durée de pose entre chaque puits en s"), default=3.0)
# tracking
tracking = models.BooleanField(_("Suivi"), help_text=_("Suivi et analyse des planaires"), default=False)
#
active = models.BooleanField(_("Actif"), default=False)
class Meta:
+8 -4
View File
@@ -138,9 +138,8 @@ class CameraRecordManager():
class ScannerProcess(Task):
def __init__(self, use_tracking=False):
def __init__(self):
super().__init__()
self.use_tracking = use_tracking
self.channel_layer = get_channel_layer()
self.group = f'scanner_proc'
self.stop_event = Event()
@@ -162,6 +161,7 @@ class ScannerProcess(Task):
def start(self, *args, **kwargs):
try:
self.conf = models.Configuration.objects.filter(active=True).first()
self.use_tracking = self.conf.tracking
self.video_quality = self.conf.video_jpeg_quality
self.image_quality = self.conf.image_quality
@@ -176,7 +176,9 @@ class ScannerProcess(Task):
self.grbl_ymax = self.conf.grbl_ymax
self.crop = self.set_crop_radius(self.crop_radius)
if settings.TEST_VIDEOFILE:
capture_type = self.conf.capture_type
if capture_type == 'file':
video_lists = []
wells = models.Well.objects.all()
for wl in wells:
@@ -194,7 +196,7 @@ class ScannerProcess(Task):
parent=self,
video_lists=video_lists,
)
elif not self.conf.use_rpicam:
elif capture_type == 'webcam':
from modules.webcam_capture import WebcamCapture
self.cam = WebcamCapture(
device_index=self.conf.webcam_device_index,
@@ -275,6 +277,7 @@ class ScannerProcess(Task):
try:
(uuid, ts, frame, metrics) = 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"):
labels.update({
"cx" : str(metrics["cx"]),
@@ -284,6 +287,7 @@ class ScannerProcess(Task):
"axial_pos" : str(metrics["axial_pos"]),
"axial_speed" : str(metrics["axial_speed"]),
})
self.recordDB.write(uuid, frame, labels, ts=ts)
self.record_queue.task_done()
except Exception as e:
+1 -2
View File
@@ -3,7 +3,6 @@ import asyncio
from celery import shared_task, group, chord, chain
from celery.utils.log import get_task_logger
from django.utils import timezone
from django.conf import settings
from .process import ScannerProcess, ReplayProcess
from .export_tasks import shm_download_video, export_images_zip, export_video_mp4
@@ -19,7 +18,7 @@ class ScannerTaskManager:
def start_scanner(self):
if self.scanner is None:
self.scanner = ScannerProcess(use_tracking=settings.TRACKING)
self.scanner = ScannerProcess()
self.scanner.start()
def stop_scanner(self):
@@ -74,7 +74,7 @@
<a href="{% url 'scanner:admin' %}" class="w3-bar-item w3-btn w3-hover-opacity w3-margin-left">
<i class="fa-solid fa-gear w3-text-pink"></i> {% trans "Administration base de données" %}
</a>
<a href="{% url 'scanner:reductstore' %}" class="w3-bar-item w3-btn w3-hover-opacity w3-margin-left">
<a href="{% url 'scanner:reductstore' %}" class="w3-bar-item w3-btn w3-hover-opacity w3-margin-left" target="_blank">
<img class="w3-no-padding" src="/static/img/reductstore.png" style="width: 24px"> {% trans "Base de données Reductstore" %}
</a>
<a href="{% url 'scanner:adminer' %}" class="w3-bar-item w3-btn w3-hover-opacity w3-margin-left" target="_blank">
@@ -83,13 +83,13 @@
<!--a href="{% url 'scanner:portainer' %}" class="w3-bar-item w3-btn w3-hover-opacity" w3-margin-left target="_blank">
<img class="w3-no-padding" src="/static/img/portainer.png" style="width: 28px"> {% trans "Portainer" %}
</a-->
<a href="{% url 'scanner:supervisor' %}" class="w3-bar-item w3-btn w3-hover-opacity w3-margin-left">
<a href="{% url 'scanner:supervisor' %}" class="w3-bar-item w3-btn w3-hover-opacity w3-margin-left" target="_blank">
<i class="fa-solid fa-gears w3-text-purple"></i> {% trans "Supervisor" %}
</a>
<a href="{% url 'scanner:logs_scheduler' %}" class="w3-bar-item w3-btn w3-hover-opacity w3-margin-left">
<a href="{% url 'scanner:logs_scheduler' %}" class="w3-bar-item w3-btn w3-hover-opacity w3-margin-left" target="_blank">
<i class="fa-regular fa-file-lines w3-text-orange"></i> {% trans "Logs des planifications" %}
</a>
<a href="{% url 'scanner:logs_worker' %}" class="w3-bar-item w3-btn w3-hover-opacity w3-margin-left">
<a href="{% url 'scanner:logs_worker' %}" class="w3-bar-item w3-btn w3-hover-opacity w3-margin-left" target="_blank">
<i class="fa-regular fa-file-lines w3-text-amber"></i> {% trans "Logs des services" %}
</a>
</div>
@@ -133,17 +133,17 @@
</div>
<div class="w3-row w3-row-padding">
<div class="w3-half">
<button id="_calib_debug" class="w3-button w3-dark-xlight w3-round-large w3-padding-small w3-block">
<button id="_calib_debug" class="w3-button w3-dark-xlight w3-round-large w3-padding-1" title="{% trans 'Ce mode permet les réglages graphiques' %}">
{% trans 'Debug' %}<br><i class="fa-solid fa-triangle-exclamation w3-text-amber w3-xlarge"></i>
</button>
</div>
<div class="w3-half">
<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 id="_median" class="w3-button w3-dark-xlight w3-round-large w3-padding-1">
{% trans 'Axes' %}<br><i class="fa-solid fa-crosshairs w3-text-amber w3-xlarge"></i>
</button>
</div>
<div class="w3-col">
<button id="_crop" class="w3-button w3-dark-xlight w3-round-large w3-padding-small w3-margin-top w3-block">
<button id="_crop" class="w3-button w3-dark-xlight w3-round-large w3-padding-small w3-margin-top w3-block" title="{% trans 'Ce mode actionne le pointage graphique' %}">
{% trans 'Recadrer' %}<br><i class="fa-solid fa-crop w3-text-amber w3-xlarge"></i>
</button>
</div>
+4 -4
View File
@@ -50,7 +50,7 @@ def admin_view(request):
@login_required
def reductstore_view(request):
return render(request, "scanner/iframe.html", context=global_context(request, link=f'http://{settings.DOMAIN_SERVER}:8383/'))
return render(request, "scanner/redirection.html", context=global_context(request, link=f'http://{settings.DOMAIN_SERVER}:8383/'))
@login_required
def adminer_view(request):
@@ -62,15 +62,15 @@ def portainer_view(request):
@login_required
def supervisor_view(request):
return render(request, "scanner/iframe.html", context=global_context(request, link=f'http://{settings.DOMAIN_SERVER}:9001/'))
return render(request, "scanner/redirection.html", context=global_context(request, link=f'http://{settings.DOMAIN_SERVER}:9001/'))
@login_required
def supervisor_worker(request):
return render(request, "scanner/iframe.html", context=global_context(request, link=f'http://{settings.DOMAIN_SERVER}:9001/logtail/test_tube:services'))
return render(request, "scanner/redirection.html", context=global_context(request, link=f'http://{settings.DOMAIN_SERVER}:9001/logtail/test_tube:services'))
@login_required
def supervisor_scheduler(request):
return render(request, "scanner/iframe.html", context=global_context(request, link=f'http://{settings.DOMAIN_SERVER}:9001/logtail/test_tube:planification'))
return render(request, "scanner/redirection.html", context=global_context(request, link=f'http://{settings.DOMAIN_SERVER}:9001/logtail/test_tube:planification'))
## Mainboard
@login_required