planarian
This commit is contained in:
@@ -8,7 +8,8 @@ from .models import ExperimentConfig
|
||||
@admin.register(ExperimentConfig)
|
||||
class ExperimentConfigAdmin(admin.ModelAdmin):
|
||||
"""Admin Django pour les configurations d'expérience."""
|
||||
readonly_fields = ('experiment', )
|
||||
#readonly_fields = ('experiment', )
|
||||
readonly_fields = ("identifier", 'px_per_mm', 'fps', 'well_radius_mm',)
|
||||
list_display = ("experiment", "well", "px_per_mm", "fps",
|
||||
"thresh_immobile", "thresh_mobile",
|
||||
"photo_mode", "chemo_strength", "created_at")
|
||||
@@ -18,19 +19,23 @@ class ExperimentConfigAdmin(admin.ModelAdmin):
|
||||
|
||||
fieldsets = (
|
||||
(_("Identification"), {
|
||||
"fields": ("experiment", "well", "description"),
|
||||
}),
|
||||
"fields": ("identifier", "experiment", "well", "description"),
|
||||
}),
|
||||
(_("Calibration optique"), {
|
||||
"fields": ("px_per_mm", "fps", "well_radius_mm"),
|
||||
"classes": ("collapse",),
|
||||
}),
|
||||
(_("Seuils de mobilité EthoVision"), {
|
||||
"fields": ("thresh_immobile", "thresh_mobile"),
|
||||
"fields": ("thresh_immobile", "thresh_mobile"),
|
||||
"classes": ("collapse",),
|
||||
}),
|
||||
(_("Tracker"), {
|
||||
"fields": ("tube_axis", "min_area_px", "planarian_count"),
|
||||
"classes": ("collapse",),
|
||||
}),
|
||||
(_("Thigmotactisme"), {
|
||||
"fields": ("thigmotaxis_wall_dist_mm",),
|
||||
"classes": ("collapse",),
|
||||
}),
|
||||
(_("Phototactisme"), {
|
||||
"fields": ("photo_mode", "photo_strength", "photo_x", "photo_y"),
|
||||
|
||||
@@ -10,6 +10,10 @@ from .models import ExperimentConfig
|
||||
|
||||
class ExperimentConfigForm(forms.ModelForm):
|
||||
"""Formulaire de saisie/modification d'un ExperimentConfig."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['identifier'].disabled = True
|
||||
|
||||
class Meta:
|
||||
model = ExperimentConfig
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
from django.db import models
|
||||
from django.dispatch import receiver
|
||||
from django.db.models.signals import post_save
|
||||
from django.conf import settings
|
||||
#from django.conf import settings
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.contrib.auth.models import User
|
||||
from scanner.models import Experiment, Well, WellPosition
|
||||
@@ -17,19 +18,10 @@ class ExperimentConfig(models.Model):
|
||||
author = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="Auteur", null=True, blank=True)
|
||||
|
||||
# --- Identification ---
|
||||
idendifier = models.CharField(
|
||||
max_length=100,
|
||||
verbose_name=_("Identifiant d'expérience"),
|
||||
help_text=_("Ex : exp_2026_04_25_ctrl"),
|
||||
)
|
||||
|
||||
experiment = models.ForeignKey(Experiment, on_delete=models.CASCADE, related_name="experiment_well" , null=True, blank=True)
|
||||
identifier = models.CharField( max_length=100, verbose_name=_("Identifiant d'expérience"), help_text=_("session_1-HD-2026-04-27"), )
|
||||
experiment = models.ForeignKey(Experiment, verbose_name="Expérience", on_delete=models.CASCADE, related_name="experiment_well" , null=True, blank=True)
|
||||
well = models.ForeignKey(Well, verbose_name="Puit", on_delete=models.CASCADE, related_name="well_experiment", null=True, blank=True )
|
||||
|
||||
description = models.TextField(
|
||||
blank=True,
|
||||
verbose_name=_("Description"),
|
||||
)
|
||||
description = models.TextField( blank=True, verbose_name=_("Description"), )
|
||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Créé le"))
|
||||
|
||||
# --- Calibration optique ---
|
||||
@@ -42,10 +34,12 @@ class ExperimentConfig(models.Model):
|
||||
fps = models.FloatField(
|
||||
default=5.0,
|
||||
verbose_name=_("FPS de capture"),
|
||||
help_text=_("Image de capture en img/s"),
|
||||
)
|
||||
well_radius_mm = models.FloatField(
|
||||
default=8.0,
|
||||
verbose_name=_("Rayon du puits (mm)"),
|
||||
verbose_name=_("Rayon du puits"),
|
||||
help_text=_("En mm"),
|
||||
)
|
||||
|
||||
# --- Seuils de mobilité EthoVision ---
|
||||
@@ -109,7 +103,7 @@ class ExperimentConfig(models.Model):
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Configuration expérience")
|
||||
verbose_name_plural = _("Configurations expériences")
|
||||
verbose_name_plural = _("Configuration des expériences")
|
||||
unique_together = ("experiment", "well")
|
||||
ordering = ["-created_at"]
|
||||
|
||||
@@ -145,21 +139,25 @@ class ExperimentConfig(models.Model):
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
session = self.get_session()
|
||||
position = self.experiment.multiwell.position
|
||||
dte = self.experiment.multiwell.finished.isoformat()
|
||||
self.idendifier = f'{session}-{position}-{self.well.name}-{dte}'
|
||||
dte = self.experiment.created.isoformat()[:19]
|
||||
self.identifier = f'{dte}-{session.id}-{self.experiment.id}-{self.experiment.multiwell.position}-{self.well.name}'
|
||||
|
||||
print(self.identifier)
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
|
||||
@receiver(post_save, sender=ExperimentConfig)
|
||||
def create_well_position(sender, instance, created, **kwargs):
|
||||
active_well = WellPosition.active_well(instance.multiwel, instance.well)
|
||||
instance.px_per_mm = active_well.px_per_mm
|
||||
instance.well_radius_mm = instance.experiment.multiwell.diameter / 2
|
||||
conf = ScannerConstants().get()
|
||||
instance.fps = conf.video_frame_rate
|
||||
instance.save()
|
||||
|
||||
if created:
|
||||
active_well = WellPosition.active_well(instance.experiment.multiwell, instance.well)
|
||||
instance.px_per_mm = active_well.px_per_mm
|
||||
instance.well_radius_mm = instance.experiment.multiwell.diameter / 2
|
||||
conf = ScannerConstants().get()
|
||||
instance.fps = conf.video_frame_rate
|
||||
instance.save()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
{% extends "scanner/base.html" %}
|
||||
{% load i18n home_tags scanner_tags %}
|
||||
|
||||
{% block columns %}{% endblock %}
|
||||
|
||||
{% block sidebar_list %}
|
||||
<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="">
|
||||
{% csrf_token %}
|
||||
<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" %}</option>
|
||||
{% for s in sessions %}
|
||||
<option value="{{ s.id }}" {% if s.id == current_session.id %}selected{% endif %}>{{ s.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<div class="w3-margin-left w3-margin-bottom">
|
||||
{% 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 %}
|
||||
</form>
|
||||
{% if current_session.id %}
|
||||
<a href="{% url 'scanner:main' %}" 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>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block js_footer %}
|
||||
{{ block.super }}
|
||||
<script>
|
||||
// Auto-dismiss messages after 5 seconds
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const messages = document.querySelectorAll('.alert');
|
||||
messages.forEach(function(message) {
|
||||
setTimeout(function() {
|
||||
// Fade out effect
|
||||
message.style.transition = 'opacity 0.5s ease-out';
|
||||
message.style.opacity = '0';
|
||||
|
||||
// Remove from DOM after fade
|
||||
setTimeout(function() {
|
||||
message.remove();
|
||||
}, 500);
|
||||
}, 5000); // 5 seconds
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,9 +1,9 @@
|
||||
{% extends "scanner/base.html" %}
|
||||
{% extends "planarian/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="w3-container w3-padding-32" style="max-width:960px; margin:auto;">
|
||||
<div class="w3-container w3-padding-small" style="max-width:960px; margin:auto;">
|
||||
|
||||
<!-- En-tête de la page -->
|
||||
<div class="w3-panel w3-teal w3-round-large w3-padding-16" style="margin-bottom:2rem;">
|
||||
@@ -45,19 +45,33 @@
|
||||
<!-- ============================================================
|
||||
Section 1 : Identification
|
||||
============================================================ -->
|
||||
<div class="w3-card-4 w3-round-large w3-margin-bottom">
|
||||
<header class="w3-container w3-teal w3-round-top-large">
|
||||
<div class="w3-card-4 w3-round-large w3-margin-bottom w3-border w3-round w3-round-large">
|
||||
<header class="w3-container w3-teal w3-round w3-round-large">
|
||||
<h3 class="w3-text-white">{% trans "Identification" %}</h3>
|
||||
</header>
|
||||
<div class="w3-container w3-padding-24">
|
||||
<div class="w3-row-padding">
|
||||
<div class="w3-container w3-padding">
|
||||
<div class="w3-row w3-row-padding">
|
||||
<!-- identifier -->
|
||||
<div class="w3-half">
|
||||
<label class="w3-text-teal"><b>{{ form.identifier.label }}</b></label>
|
||||
{{ form.identifier }}
|
||||
{% if form.identifier.errors %}
|
||||
<span class="w3-text-red w3-small">{{ form.identifier.errors|join:", " }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- author -->
|
||||
<div class="w3-half w3-margin-bottom">
|
||||
<label class="w3-text-teal"><b>{{ form.author.label }}</b></label>
|
||||
{{ form.author }}
|
||||
{% if form.author.errors %}
|
||||
<span class="w3-text-red w3-small">{{ form.author.errors|join:", " }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Experiment -->
|
||||
<div class="w3-col m6 s12 w3-margin-bottom">
|
||||
<div class="w3-half w3-margin-bottom">
|
||||
<label class="w3-text-teal"><b>{{ form.experiment.label }}</b></label>
|
||||
{% if form.experiment.help_text %}
|
||||
<p class="w3-small w3-text-grey" style="margin:0 0 4px;">{{ form.experiment.help_text }}</p>
|
||||
{% endif %}
|
||||
{{ form.experiment }}
|
||||
{% if form.experiment.errors %}
|
||||
<span class="w3-text-red w3-small">{{ form.experiment.errors|join:", " }}</span>
|
||||
@@ -65,11 +79,8 @@
|
||||
</div>
|
||||
|
||||
<!-- Well -->
|
||||
<div class="w3-col m6 s12 w3-margin-bottom">
|
||||
<div class="w3-half w3-margin-bottom">
|
||||
<label class="w3-text-teal"><b>{{ form.well.label }}</b></label>
|
||||
{% if form.well.help_text %}
|
||||
<p class="w3-small w3-text-grey" style="margin:0 0 4px;">{{ form.well.help_text }}</p>
|
||||
{% endif %}
|
||||
{{ form.well }}
|
||||
{% if form.well.errors %}
|
||||
<span class="w3-text-red w3-small">{{ form.well.errors|join:", " }}</span>
|
||||
@@ -79,7 +90,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<div class="w3-margin-bottom">
|
||||
<div class="w3-margin-bottom w3-padding">
|
||||
<label class="w3-text-teal"><b>{{ form.description.label }}</b></label>
|
||||
{{ form.description }}
|
||||
{% if form.description.errors %}
|
||||
@@ -93,12 +104,12 @@
|
||||
<!-- ============================================================
|
||||
Section 2 : Calibration optique
|
||||
============================================================ -->
|
||||
<div class="w3-card-4 w3-round-large w3-margin-bottom">
|
||||
<header class="w3-container w3-blue-grey w3-round-top-large">
|
||||
<div class="w3-card-4 w3-round-large w3-margin-bottom w3-border w3-round w3-round-large">
|
||||
<header class="w3-container w3-blue-grey w3-round w3-round-top-large">
|
||||
<h3 class="w3-text-white">{% trans "Calibration optique" %}</h3>
|
||||
</header>
|
||||
<div class="w3-container w3-padding-24">
|
||||
<div class="w3-row-padding">
|
||||
<div class="w3-row w3-row-padding">
|
||||
|
||||
<div class="w3-col m4 s12 w3-margin-bottom">
|
||||
<label class="w3-text-blue-grey"><b>{{ form.px_per_mm.label }}</b></label>
|
||||
@@ -113,6 +124,9 @@
|
||||
|
||||
<div class="w3-col m4 s12 w3-margin-bottom">
|
||||
<label class="w3-text-blue-grey"><b>{{ form.fps.label }}</b></label>
|
||||
{% if form.fps.help_text %}
|
||||
<p class="w3-small w3-text-grey" style="margin:0 0 4px;">{{ form.fps.help_text }}</p>
|
||||
{% endif %}
|
||||
{{ form.fps }}
|
||||
{% if form.fps.errors %}
|
||||
<span class="w3-text-red w3-small">{{ form.fps.errors|join:", " }}</span>
|
||||
@@ -121,6 +135,9 @@
|
||||
|
||||
<div class="w3-col m4 s12 w3-margin-bottom">
|
||||
<label class="w3-text-blue-grey"><b>{{ form.well_radius_mm.label }}</b></label>
|
||||
{% if form.well_radius_mm.help_text %}
|
||||
<p class="w3-small w3-text-grey" style="margin:0 0 4px;">{{ form.well_radius_mm.help_text }}</p>
|
||||
{% endif %}
|
||||
{{ form.well_radius_mm }}
|
||||
{% if form.well_radius_mm.errors %}
|
||||
<span class="w3-text-red w3-small">{{ form.well_radius_mm.errors|join:", " }}</span>
|
||||
@@ -130,12 +147,48 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ============================================================
|
||||
Section 4 : Tracker
|
||||
============================================================ -->
|
||||
<div class="w3-card-4 w3-round-large w3-margin-bottom w3-border w3-round w3-round-large">
|
||||
<header class="w3-container w3-dark-grey w3-round w3-round-top-large">
|
||||
<h3 class="w3-text-white">{% trans "Tracker" %}</h3>
|
||||
</header>
|
||||
<div class="w3-container w3-padding-24">
|
||||
<div class="w3-row-padding">
|
||||
|
||||
<div class="w3-col m4 s12 w3-margin-bottom">
|
||||
<label class="w3-text-grey"><b>{{ form.tube_axis.label }}</b></label>
|
||||
{{ form.tube_axis }}
|
||||
{% if form.tube_axis.errors %}
|
||||
<span class="w3-text-red w3-small">{{ form.tube_axis.errors|join:", " }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="w3-col m4 s12 w3-margin-bottom">
|
||||
<label class="w3-text-grey"><b>{{ form.min_area_px.label }}</b></label>
|
||||
{{ form.min_area_px }}
|
||||
{% if form.min_area_px.errors %}
|
||||
<span class="w3-text-red w3-small">{{ form.min_area_px.errors|join:", " }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="w3-col m4 s12 w3-margin-bottom">
|
||||
<label class="w3-text-grey"><b>{{ form.planarian_count.label }}</b></label>
|
||||
{{ form.planarian_count }}
|
||||
{% if form.planarian_count.errors %}
|
||||
<span class="w3-text-red w3-small">{{ form.planarian_count.errors|join:", " }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ============================================================
|
||||
Section 3 : Seuils de mobilité EthoVision
|
||||
============================================================ -->
|
||||
<div class="w3-card-4 w3-round-large w3-margin-bottom">
|
||||
<header class="w3-container w3-indigo w3-round-top-large">
|
||||
<div class="w3-card-4 w3-round-large w3-margin-bottom w3-border w3-round w3-round-large">
|
||||
<header class="w3-container w3-indigo w3-round w3-round-top-large">
|
||||
<h3 class="w3-text-white">{% trans "Seuils de mobilité EthoVision XT" %}</h3>
|
||||
</header>
|
||||
<div class="w3-container w3-padding-24">
|
||||
@@ -177,49 +230,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============================================================
|
||||
Section 4 : Tracker
|
||||
============================================================ -->
|
||||
<div class="w3-card-4 w3-round-large w3-margin-bottom">
|
||||
<header class="w3-container w3-dark-grey w3-round-top-large">
|
||||
<h3 class="w3-text-white">{% trans "Tracker" %}</h3>
|
||||
</header>
|
||||
<div class="w3-container w3-padding-24">
|
||||
<div class="w3-row-padding">
|
||||
|
||||
<div class="w3-col m4 s12 w3-margin-bottom">
|
||||
<label class="w3-text-dark-grey"><b>{{ form.tube_axis.label }}</b></label>
|
||||
{{ form.tube_axis }}
|
||||
{% if form.tube_axis.errors %}
|
||||
<span class="w3-text-red w3-small">{{ form.tube_axis.errors|join:", " }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="w3-col m4 s12 w3-margin-bottom">
|
||||
<label class="w3-text-dark-grey"><b>{{ form.min_area_px.label }}</b></label>
|
||||
{{ form.min_area_px }}
|
||||
{% if form.min_area_px.errors %}
|
||||
<span class="w3-text-red w3-small">{{ form.min_area_px.errors|join:", " }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="w3-col m4 s12 w3-margin-bottom">
|
||||
<label class="w3-text-dark-grey"><b>{{ form.planarian_count.label }}</b></label>
|
||||
{{ form.planarian_count }}
|
||||
{% if form.planarian_count.errors %}
|
||||
<span class="w3-text-red w3-small">{{ form.planarian_count.errors|join:", " }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============================================================
|
||||
Section 5 : Comportements (accordéon W3.CSS)
|
||||
============================================================ -->
|
||||
<div class="w3-card-4 w3-round-large w3-margin-bottom">
|
||||
<header class="w3-container w3-teal w3-round-top-large">
|
||||
<div class="w3-card-4 w3-round-large w3-margin-bottom w3-border w3-round w3-round-large">
|
||||
<header class="w3-container w3-teal w3-round w3-round-top-large">
|
||||
<h3 class="w3-text-white">{% trans "Comportements" %}</h3>
|
||||
</header>
|
||||
|
||||
@@ -252,8 +267,7 @@
|
||||
|
||||
<!-- --- Phototactisme --- -->
|
||||
<div class="w3-container w3-padding-16 w3-border-bottom">
|
||||
<button type="button" class="w3-button w3-block w3-left-align w3-hover-light-grey"
|
||||
onclick="toggleSection('photo')">
|
||||
<button type="button" class="w3-button w3-block w3-left-align w3-hover-light-grey" onclick="toggleSection('photo')">
|
||||
<span class="w3-large">💡</span>
|
||||
<b class="w3-margin-left">{% trans "Phototactisme" %}</b>
|
||||
<span class="w3-small w3-text-grey w3-margin-left">
|
||||
@@ -266,6 +280,7 @@
|
||||
|
||||
<div class="w3-col m6 s12 w3-margin-bottom">
|
||||
<label class="w3-text-teal"><b>{{ form.photo_mode.label }}</b></label>
|
||||
<p class="w3-small w3-text-grey" style="margin:0 0 4px;"> </p>
|
||||
{{ form.photo_mode }}
|
||||
{% if form.photo_mode.errors %}
|
||||
<span class="w3-text-red w3-small">{{ form.photo_mode.errors|join:", " }}</span>
|
||||
@@ -283,6 +298,7 @@
|
||||
|
||||
<div class="w3-col m6 s12 w3-margin-bottom">
|
||||
<label class="w3-text-teal"><b>{{ form.photo_x.label }}</b></label>
|
||||
<p class="w3-small w3-text-grey" style="margin:0 0 4px;"> </p>
|
||||
{{ form.photo_x }}
|
||||
{% if form.photo_x.errors %}
|
||||
<span class="w3-text-red w3-small">{{ form.photo_x.errors|join:", " }}</span>
|
||||
@@ -291,6 +307,7 @@
|
||||
|
||||
<div class="w3-col m6 s12 w3-margin-bottom">
|
||||
<label class="w3-text-teal"><b>{{ form.photo_y.label }}</b></label>
|
||||
<p class="w3-small w3-text-grey" style="margin:0 0 4px;"> </p>
|
||||
{{ form.photo_y }}
|
||||
{% if form.photo_y.errors %}
|
||||
<span class="w3-text-red w3-small">{{ form.photo_y.errors|join:", " }}</span>
|
||||
@@ -326,6 +343,7 @@
|
||||
|
||||
<div class="w3-col m4 s12 w3-margin-bottom">
|
||||
<label class="w3-text-teal"><b>{{ form.chemo_x.label }}</b></label>
|
||||
<p class="w3-small w3-text-grey" style="margin:0 0 4px;">{% trans "Mini = 0, maxi = 1" %}</p>
|
||||
{{ form.chemo_x }}
|
||||
{% if form.chemo_x.errors %}
|
||||
<span class="w3-text-red w3-small">{{ form.chemo_x.errors|join:", " }}</span>
|
||||
@@ -334,6 +352,7 @@
|
||||
|
||||
<div class="w3-col m4 s12 w3-margin-bottom">
|
||||
<label class="w3-text-teal"><b>{{ form.chemo_y.label }}</b></label>
|
||||
<p class="w3-small w3-text-grey" style="margin:0 0 4px;">{% trans "Mini = 0, maxi = 1" %}</p>
|
||||
{{ form.chemo_y }}
|
||||
{% if form.chemo_y.errors %}
|
||||
<span class="w3-text-red w3-small">{{ form.chemo_y.errors|join:", " }}</span>
|
||||
@@ -397,7 +416,7 @@
|
||||
|
||||
<div class="w3-col m8 s12 w3-margin-bottom">
|
||||
<button type="submit" class="w3-button w3-teal w3-round w3-large w3-padding-large">
|
||||
{% if object %}
|
||||
{% if is_update %}
|
||||
💾 {% trans "Enregistrer les modifications" %}
|
||||
{% else %}
|
||||
➕ {% trans "Créer la configuration" %}
|
||||
@@ -405,7 +424,7 @@
|
||||
</button>
|
||||
<a href="{% url 'planarian:experiment-list' %}"
|
||||
class="w3-button w3-light-grey w3-round w3-large w3-padding-large w3-margin-left">
|
||||
✖ {% trans "Annuler" %}
|
||||
✖ {% trans "Retour" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% extends "planarian/base.html" %}
|
||||
{% load i18n home_tags scanner_tags %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="w3-container w3-padding-32" style="max-width:1200px; margin:auto;">
|
||||
|
||||
<!-- En-tête -->
|
||||
@@ -36,7 +35,6 @@
|
||||
|
||||
<!-- Barre d'outils : recherche + import -->
|
||||
<div class="w3-row-padding w3-margin-bottom">
|
||||
|
||||
<!-- Recherche côté client -->
|
||||
<div class="w3-col m7 s12 w3-margin-bottom">
|
||||
<div class="w3-border w3-round" style="display:flex; align-items:center; background:#fafafa;">
|
||||
@@ -48,7 +46,6 @@
|
||||
oninput="filterTable(this.value)">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Boutons import / export -->
|
||||
<div class="w3-col m5 s12 w3-right-align w3-margin-bottom">
|
||||
<a href="{% url 'planarian:import-params' %}"
|
||||
@@ -60,19 +57,15 @@
|
||||
📥 {% trans "Exporter CSV" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ============================================================
|
||||
Tableau des configurations
|
||||
============================================================ -->
|
||||
{% if configs %}
|
||||
|
||||
<!-- Compteur -->
|
||||
<p class="w3-text-grey w3-small" id="row-count">
|
||||
{{ configs|length }} {% trans "configuration(s)" %}
|
||||
</p>
|
||||
|
||||
<div class="w3-responsive w3-card-4 w3-round-large">
|
||||
<table class="w3-table w3-striped w3-hoverable w3-bordered" id="config-table">
|
||||
<thead>
|
||||
@@ -98,26 +91,21 @@
|
||||
<tbody id="config-tbody">
|
||||
{% for cfg in configs %}
|
||||
<tr class="config-row">
|
||||
|
||||
<!-- Expérience -->
|
||||
<td>
|
||||
<b>{{ cfg.experiment }}</b>
|
||||
<b><span class="w3-text-grey">{{ cfg.experiment }}</span></b>
|
||||
{% if cfg.description %}
|
||||
<br><span class="w3-small w3-text-grey">{{ cfg.description|truncatechars:40 }}</span>
|
||||
<br><span class="w3-small w3-text-blue-grey">{{ cfg.description|truncatechars:40 }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
|
||||
<!-- Puits -->
|
||||
<td>
|
||||
<span class="w3-tag w3-teal w3-round">{{ cfg.well }}</span>
|
||||
</td>
|
||||
|
||||
<!-- px/mm -->
|
||||
<td class="w3-hide-small">{{ cfg.px_per_mm }}</td>
|
||||
|
||||
<td class="w3-hide-small w3-text-grey">{{ cfg.px_per_mm }}</td>
|
||||
<!-- FPS -->
|
||||
<td class="w3-hide-small">{{ cfg.fps }}</td>
|
||||
|
||||
<td class="w3-hide-small w3-text-grey">{{ cfg.fps }}</td>
|
||||
<!-- Seuils de mobilité -->
|
||||
<td class="w3-hide-small">
|
||||
<span class="w3-tag w3-red w3-round w3-small"
|
||||
@@ -129,7 +117,6 @@
|
||||
< {{ cfg.thresh_mobile }}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<!-- Comportements actifs -->
|
||||
<td class="w3-hide-small">
|
||||
{% if cfg.thigmotaxis_wall_dist_mm > 0 %}
|
||||
@@ -166,9 +153,7 @@
|
||||
<button type="button"
|
||||
class="w3-button w3-small w3-red w3-round"
|
||||
title="{% trans 'Supprimer' %}"
|
||||
onclick="confirmDelete('{{ cfg.pk }}', '{{ cfg.experiment }}', '{{ cfg.well }}')">
|
||||
🗑
|
||||
</button>
|
||||
onclick="confirmDelete('{{ cfg.pk }}', '{{ cfg.experiment }}', '{{ cfg.well }}')"> 🗑 </button>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@@ -181,7 +166,6 @@
|
||||
<div id="no-results" class="w3-panel w3-pale-yellow w3-round w3-margin-top" style="display:none;">
|
||||
<p>{% trans "Aucune configuration ne correspond à la recherche." %}</p>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<!-- Liste vide -->
|
||||
<div class="w3-panel w3-pale-blue w3-round-large w3-padding-32" style="text-align:center;">
|
||||
@@ -194,13 +178,9 @@
|
||||
class="w3-button w3-teal w3-round w3-large w3-margin-right">
|
||||
➕ {% trans "Nouvelle configuration" %}
|
||||
</a>
|
||||
<a href="{% url 'planarian:import-params' %}"
|
||||
class="w3-button w3-blue-grey w3-round w3-large">
|
||||
📂 {% trans "Importer CSV" %}
|
||||
</a>
|
||||
<a href="{% url 'planarian:import-params' %}" class="w3-button w3-blue-grey w3-round w3-large"> 📂 {% trans "Importer CSV" %} </a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div><!-- fin container -->
|
||||
|
||||
<!-- ============================================================
|
||||
@@ -216,9 +196,7 @@
|
||||
<form id="delete-form" method="post" action="">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="_method" value="delete">
|
||||
<button type="submit" class="w3-button w3-red w3-round w3-margin-right">
|
||||
🗑 {% trans "Supprimer" %}
|
||||
</button>
|
||||
<button type="submit" class="w3-button w3-red w3-round w3-margin-right"> 🗑 {% trans "Supprimer" %}</button>
|
||||
<button type="button" class="w3-button w3-light-grey w3-round"
|
||||
onclick="document.getElementById('delete-modal').style.display='none'">
|
||||
✖ {% trans "Annuler" %}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "planarian/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="w3-container w3-padding-32" style="max-width:760px; margin:auto;">
|
||||
<div class="w3-container w3-padding" style="max-width:760px; margin:auto;">
|
||||
|
||||
<!-- En-tête -->
|
||||
<div class="w3-panel w3-blue w3-round-large w3-padding-16 w3-margin-bottom">
|
||||
@@ -56,8 +56,8 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="w3-card-4 w3-round-large">
|
||||
<header class="w3-container w3-blue w3-round-top-large">
|
||||
<div class="w3-card-4 w3-border w3-round w3-round-large">
|
||||
<header class="w3-container w3-blue w3-round w3-round-top-large">
|
||||
<h3 class="w3-text-white">{% trans "Paramètres d'export" %}</h3>
|
||||
</header>
|
||||
<div class="w3-container w3-padding-24">
|
||||
@@ -108,7 +108,7 @@
|
||||
|
||||
<div class="w3-col m4 s12 w3-margin-bottom">
|
||||
<label class="w3-text-blue"><b>{{ form.planarian.label }}</b></label>
|
||||
<p class="w3-small w3-text-grey" style="margin:0 0 4px;">
|
||||
<p class="w3-small w3-text-light-grey" style="margin:0 0 4px;">
|
||||
{% trans "Index du planaire dans le puits (commence à 0)" %}
|
||||
</p>
|
||||
{{ form.planarian }}
|
||||
@@ -119,20 +119,12 @@
|
||||
|
||||
<div class="w3-col m8 s12 w3-margin-bottom">
|
||||
<label class="w3-text-blue"><b>{{ form.record_type.label }}</b></label>
|
||||
<p class="w3-small w3-text-grey" style="margin:0 0 4px;">
|
||||
<p class="w3-small w3-text-light-grey" style="margin:0 0 4px;">
|
||||
{% trans "Frame par frame : une ligne par image. Résumé : métriques agrégées de la session." %}
|
||||
</p>
|
||||
<!-- Radio buttons stylisés W3 -->
|
||||
<div class="w3-row-padding" style="margin-top:6px;">
|
||||
{% for radio in form.record_type %}
|
||||
<div class="w3-col m6 s12">
|
||||
<label class="w3-button w3-block w3-round w3-border
|
||||
{% if radio.data.value == form.record_type.value %}w3-blue w3-text-white{% else %}w3-white{% endif %}"
|
||||
style="text-align:center; margin-bottom:6px; cursor:pointer;">
|
||||
{{ radio.tag }} {{ radio.choice_label }}
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{{ form.record_type }}
|
||||
</div>
|
||||
{% if form.record_type.errors %}
|
||||
<span class="w3-text-red w3-small">{{ form.record_type.errors|join:", " }}</span>
|
||||
@@ -176,7 +168,7 @@
|
||||
<!-- Boutons -->
|
||||
<div class="w3-row-padding w3-margin-top">
|
||||
<div class="w3-col s12">
|
||||
<button type="submit" class="w3-button w3-blue w3-round w3-large w3-padding-large">
|
||||
<button type="submit" class="w3-button w3-blue w3-large w3-padding-large">
|
||||
📥 {% trans "Générer et télécharger le CSV" %}
|
||||
</button>
|
||||
<a href="{% url 'planarian:experiment-list' %}"
|
||||
@@ -189,8 +181,8 @@
|
||||
</form>
|
||||
|
||||
<!-- Rappel des colonnes exportées -->
|
||||
<div class="w3-card w3-round-large w3-margin-top">
|
||||
<header class="w3-container w3-blue-grey w3-round-top-large">
|
||||
<div class="w3-card w3-round-large w3-margin-top w3-border w3-round w3-round-large w3-margin-bottom">
|
||||
<header class="w3-container w3-blue-grey w3-round w3-round-top-large">
|
||||
<h4 class="w3-text-white" style="margin:8px 0;">
|
||||
{% trans "Colonnes du fichier CSV exporté" %}
|
||||
</h4>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "planarian/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="w3-container w3-padding-32" style="max-width:760px; margin:auto;">
|
||||
|
||||
<!-- En-tête -->
|
||||
@@ -45,8 +44,8 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="w3-card-4 w3-round-large w3-margin-bottom">
|
||||
<header class="w3-container w3-blue-grey w3-round-top-large">
|
||||
<div class="w3-card-4 w3-margin-bottom w3-border w3-round w3-round-large">
|
||||
<header class="w3-container w3-blue-grey w3-round w3-round-top-large">
|
||||
<h3 class="w3-text-white">{% trans "Fichier CSV à importer" %}</h3>
|
||||
</header>
|
||||
<div class="w3-container w3-padding-24">
|
||||
@@ -129,8 +128,8 @@
|
||||
<!-- ============================================================
|
||||
Format attendu du CSV
|
||||
============================================================ -->
|
||||
<div class="w3-card-4 w3-round-large">
|
||||
<header class="w3-container w3-teal w3-round-top-large">
|
||||
<div class="w3-card-4 w3-round-large w3-border w3-round w3-round-large">
|
||||
<header class="w3-container w3-teal w3-round w3-round-top-large">
|
||||
<h3 class="w3-text-white">{% trans "Format du fichier CSV" %}</h3>
|
||||
</header>
|
||||
<div class="w3-container w3-padding-24">
|
||||
@@ -191,24 +190,24 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td><code>well_radius_mm</code></td> <td>8.0</td> <td>{% trans "Rayon du puits (mm)" %}</td></tr>
|
||||
<tr><td><code>thresh_immobile</code></td> <td>0.2</td> <td>{% trans "Seuil Immobile EthoVision (mm/s)" %}</td></tr>
|
||||
<tr class="w3-grey"><td><code>thresh_immobile</code></td> <td>0.2</td> <td>{% trans "Seuil Immobile EthoVision (mm/s)" %}</td></tr>
|
||||
<tr><td><code>thresh_mobile</code></td> <td>1.5</td> <td>{% trans "Seuil Mobile EthoVision (mm/s)" %}</td></tr>
|
||||
<tr><td><code>tube_axis</code></td> <td>vertical</td> <td>{% trans "Axe du tube : vertical | horizontal" %}</td></tr>
|
||||
<tr class="w3-grey"><td><code>tube_axis</code></td> <td>vertical</td> <td>{% trans "Axe du tube : vertical | horizontal" %}</td></tr>
|
||||
<tr><td><code>min_area_px</code></td> <td>20</td> <td>{% trans "Surface min de détection (px²)" %}</td></tr>
|
||||
<tr><td><code>planarian_count</code></td> <td>1</td> <td>{% trans "Nombre de planaires par puits" %}</td></tr>
|
||||
<tr class="w3-grey"><td><code>planarian_count</code></td> <td>1</td> <td>{% trans "Nombre de planaires par puits" %}</td></tr>
|
||||
<tr><td><code>photo_mode</code></td> <td>none</td> <td>{% trans "Phototactisme : none | fixed | sine | radial" %}</td></tr>
|
||||
<tr><td><code>photo_strength</code></td> <td>0.0</td> <td>{% trans "Intensité phototactisme (0-1)" %}</td></tr>
|
||||
<tr class="w3-grey"><td><code>photo_strength</code></td> <td>0.0</td> <td>{% trans "Intensité phototactisme (0-1)" %}</td></tr>
|
||||
<tr><td><code>chemo_strength</code></td> <td>0.0</td> <td>{% trans "Intensité chimiotactisme (0-1)" %}</td></tr>
|
||||
<tr><td><code>chemo_radius_mm</code></td> <td>2.0</td> <td>{% trans "Rayon zone nourriture (mm)" %}</td></tr>
|
||||
<tr class="w3-grey"><td><code>chemo_radius_mm</code></td> <td>2.0</td> <td>{% trans "Rayon zone nourriture (mm)" %}</td></tr>
|
||||
<tr><td><code>avoid_radius_mm</code></td> <td>3.0</td> <td>{% trans "Rayon évitement inter-individus (mm)" %}</td></tr>
|
||||
<tr><td><code>aggreg_radius_mm</code></td> <td>6.0</td> <td>{% trans "Rayon agrégation inter-individus (mm)" %}</td></tr>
|
||||
<tr class="w3-grey"><td><code>aggreg_radius_mm</code></td> <td>6.0</td> <td>{% trans "Rayon agrégation inter-individus (mm)" %}</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Exemple de fichier -->
|
||||
<p><b>{% trans "Exemple minimal" %}</b></p>
|
||||
<div class="w3-code w3-round" style="font-size:12px; overflow-x:auto;">
|
||||
<div class="w3-code w3-round w3-text-grey" style="font-size:12px; overflow-x:auto;">
|
||||
experiment,well,px_per_mm,fps,thresh_immobile,thresh_mobile,photo_mode<br>
|
||||
exp_ctrl_01,A1,26.25,10,0.2,1.5,none<br>
|
||||
exp_ctrl_01,A2,26.25,10,0.2,1.5,none<br>
|
||||
|
||||
@@ -11,15 +11,41 @@ from django.shortcuts import get_object_or_404, redirect #, render
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views import View
|
||||
from django.views.generic import FormView, ListView
|
||||
|
||||
from django.views.decorators.http import require_GET
|
||||
|
||||
from .forms import CsvImportForm, ExperimentConfigForm, ExportCsvForm
|
||||
from .models import ExperimentConfig
|
||||
from modules.planarian_metrics import ExperimentParams, ReductStoreClient
|
||||
|
||||
from modules.system_stats import get_cached_stats, start_background_updater
|
||||
from scanner.constants import ScannerConstants
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
start_background_updater()
|
||||
|
||||
|
||||
@require_GET
|
||||
def stats_view(request):
|
||||
"""
|
||||
Retourne tout le cache (shm, cpu_info, memory_info, disk_info, updated_at)
|
||||
"""
|
||||
try:
|
||||
data = get_cached_stats()
|
||||
return JsonResponse(data, safe=False)
|
||||
except Exception as e:
|
||||
return JsonResponse({"error": str(e)}, status=500)
|
||||
|
||||
|
||||
def global_context(request, **ctx):
|
||||
conf = ScannerConstants().get()
|
||||
return dict(
|
||||
app_title=settings.APP_TITLE,
|
||||
app_sub_title=settings.APP_SUB_TITLE,
|
||||
domain_server=settings.DOMAIN_SERVER,
|
||||
conf=conf,
|
||||
**ctx
|
||||
)
|
||||
|
||||
|
||||
def _get_reduct_client() -> ReductStoreClient:
|
||||
"""Instancie le client ReductStore depuis les settings Django."""
|
||||
@@ -41,7 +67,11 @@ class ExperimentConfigListView(ListView):
|
||||
template_name = "planarian/experiment_list.html"
|
||||
context_object_name = "configs"
|
||||
ordering = ["-created_at"]
|
||||
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
return global_context(self.request, **context)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Vue : création / modification d'une configuration
|
||||
@@ -53,6 +83,7 @@ class ExperimentConfigFormView(FormView):
|
||||
template_name = "planarian/experiment_form.html"
|
||||
form_class = ExperimentConfigForm
|
||||
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
pk = self.kwargs.get("pk")
|
||||
if pk:
|
||||
@@ -64,7 +95,25 @@ class ExperimentConfigFormView(FormView):
|
||||
form.save()
|
||||
messages.success(self.request, _("Configuration sauvegardée."))
|
||||
return redirect("planarian:experiment-list")
|
||||
|
||||
def form_invalid(self, form):
|
||||
# Called when form validation fails
|
||||
print(f"Form validation failed: {form.errors}")
|
||||
messages.error(self.request, form.errors)
|
||||
return super().form_invalid(form)
|
||||
|
||||
#def post(self, request, *args, **kwargs):
|
||||
# # Custom logic before processing the form
|
||||
# print(f"Received POST data: {request.POST}")
|
||||
# return response
|
||||
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['is_creation'] = 'pk' not in self.kwargs
|
||||
context['is_update'] = 'pk' in self.kwargs
|
||||
return global_context(self.request, choice_title=_("Paramètres d'une expérience"), **context)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Vue : import CSV de paramètres
|
||||
@@ -119,6 +168,11 @@ class ImportParamsView(FormView):
|
||||
% {"c": created, "u": updated, "e": errors},
|
||||
)
|
||||
return redirect("planarian:experiment-list")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
return global_context(self.request, choice_title=_("configurations d'expérience depuis un fichier CSV"), **context)
|
||||
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -168,7 +222,11 @@ class ExportCsvView(FormView):
|
||||
response["Content-Disposition"] = f'attachment; filename="{filename}"'
|
||||
messages.success(self.request, _("%(n)d lignes exportées.") % {"n": n})
|
||||
return response
|
||||
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
return global_context(self.request, choice_title=_("Tracking depuis ReductStore vers un fichier CSV"), **context)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Vue API JSON : données de tracking (pour polling front-end)
|
||||
@@ -207,4 +265,8 @@ class TrackingDataView(View):
|
||||
|
||||
records = _fetch()
|
||||
return JsonResponse({"count": len(records), "records": records})
|
||||
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
return global_context(self.request, choice_title=_("Métriques de tracking d'un planaire"), **context)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user