export
This commit is contained in:
@@ -50,6 +50,8 @@ sudo bash -c "echo '//$samba_server/$public_share /mnt/samba/public cifs guest,u
|
|||||||
echo "Montage des partages Samba..."
|
echo "Montage des partages Samba..."
|
||||||
sudo mount -a
|
sudo mount -a
|
||||||
|
|
||||||
|
sudo mkdir -p /mnt/samba/public/images /mnt/samba/public/videos
|
||||||
|
|
||||||
# Vérification du montage
|
# Vérification du montage
|
||||||
echo "Vérification des partages montés :"
|
echo "Vérification des partages montés :"
|
||||||
df -h | grep samba
|
df -h | grep samba
|
||||||
|
|||||||
@@ -390,7 +390,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"]
|
||||||
|
#EXPORT_DESTINATIONS = ["remote"] # only remote
|
||||||
|
|
||||||
TEST_VIDEOFILE = False
|
TEST_VIDEOFILE = False
|
||||||
|
|
||||||
|
|||||||
@@ -145,6 +145,10 @@ def _copy_to_destinations(source_path: str, filename: str) -> dict:
|
|||||||
"""
|
"""
|
||||||
results = {"local": None, "remote": None}
|
results = {"local": None, "remote": None}
|
||||||
|
|
||||||
|
filename = filename.replace(':', '_')
|
||||||
|
|
||||||
|
logger.info("%s %s", source_path, filename)
|
||||||
|
|
||||||
for dest in settings.EXPORT_DESTINATIONS:
|
for dest in settings.EXPORT_DESTINATIONS:
|
||||||
|
|
||||||
if dest == "local":
|
if dest == "local":
|
||||||
@@ -152,9 +156,9 @@ def _copy_to_destinations(source_path: str, filename: str) -> dict:
|
|||||||
results["local"] = source_path
|
results["local"] = source_path
|
||||||
|
|
||||||
elif dest == "remote":
|
elif dest == "remote":
|
||||||
remote_path = os.path.join(settings.EXPORT_REMOTE_DIR, filename)
|
remote_path = os.path.join(settings.EXPORT_REMOTE_PATH, filename)
|
||||||
try:
|
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")
|
logger.warning("Partage Samba non disponible, copie ignorée")
|
||||||
results["remote_error"] = "Montage indisponible"
|
results["remote_error"] = "Montage indisponible"
|
||||||
continue
|
continue
|
||||||
@@ -248,6 +252,7 @@ async def export_images_zip(
|
|||||||
# --- Génération du ZIP ---
|
# --- Génération du ZIP ---
|
||||||
max_zip_bytes = max_zip_size_mb * 1024 * 1024 if max_zip_size_mb > 0 else 0
|
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)
|
ts_s = unix_timestamp_to_iso(start_ts)
|
||||||
|
|
||||||
zip_filename = f"{uuid}_{ts_s}.zip"
|
zip_filename = f"{uuid}_{ts_s}.zip"
|
||||||
zip_path = os.path.join(settings.EXPORTS_LOCAL_PATH, 'images', zip_filename)
|
zip_path = os.path.join(settings.EXPORTS_LOCAL_PATH, 'images', zip_filename)
|
||||||
os.makedirs(os.path.dirname(zip_path), exist_ok=True)
|
os.makedirs(os.path.dirname(zip_path), exist_ok=True)
|
||||||
@@ -297,14 +302,16 @@ async def export_images_zip(
|
|||||||
current_offset += size
|
current_offset += size
|
||||||
|
|
||||||
## Copie vers les destinations (local + Samba)
|
## 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 {
|
return {
|
||||||
"status": "success",
|
"status": "success",
|
||||||
"zip_path": zip_path,
|
"zip_path": zip_path,
|
||||||
"frames_written": written,
|
"frames_written": written,
|
||||||
"frames_skipped": skipped,
|
"frames_skipped": skipped,
|
||||||
"jpeg_quality": jpeg_quality,
|
"jpeg_quality": jpeg_quality,
|
||||||
#"destinations": destinations,
|
"destinations": destinations,
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
@@ -461,8 +468,10 @@ async def export_video_mp4(
|
|||||||
return {"status": "error", "message": f"Fichier {opencv_video_type} non créé"}
|
return {"status": "error", "message": f"Fichier {opencv_video_type} non créé"}
|
||||||
|
|
||||||
## Copie vers les destinations (local + Samba)
|
## Copie vers les destinations (local + Samba)
|
||||||
#filename = os.path.basename(video_path)
|
filename = os.path.basename(video_path)
|
||||||
#destinations = _copy_to_destinations(video_path, filename)
|
filename = os.path.join('videos', filename)
|
||||||
|
|
||||||
|
destinations = _copy_to_destinations(video_path, filename)
|
||||||
return {
|
return {
|
||||||
"status": "success",
|
"status": "success",
|
||||||
"video_path": video_path,
|
"video_path": video_path,
|
||||||
@@ -470,7 +479,7 @@ async def export_video_mp4(
|
|||||||
"frames_skipped": skipped,
|
"frames_skipped": skipped,
|
||||||
"frame_rate": frame_rate,
|
"frame_rate": frame_rate,
|
||||||
"file_size_mb": round(os.path.getsize(video_path) / 1024 / 1024, 2),
|
"file_size_mb": round(os.path.getsize(video_path) / 1024 / 1024, 2),
|
||||||
#"destinations": destinations,
|
"destinations": destinations,
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
|||||||
@@ -228,6 +228,7 @@ class Experiment(models.Model):
|
|||||||
started = models.DateTimeField (_("Date de début"), null=True, blank=True)
|
started = models.DateTimeField (_("Date de début"), null=True, blank=True)
|
||||||
finished = models.DateTimeField (_("Date de fin"), null=True, blank=True)
|
finished = models.DateTimeField (_("Date de fin"), null=True, blank=True)
|
||||||
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['-created', ]
|
ordering = ['-created', ]
|
||||||
verbose_name = _("Expérience")
|
verbose_name = _("Expérience")
|
||||||
@@ -271,6 +272,11 @@ class Session(models.Model):
|
|||||||
scanning_finished_at = models.DateTimeField(_("Balayage terminé à"), null=True, blank=True)
|
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:
|
class Meta:
|
||||||
ordering = ['-created', ]
|
ordering = ['-created', ]
|
||||||
verbose_name = _("Session d'expérience")
|
verbose_name = _("Session d'expérience")
|
||||||
|
|||||||
@@ -9,19 +9,26 @@
|
|||||||
<a href="/" class="w3-bar-item w3-btn w3-hover-opacity"><i class="fa-solid fa-house w3-text-orange w3-xlarge"></i> {% trans "Retour accueil" %}</a>
|
<a href="/" class="w3-bar-item w3-btn w3-hover-opacity"><i class="fa-solid fa-house w3-text-orange w3-xlarge"></i> {% trans "Retour accueil" %}</a>
|
||||||
<form method="post" class="w3-bar-item" action="">
|
<form method="post" class="w3-bar-item" action="">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<select id="_sid" name="_sid" class="w3-select w3-margin-bottom" onchange="this.form.submit()">
|
<select id="_sid" name="_sid" class="w3-select w3-margin-bottom" onchange="this.form.submit()" title="{% trans "Ensemble d'expériences" %}">
|
||||||
<option value="0">---- {% trans "Session d'expérience" %}</option>
|
<option value="0">---- {% trans "Session" %}</option>
|
||||||
{% for s in sessions %}
|
{% for s in sessions %}
|
||||||
<option value="{{ s.id }}" {% if s.id == cursid %}selected{% endif %}>{{ s.name }}</option>
|
<option value="{{ s.id }}" {% if s.id == current_session.id %}selected{% endif %}>{{ s.name }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
{% if cursid %}
|
<div class="w3-margin-left w3-margin-bottom">
|
||||||
{% multiwell_cards cursid experiments %}
|
{% for ss in experiments %}
|
||||||
|
<input class="" type="radio" name="_expid" value="{{ ss.id }}" {% if ss.id == current_experiment.id %}checked{% endif %} onchange="this.form.submit()" >
|
||||||
|
<label>{{ ss.multiwell }}</label>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% if current_session.id and current_experiment %}
|
||||||
|
{% multiwell_cards current_session.id current_experiment %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</form>
|
</form>
|
||||||
{% if cursid %}
|
{% if current_session.id %}
|
||||||
<a href="#" class="w3-bar-item w3-btn w3-hover-opacity" onclick="download_all_images()">
|
<a href="#" class="w3-bar-item w3-btn w3-hover-opacity" onclick="download_all_images()">
|
||||||
<i class="fa-solid fa-file-export w3-text-red w3-xlarge"></i> {% trans "Exporter l'ensemble des images" %}
|
<i class="fa-solid fa-file-export w3-text-red w3-xlarge"></i> {% trans "Exporter l'ensemble des images" %}<br>
|
||||||
|
<span class="w3-margin-left">{{ export_destination }}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -95,14 +102,15 @@
|
|||||||
action: 'export_images',
|
action: 'export_images',
|
||||||
sid: sid,
|
sid: sid,
|
||||||
})
|
})
|
||||||
}).then(res => {
|
})
|
||||||
console.log(res);
|
.then(response => response.json())
|
||||||
|
.then(res => {
|
||||||
}).catch(error => {
|
alert(res.msg);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
console.error('Erreur:', error);
|
console.error('Erreur:', error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setGridColumns(columns);
|
setGridColumns(columns);
|
||||||
if (uuid_btn)
|
if (uuid_btn)
|
||||||
handleMultiwell(uuid_btn);
|
handleMultiwell(uuid_btn);
|
||||||
|
|||||||
@@ -11,18 +11,26 @@
|
|||||||
<form method="post" class="w3-bar-item" action="">
|
<form method="post" class="w3-bar-item" action="">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<select id="_sid" name="_sid" class="w3-select w3-margin-bottom" onchange="this.form.submit()">
|
<select id="_sid" name="_sid" class="w3-select w3-margin-bottom" onchange="this.form.submit()">
|
||||||
<option value="0">---- {% trans "Session d'expériences" %}</option>
|
<option value="0">---- {% trans "Session" %}</option>
|
||||||
{% for s in sessions %}
|
{% for s in sessions %}
|
||||||
<option value="{{ s.id }}" {% if s.id == cursid %}selected{% endif %}>{{ s.name }}</option>
|
<option value="{{ s.id }}" {% if s.id == current_session.id %}selected{% endif %}>{{ s.name }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
{% if cursid %}
|
<div class="w3-margin-left w3-margin-bottom">
|
||||||
{% multiwell_cards cursid experiments %}
|
{% for ss in experiments %}
|
||||||
|
<input class="" type="radio" name="_expid" value="{{ ss.id }}" {% if ss.id == current_experiment.id %}checked{% endif %} onchange="this.form.submit()" >
|
||||||
|
<label>{{ ss.multiwell }}</label>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if current_session.id and current_experiment %}
|
||||||
|
{% multiwell_cards current_session.id current_experiment %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</form>
|
</form>
|
||||||
{% if cursid %}
|
{% if current_session.id %}
|
||||||
<a href="#" class="w3-bar-item w3-btn w3-hover-opacity" onclick="download_all_videos()">
|
<a href="#" class="w3-bar-item w3-btn w3-hover-opacity" onclick="download_all_videos()">
|
||||||
<i class="fa-solid fa-file-export w3-text-red w3-xlarge"></i> {% trans "Exporter l'ensemble des vidéos" %}
|
<i class="fa-solid fa-file-export w3-text-red w3-xlarge"></i> {% trans "Exporter l'ensemble des vidéos" %}<br>
|
||||||
|
<span class="w3-margin-left">{{ export_destination }}</span>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -80,9 +88,12 @@
|
|||||||
action: 'export_videos',
|
action: 'export_videos',
|
||||||
sid: sid,
|
sid: sid,
|
||||||
})
|
})
|
||||||
}).then(res => {
|
})
|
||||||
console.log(res);
|
.then(response => response.json())
|
||||||
}).catch(error => {
|
.then(res => {
|
||||||
|
alert(res.msg);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
console.error('Erreur:', error);
|
console.error('Erreur:', error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,22 +5,19 @@ from django.utils.html import mark_safe
|
|||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
@register.simple_tag
|
@register.simple_tag
|
||||||
def multiwell_cards(sid, experiments):
|
def multiwell_cards(sid, experiment):
|
||||||
multiwells = []
|
multiwells = []
|
||||||
for obs in experiments:
|
row_def = experiment.multiwell.row_def.split(',')
|
||||||
row_def = obs.multiwell.row_def.split(',')
|
multiwells.append(
|
||||||
multiwells.append(
|
f'''
|
||||||
f'''
|
<div class="w3-center w3-sand">{experiment.title}</div>
|
||||||
<div class="w3-center w3-sand">{obs.title}</div>
|
<div class="w3-border multiwell_cards">
|
||||||
<div class="w3-border multiwell_cards">
|
''')
|
||||||
''')
|
for row in range(experiment.multiwell.rows):
|
||||||
for row in range(obs.multiwell.rows):
|
for col in range(experiment.multiwell.cols):
|
||||||
for col in range(obs.multiwell.cols):
|
btn = f'{row_def[row]}{col+1}'
|
||||||
btn = f'{row_def[row]}{col+1}'
|
uuid = f'{sid}-{experiment.multiwell.position}-{btn}'
|
||||||
uuid = f'{sid}-{obs.multiwell.position}-{btn}'
|
multiwells.append(f"""<button id="btn-{uuid}" name="_multiwell" class="multiwell w3-button" value="{uuid}" onclick="this.form.submit()">{btn}</button>""")
|
||||||
multiwells.append(f"""<button id="btn-{uuid}" name="_multiwell" class="multiwell w3-button" value="{uuid}" onclick="this.form.submit()">{btn}</button>""")
|
multiwells.append('''</div>''')
|
||||||
multiwells.append('''</div>''')
|
|
||||||
|
|
||||||
return mark_safe("\n".join(multiwells))
|
return mark_safe("\n".join(multiwells))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -35,14 +35,13 @@ def stats_view(request):
|
|||||||
def global_context(request, **ctx):
|
def global_context(request, **ctx):
|
||||||
default_multiwell = models.MultiWell.objects.filter(default=True).first()
|
default_multiwell = models.MultiWell.objects.filter(default=True).first()
|
||||||
conf = ScannerConstants().get()
|
conf = ScannerConstants().get()
|
||||||
|
|
||||||
print(conf, type(conf))
|
|
||||||
return dict(
|
return dict(
|
||||||
app_title=settings.APP_TITLE,
|
app_title=settings.APP_TITLE,
|
||||||
app_sub_title=settings.APP_SUB_TITLE,
|
app_sub_title=settings.APP_SUB_TITLE,
|
||||||
domain_server=settings.DOMAIN_SERVER,
|
domain_server=settings.DOMAIN_SERVER,
|
||||||
conf=conf,
|
conf=conf,
|
||||||
default_position = default_multiwell.position or 'HD',
|
default_position = default_multiwell.position or 'HD',
|
||||||
|
export_destination=settings.EXPORT_DESTINATIONS,
|
||||||
**ctx
|
**ctx
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -97,6 +96,17 @@ def calibration_view(request):
|
|||||||
return render(request, "scanner/calibration.html", context=global_context(request, **ctx))
|
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
|
## images
|
||||||
def get_images(uuid):
|
def get_images(uuid):
|
||||||
oldest, latest, n, images = 0, 0, 0, []
|
oldest, latest, n, images = 0, 0, 0, []
|
||||||
@@ -124,17 +134,21 @@ def get_images(uuid):
|
|||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def images_view(request):
|
def images_view(request):
|
||||||
cursid, duration, uuid, images = 0, 0, "", []
|
cursid, expid, duration, uuid, images = 0, None, 0, "", []
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
cursid = request.POST.get('_sid')
|
cursid = request.POST.get('_sid')
|
||||||
|
expid = request.POST.get('_expid')
|
||||||
uuid = request.POST.get('_multiwell')
|
uuid = request.POST.get('_multiwell')
|
||||||
duration, images = get_images(uuid)
|
duration, images = get_images(uuid)
|
||||||
|
|
||||||
|
current_session = models.Session.get_session(cursid)
|
||||||
|
experiments, current_experiment = get_not_active_experiments(current_session, expid)
|
||||||
ctx = dict(
|
ctx = dict(
|
||||||
choice_title=_("Gestionnaire d'images"),
|
choice_title=_("Gestionnaire d'images"),
|
||||||
sessions=models.Session.objects.filter(active=False).all(),
|
sessions=models.Session.objects.filter(active=False).all(),
|
||||||
experiments=models.SessionExperiment.experiment_by_session(cursid, active=False),
|
experiments=experiments or [],
|
||||||
cursid=int(cursid),
|
current_session=current_session,
|
||||||
|
current_experiment=current_experiment,
|
||||||
images=images,
|
images=images,
|
||||||
uuid=uuid,
|
uuid=uuid,
|
||||||
duration=duration,
|
duration=duration,
|
||||||
@@ -164,20 +178,24 @@ def get_video(uuid):
|
|||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def replay_view(request):
|
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':
|
if request.method == 'POST':
|
||||||
cursid = request.POST.get('_sid')
|
cursid = request.POST.get('_sid')
|
||||||
|
expid = request.POST.get('_expid')
|
||||||
uuid = request.POST.get('_multiwell')
|
uuid = request.POST.get('_multiwell')
|
||||||
if uuid:
|
if uuid:
|
||||||
oldest, latest, image = get_video(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(
|
ctx = dict(
|
||||||
choice_title=_("Gestionnaire de vidéos"),
|
choice_title=_("Gestionnaire de vidéos"),
|
||||||
ws_route=settings.REPLAY_WEBSOCKET_ROUTE,
|
ws_route=settings.REPLAY_WEBSOCKET_ROUTE,
|
||||||
sessions=models.Session.objects.filter(active=False).all(),
|
sessions=models.Session.objects.filter(active=False).all(),
|
||||||
experiments=models.SessionExperiment.experiment_by_session(cursid, active=False),
|
experiments=experiments or [],
|
||||||
cursid=int(cursid),
|
current_session=current_session,
|
||||||
|
current_experiment=current_experiment,
|
||||||
image=image,
|
image=image,
|
||||||
uuid=uuid,
|
uuid=uuid,
|
||||||
oldest=oldest,
|
oldest=oldest,
|
||||||
@@ -193,12 +211,12 @@ def export_api(request):
|
|||||||
action = data.get("action")
|
action = data.get("action")
|
||||||
|
|
||||||
if action == 'export_images':
|
if action == 'export_images':
|
||||||
job_zip = export_all_images(session_id)
|
export_all_images(session_id)
|
||||||
return JsonResponse({"state": True, "tasks": job_zip})
|
return JsonResponse({"success": True, "msg": str(_("Images téléchargées"))})
|
||||||
elif action == 'export_videos':
|
elif action == 'export_videos':
|
||||||
job_mp4 = export_all_videos(session_id)
|
export_all_videos(session_id)
|
||||||
return JsonResponse({"state": True, "tasks": job_mp4})
|
return JsonResponse({"success": True, "msg": str(_("Vidéos téléchargées"))})
|
||||||
else:
|
else:
|
||||||
return JsonResponse({"state": False})
|
return JsonResponse({"success": False, "msg": str(_("Erreur d'exportation"))})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user