diff --git a/etc/3-install-samba-client.sh b/etc/3-install-samba-client.sh
index 5a935d0..6893cfb 100755
--- a/etc/3-install-samba-client.sh
+++ b/etc/3-install-samba-client.sh
@@ -50,6 +50,8 @@ sudo bash -c "echo '//$samba_server/$public_share /mnt/samba/public cifs guest,u
echo "Montage des partages Samba..."
sudo mount -a
+sudo mkdir -p /mnt/samba/public/images /mnt/samba/public/videos
+
# Vérification du montage
echo "Vérification des partages montés :"
df -h | grep samba
diff --git a/test_tube_scanner/home/settings.py b/test_tube_scanner/home/settings.py
index 4ab3a33..a3ebb59 100644
--- a/test_tube_scanner/home/settings.py
+++ b/test_tube_scanner/home/settings.py
@@ -390,7 +390,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"]
+#EXPORT_DESTINATIONS = ["remote"] # only remote
TEST_VIDEOFILE = False
diff --git a/test_tube_scanner/scanner/export_tasks.py b/test_tube_scanner/scanner/export_tasks.py
index 8af0bb9..734d02d 100644
--- a/test_tube_scanner/scanner/export_tasks.py
+++ b/test_tube_scanner/scanner/export_tasks.py
@@ -144,6 +144,10 @@ def _copy_to_destinations(source_path: str, filename: str) -> dict:
Retourne un dict avec les chemins effectivement écrits.
"""
results = {"local": None, "remote": None}
+
+ filename = filename.replace(':', '_')
+
+ logger.info("%s %s", source_path, filename)
for dest in settings.EXPORT_DESTINATIONS:
@@ -151,10 +155,10 @@ def _copy_to_destinations(source_path: str, filename: str) -> dict:
# Déjà sur place, rien à copier
results["local"] = source_path
- elif dest == "remote":
- remote_path = os.path.join(settings.EXPORT_REMOTE_DIR, filename)
+ elif dest == "remote":
+ remote_path = os.path.join(settings.EXPORT_REMOTE_PATH, filename)
try:
- if not remote_mount_available(settings.EXPORT_REMOTE_DIR):
+ if not remote_mount_available(settings.EXPORT_REMOTE_PATH):
logger.warning("Partage Samba non disponible, copie ignorée")
results["remote_error"] = "Montage indisponible"
continue
@@ -248,6 +252,7 @@ async def export_images_zip(
# --- Génération du ZIP ---
max_zip_bytes = max_zip_size_mb * 1024 * 1024 if max_zip_size_mb > 0 else 0
ts_s = unix_timestamp_to_iso(start_ts)
+
zip_filename = f"{uuid}_{ts_s}.zip"
zip_path = os.path.join(settings.EXPORTS_LOCAL_PATH, 'images', zip_filename)
os.makedirs(os.path.dirname(zip_path), exist_ok=True)
@@ -297,14 +302,16 @@ async def export_images_zip(
current_offset += size
## Copie vers les destinations (local + Samba)
- #destinations = _copy_to_destinations(zip_path, zip_filename)
+ filename = os.path.join('images', zip_filename)
+
+ destinations = _copy_to_destinations(zip_path, filename)
return {
"status": "success",
"zip_path": zip_path,
"frames_written": written,
"frames_skipped": skipped,
"jpeg_quality": jpeg_quality,
- #"destinations": destinations,
+ "destinations": destinations,
}
except Exception as exc:
@@ -461,8 +468,10 @@ async def export_video_mp4(
return {"status": "error", "message": f"Fichier {opencv_video_type} non créé"}
## Copie vers les destinations (local + Samba)
- #filename = os.path.basename(video_path)
- #destinations = _copy_to_destinations(video_path, filename)
+ filename = os.path.basename(video_path)
+ filename = os.path.join('videos', filename)
+
+ destinations = _copy_to_destinations(video_path, filename)
return {
"status": "success",
"video_path": video_path,
@@ -470,7 +479,7 @@ async def export_video_mp4(
"frames_skipped": skipped,
"frame_rate": frame_rate,
"file_size_mb": round(os.path.getsize(video_path) / 1024 / 1024, 2),
- #"destinations": destinations,
+ "destinations": destinations,
}
except Exception as exc:
diff --git a/test_tube_scanner/scanner/models.py b/test_tube_scanner/scanner/models.py
index abaacd1..da3a86e 100644
--- a/test_tube_scanner/scanner/models.py
+++ b/test_tube_scanner/scanner/models.py
@@ -227,6 +227,7 @@ class Experiment(models.Model):
created = models.DateTimeField(_("Date de création"), default=timezone.now)
started = models.DateTimeField (_("Date de début"), null=True, blank=True)
finished = models.DateTimeField (_("Date de fin"), null=True, blank=True)
+
class Meta:
ordering = ['-created', ]
@@ -270,7 +271,12 @@ class Session(models.Model):
null=True, blank=True, on_delete=models.SET_NULL, related_name="scanning_session")
scanning_finished_at = models.DateTimeField(_("Balayage terminé à"), null=True, blank=True)
-
+
+ @classmethod
+ def get_session(self, sid):
+ return Session.objects.filter(pk=sid).first()
+
+
class Meta:
ordering = ['-created', ]
verbose_name = _("Session d'expérience")
diff --git a/test_tube_scanner/scanner/templates/scanner/images.html b/test_tube_scanner/scanner/templates/scanner/images.html
index d22862c..93851eb 100644
--- a/test_tube_scanner/scanner/templates/scanner/images.html
+++ b/test_tube_scanner/scanner/templates/scanner/images.html
@@ -9,19 +9,26 @@
{% trans "Retour accueil" %}
- {% if cursid %}
+ {% if current_session.id %}
- {% trans "Exporter l'ensemble des images" %}
+ {% trans "Exporter l'ensemble des images" %}
+ {{ export_destination }}
{% endif %}
{% endblock %}
@@ -95,14 +102,15 @@
action: 'export_images',
sid: sid,
})
- }).then(res => {
- console.log(res);
-
- }).catch(error => {
+ })
+ .then(response => response.json())
+ .then(res => {
+ alert(res.msg);
+ })
+ .catch(error => {
console.error('Erreur:', error);
});
}
-
setGridColumns(columns);
if (uuid_btn)
handleMultiwell(uuid_btn);
diff --git a/test_tube_scanner/scanner/templates/scanner/replay.html b/test_tube_scanner/scanner/templates/scanner/replay.html
index 6ef00e0..bd64415 100644
--- a/test_tube_scanner/scanner/templates/scanner/replay.html
+++ b/test_tube_scanner/scanner/templates/scanner/replay.html
@@ -11,18 +11,26 @@
- {% if cursid %}
+ {% if current_session.id %}
- {% trans "Exporter l'ensemble des vidéos" %}
+ {% trans "Exporter l'ensemble des vidéos" %}
+ {{ export_destination }}
{% endif %}
{% endblock %}
@@ -80,9 +88,12 @@
action: 'export_videos',
sid: sid,
})
- }).then(res => {
- console.log(res);
- }).catch(error => {
+ })
+ .then(response => response.json())
+ .then(res => {
+ alert(res.msg);
+ })
+ .catch(error => {
console.error('Erreur:', error);
});
}
diff --git a/test_tube_scanner/scanner/templatetags/scanner_tags.py b/test_tube_scanner/scanner/templatetags/scanner_tags.py
index 9a66f63..71fc2de 100644
--- a/test_tube_scanner/scanner/templatetags/scanner_tags.py
+++ b/test_tube_scanner/scanner/templatetags/scanner_tags.py
@@ -5,22 +5,19 @@ from django.utils.html import mark_safe
register = template.Library()
@register.simple_tag
-def multiwell_cards(sid, experiments):
+def multiwell_cards(sid, experiment):
multiwells = []
- for obs in experiments:
- row_def = obs.multiwell.row_def.split(',')
- multiwells.append(
- f'''
- {obs.title}
-
- ''')
- for row in range(obs.multiwell.rows):
- for col in range(obs.multiwell.cols):
- btn = f'{row_def[row]}{col+1}'
- uuid = f'{sid}-{obs.multiwell.position}-{btn}'
- multiwells.append(f"""{btn} """)
- multiwells.append('''
''')
+ row_def = experiment.multiwell.row_def.split(',')
+ multiwells.append(
+ f'''
+ {experiment.title}
+
+ ''')
+ for row in range(experiment.multiwell.rows):
+ for col in range(experiment.multiwell.cols):
+ btn = f'{row_def[row]}{col+1}'
+ uuid = f'{sid}-{experiment.multiwell.position}-{btn}'
+ multiwells.append(f"""{btn} """)
+ multiwells.append('''
''')
return mark_safe("\n".join(multiwells))
-
-
diff --git a/test_tube_scanner/scanner/views.py b/test_tube_scanner/scanner/views.py
index 7100e3a..10e0c55 100644
--- a/test_tube_scanner/scanner/views.py
+++ b/test_tube_scanner/scanner/views.py
@@ -35,14 +35,13 @@ def stats_view(request):
def global_context(request, **ctx):
default_multiwell = models.MultiWell.objects.filter(default=True).first()
conf = ScannerConstants().get()
-
- print(conf, type(conf))
return dict(
app_title=settings.APP_TITLE,
app_sub_title=settings.APP_SUB_TITLE,
domain_server=settings.DOMAIN_SERVER,
conf=conf,
default_position = default_multiwell.position or 'HD',
+ export_destination=settings.EXPORT_DESTINATIONS,
**ctx
)
@@ -97,6 +96,17 @@ def calibration_view(request):
return render(request, "scanner/calibration.html", context=global_context(request, **ctx))
+def get_not_active_experiments(session, expid=None):
+ if session:
+ experiments = models.SessionExperiment.experiment_by_session(session.id, active=False) or []
+ if experiments and not expid:
+ return experiments, experiments[0]
+ for e in experiments:
+ if expid == str(e.id):
+ return experiments, e
+ return [], None
+
+
## images
def get_images(uuid):
oldest, latest, n, images = 0, 0, 0, []
@@ -124,17 +134,21 @@ def get_images(uuid):
@login_required
def images_view(request):
- cursid, duration, uuid, images = 0, 0, "", []
+ cursid, expid, duration, uuid, images = 0, None, 0, "", []
if request.method == 'POST':
cursid = request.POST.get('_sid')
+ expid = request.POST.get('_expid')
uuid = request.POST.get('_multiwell')
duration, images = get_images(uuid)
+ current_session = models.Session.get_session(cursid)
+ experiments, current_experiment = get_not_active_experiments(current_session, expid)
ctx = dict(
choice_title=_("Gestionnaire d'images"),
sessions=models.Session.objects.filter(active=False).all(),
- experiments=models.SessionExperiment.experiment_by_session(cursid, active=False),
- cursid=int(cursid),
+ experiments=experiments or [],
+ current_session=current_session,
+ current_experiment=current_experiment,
images=images,
uuid=uuid,
duration=duration,
@@ -164,20 +178,24 @@ def get_video(uuid):
@login_required
def replay_view(request):
- cursid, oldest, latest, uuid, image = 0, 0, 0, "", ""
+ cursid, expid, oldest, latest, uuid, image = 0, None, 0, 0, "", ""
if request.method == 'POST':
cursid = request.POST.get('_sid')
+ expid = request.POST.get('_expid')
uuid = request.POST.get('_multiwell')
if uuid:
oldest, latest, image = get_video(uuid)
-
+
+ current_session = models.Session.get_session(cursid)
+ experiments, current_experiment = get_not_active_experiments(current_session, expid)
ctx = dict(
choice_title=_("Gestionnaire de vidéos"),
ws_route=settings.REPLAY_WEBSOCKET_ROUTE,
sessions=models.Session.objects.filter(active=False).all(),
- experiments=models.SessionExperiment.experiment_by_session(cursid, active=False),
- cursid=int(cursid),
+ experiments=experiments or [],
+ current_session=current_session,
+ current_experiment=current_experiment,
image=image,
uuid=uuid,
oldest=oldest,
@@ -193,12 +211,12 @@ def export_api(request):
action = data.get("action")
if action == 'export_images':
- job_zip = export_all_images(session_id)
- return JsonResponse({"state": True, "tasks": job_zip})
+ export_all_images(session_id)
+ return JsonResponse({"success": True, "msg": str(_("Images téléchargées"))})
elif action == 'export_videos':
- job_mp4 = export_all_videos(session_id)
- return JsonResponse({"state": True, "tasks": job_mp4})
+ export_all_videos(session_id)
+ return JsonResponse({"success": True, "msg": str(_("Vidéos téléchargées"))})
else:
- return JsonResponse({"state": False})
+ return JsonResponse({"success": False, "msg": str(_("Erreur d'exportation"))})