tracking
This commit is contained in:
@@ -30,6 +30,7 @@ echo "---- Tables ..."
|
|||||||
./manage.py loaddata $ETC/db/configuration.json
|
./manage.py loaddata $ETC/db/configuration.json
|
||||||
./manage.py loaddata $ETC/db/well.json
|
./manage.py loaddata $ETC/db/well.json
|
||||||
./manage.py loaddata $ETC/db/multiwell.json
|
./manage.py loaddata $ETC/db/multiwell.json
|
||||||
|
./manage.py reset_all_well_position --all True
|
||||||
|
|
||||||
echo "---- start test_tube:*"
|
echo "---- start test_tube:*"
|
||||||
echo
|
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)
|
||||||
|
|
||||||
@@ -389,10 +389,9 @@ DATETIME_FORMAT = '%d-%m-%Y-%m %H:%M:%S'
|
|||||||
|
|
||||||
EXPORTS_LOCAL_PATH = config("EXPORTS_LOCAL_PATH")
|
EXPORTS_LOCAL_PATH = config("EXPORTS_LOCAL_PATH")
|
||||||
EXPORT_REMOTE_PATH = config("EXPORT_REMOTE_PATH")
|
EXPORT_REMOTE_PATH = config("EXPORT_REMOTE_PATH")
|
||||||
|
|
||||||
EXPORT_DESTINATIONS = ["local", "remote"]
|
EXPORT_DESTINATIONS = ["local", "remote"]
|
||||||
|
|
||||||
TEST_VIDEOFILE = True
|
TEST_VIDEOFILE = False
|
||||||
|
|
||||||
TRACKING = True
|
TRACKING = True
|
||||||
TRACKER_TUBE_AXIS = "vertical"
|
TRACKER_TUBE_AXIS = "vertical"
|
||||||
|
|||||||
@@ -14,12 +14,12 @@ from django.contrib.auth.models import User
|
|||||||
|
|
||||||
# Multi-well positions on the table for calibration and observation
|
# Multi-well positions on the table for calibration and observation
|
||||||
MULTIWELL_POSITION = [
|
MULTIWELL_POSITION = [
|
||||||
('HG', _("Haut gauche")),
|
('HG', _("HG-Haut gauche")),
|
||||||
('HD', _("Haut droit")),
|
('HD', _("HD-Haut droit")),
|
||||||
('BG', _("Bas gauche")),
|
('BG', _("BG-Bas gauche")),
|
||||||
('BD', _("Bas droit")),
|
('BD', _("BD-Bas droit")),
|
||||||
('BM', _("Bas milieu")),
|
('BM', _("BM-Bas milieu")),
|
||||||
('HM', _("Haut milieu")),
|
('HM', _("HM-Haut milieu")),
|
||||||
]
|
]
|
||||||
|
|
||||||
FOURCC_FORMAT = [
|
FOURCC_FORMAT = [
|
||||||
@@ -32,6 +32,12 @@ VIDEO_TYPE = [
|
|||||||
('avi', _("AVI")),
|
('avi', _("AVI")),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
CAPTURE_TYPE = [
|
||||||
|
('rpi', _("Arducam")),
|
||||||
|
('webcam', _("Webcam")),
|
||||||
|
('file', _("mp4")),
|
||||||
|
]
|
||||||
|
|
||||||
class Configuration(models.Model):
|
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"))
|
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)
|
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 configuration
|
||||||
grbl_xmax = models.FloatField(_("Grbl Xmax"), help_text=_("CNC Grbl Xmax en mm"), blank=False, default=350.0)
|
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)
|
grbl_ymax = models.FloatField(_("Grbl Ymax"), help_text=_("CNC Grbl Ymax en mm"), blank=False, default=250.0)
|
||||||
|
|
||||||
|
|
||||||
# camera configuration
|
# camera configuration
|
||||||
use_rpicam = models.BooleanField(_("Utiliser rpicam"), help_text=_("Par défaaut. Sinon USB webcam"), default=True)
|
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)
|
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)
|
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)
|
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_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_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)
|
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)
|
active = models.BooleanField(_("Actif"), default=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|||||||
@@ -138,9 +138,8 @@ class CameraRecordManager():
|
|||||||
|
|
||||||
class ScannerProcess(Task):
|
class ScannerProcess(Task):
|
||||||
|
|
||||||
def __init__(self, use_tracking=False):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.use_tracking = use_tracking
|
|
||||||
self.channel_layer = get_channel_layer()
|
self.channel_layer = get_channel_layer()
|
||||||
self.group = f'scanner_proc'
|
self.group = f'scanner_proc'
|
||||||
self.stop_event = Event()
|
self.stop_event = Event()
|
||||||
@@ -162,6 +161,7 @@ class ScannerProcess(Task):
|
|||||||
def start(self, *args, **kwargs):
|
def start(self, *args, **kwargs):
|
||||||
try:
|
try:
|
||||||
self.conf = models.Configuration.objects.filter(active=True).first()
|
self.conf = models.Configuration.objects.filter(active=True).first()
|
||||||
|
self.use_tracking = self.conf.tracking
|
||||||
|
|
||||||
self.video_quality = self.conf.video_jpeg_quality
|
self.video_quality = self.conf.video_jpeg_quality
|
||||||
self.image_quality = self.conf.image_quality
|
self.image_quality = self.conf.image_quality
|
||||||
@@ -176,7 +176,9 @@ class ScannerProcess(Task):
|
|||||||
self.grbl_ymax = self.conf.grbl_ymax
|
self.grbl_ymax = self.conf.grbl_ymax
|
||||||
|
|
||||||
self.crop = self.set_crop_radius(self.crop_radius)
|
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 = []
|
video_lists = []
|
||||||
wells = models.Well.objects.all()
|
wells = models.Well.objects.all()
|
||||||
for wl in wells:
|
for wl in wells:
|
||||||
@@ -194,7 +196,7 @@ class ScannerProcess(Task):
|
|||||||
parent=self,
|
parent=self,
|
||||||
video_lists=video_lists,
|
video_lists=video_lists,
|
||||||
)
|
)
|
||||||
elif not self.conf.use_rpicam:
|
elif capture_type == 'webcam':
|
||||||
from modules.webcam_capture import WebcamCapture
|
from modules.webcam_capture import WebcamCapture
|
||||||
self.cam = WebcamCapture(
|
self.cam = WebcamCapture(
|
||||||
device_index=self.conf.webcam_device_index,
|
device_index=self.conf.webcam_device_index,
|
||||||
@@ -275,6 +277,7 @@ class ScannerProcess(Task):
|
|||||||
try:
|
try:
|
||||||
(uuid, ts, frame, metrics) = self.record_queue.get()
|
(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")
|
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"):
|
||||||
labels.update({
|
labels.update({
|
||||||
"cx" : str(metrics["cx"]),
|
"cx" : str(metrics["cx"]),
|
||||||
@@ -283,7 +286,8 @@ class ScannerProcess(Task):
|
|||||||
"speed_px_s" : str(metrics["speed_px_s"]),
|
"speed_px_s" : str(metrics["speed_px_s"]),
|
||||||
"axial_pos" : str(metrics["axial_pos"]),
|
"axial_pos" : str(metrics["axial_pos"]),
|
||||||
"axial_speed" : str(metrics["axial_speed"]),
|
"axial_speed" : str(metrics["axial_speed"]),
|
||||||
})
|
})
|
||||||
|
|
||||||
self.recordDB.write(uuid, frame, labels, ts=ts)
|
self.recordDB.write(uuid, frame, labels, ts=ts)
|
||||||
self.record_queue.task_done()
|
self.record_queue.task_done()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import asyncio
|
|||||||
from celery import shared_task, group, chord, chain
|
from celery import shared_task, group, chord, chain
|
||||||
from celery.utils.log import get_task_logger
|
from celery.utils.log import get_task_logger
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
from .process import ScannerProcess, ReplayProcess
|
from .process import ScannerProcess, ReplayProcess
|
||||||
from .export_tasks import shm_download_video, export_images_zip, export_video_mp4
|
from .export_tasks import shm_download_video, export_images_zip, export_video_mp4
|
||||||
@@ -19,7 +18,7 @@ class ScannerTaskManager:
|
|||||||
|
|
||||||
def start_scanner(self):
|
def start_scanner(self):
|
||||||
if self.scanner is None:
|
if self.scanner is None:
|
||||||
self.scanner = ScannerProcess(use_tracking=settings.TRACKING)
|
self.scanner = ScannerProcess()
|
||||||
self.scanner.start()
|
self.scanner.start()
|
||||||
|
|
||||||
def stop_scanner(self):
|
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">
|
<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" %}
|
<i class="fa-solid fa-gear w3-text-pink"></i> {% trans "Administration base de données" %}
|
||||||
</a>
|
</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" %}
|
<img class="w3-no-padding" src="/static/img/reductstore.png" style="width: 24px"> {% trans "Base de données Reductstore" %}
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'scanner:adminer' %}" class="w3-bar-item w3-btn w3-hover-opacity w3-margin-left" target="_blank">
|
<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">
|
<!--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" %}
|
<img class="w3-no-padding" src="/static/img/portainer.png" style="width: 28px"> {% trans "Portainer" %}
|
||||||
</a-->
|
</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" %}
|
<i class="fa-solid fa-gears w3-text-purple"></i> {% trans "Supervisor" %}
|
||||||
</a>
|
</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" %}
|
<i class="fa-regular fa-file-lines w3-text-orange"></i> {% trans "Logs des planifications" %}
|
||||||
</a>
|
</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" %}
|
<i class="fa-regular fa-file-lines w3-text-amber"></i> {% trans "Logs des services" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -133,17 +133,17 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="w3-row w3-row-padding">
|
<div class="w3-row w3-row-padding">
|
||||||
<div class="w3-half">
|
<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>
|
{% trans 'Debug' %}<br><i class="fa-solid fa-triangle-exclamation w3-text-amber w3-xlarge"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="w3-half">
|
<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-padding-1">
|
||||||
{% trans 'Axes' %} <br><i class="fa-solid fa-crosshairs w3-text-amber w3-xlarge"></i>
|
{% trans 'Axes' %}<br><i class="fa-solid fa-crosshairs w3-text-amber w3-xlarge"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="w3-col">
|
<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>
|
{% trans 'Recadrer' %}<br><i class="fa-solid fa-crop w3-text-amber w3-xlarge"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ def admin_view(request):
|
|||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def reductstore_view(request):
|
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
|
@login_required
|
||||||
def adminer_view(request):
|
def adminer_view(request):
|
||||||
@@ -62,15 +62,15 @@ def portainer_view(request):
|
|||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def supervisor_view(request):
|
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
|
@login_required
|
||||||
def supervisor_worker(request):
|
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
|
@login_required
|
||||||
def supervisor_scheduler(request):
|
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
|
## Mainboard
|
||||||
@login_required
|
@login_required
|
||||||
|
|||||||
Reference in New Issue
Block a user