diff --git a/backend/proteins/migrations/0056_camera_full_well_capacity_camera_pixel_size_and_more.py b/backend/proteins/migrations/0056_camera_full_well_capacity_camera_pixel_size_and_more.py new file mode 100644 index 00000000..a150bb41 --- /dev/null +++ b/backend/proteins/migrations/0056_camera_full_well_capacity_camera_pixel_size_and_more.py @@ -0,0 +1,57 @@ +# Generated by Django 4.2.24 on 2025-09-04 16:00 + +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('proteins', '0055_spectrum_status_spectrum_status_changed'), + ] + + operations = [ + migrations.AddField( + model_name='camera', + name='full_well_capacity', + field=models.PositiveIntegerField(blank=True, help_text='Full well capacity in electrons', null=True), + ), + migrations.AddField( + model_name='camera', + name='pixel_size', + field=models.FloatField(blank=True, help_text="Pixel size in microns e.g. '6.5'. (assumes square pixels)", null=True, validators=[django.core.validators.MinValueValidator(0.0001)]), + ), + migrations.AddField( + model_name='camera', + name='pixels_height', + field=models.PositiveIntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='camera', + name='pixels_width', + field=models.PositiveIntegerField(blank=True, null=True), + ), + migrations.CreateModel( + name='CameraMode', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(help_text="e.g. 'HCG 12-bit fast', 'LCG 16-bit slow', 'EM gain 300'", max_length=128)), + ('adc_bit_depth', models.PositiveIntegerField(blank=True, help_text='e.g. 12, 14, 16', null=True)), + ('readout_rate_mhz', models.FloatField(blank=True, help_text='Pixel clock / ADC rate (MHz) if applicable', null=True, validators=[django.core.validators.MinValueValidator(0)])), + ('temperature_setpoint_c', models.FloatField(blank=True, help_text='Cooling setpoint (°C). Dark current depends on this.', null=True)), + ('dark_current', models.FloatField(blank=True, help_text='Electrons/pixel/second at the stated temperature', null=True, validators=[django.core.validators.MinValueValidator(0)])), + ('read_noise_median', models.FloatField(blank=True, help_text='e- median', null=True, validators=[django.core.validators.MinValueValidator(0)])), + ('read_noise_rms', models.FloatField(blank=True, help_text='e- RMS', null=True, validators=[django.core.validators.MinValueValidator(0)])), + ('frame_rate', models.FloatField(blank=True, help_text='Max full-chip FPS under this mode.', null=True, validators=[django.core.validators.MinValueValidator(0)])), + ('camera', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='modes', to='proteins.camera')), + ], + options={ + 'indexes': [models.Index(fields=['camera', 'name'], name='proteins_ca_camera__0953d7_idx')], + }, + ), + migrations.AddConstraint( + model_name='cameramode', + constraint=models.UniqueConstraint(fields=('camera', 'name'), name='unique_camera_mode'), + ), + ] diff --git a/backend/proteins/models/spectrum.py b/backend/proteins/models/spectrum.py index fe71cd63..8da43de4 100644 --- a/backend/proteins/models/spectrum.py +++ b/backend/proteins/models/spectrum.py @@ -55,6 +55,66 @@ def d3_dicts(self): class Camera(SpectrumOwner, Product): manufacturer = models.CharField(max_length=128, blank=True) + full_well_capacity = models.PositiveIntegerField( + null=True, blank=True, help_text="Full well capacity in electrons" + ) + pixel_size = models.FloatField( + null=True, + blank=True, + help_text="Pixel size in microns e.g. '6.5'. (assumes square pixels)", + validators=[MinValueValidator(0.0001)], # Must be > 0 + ) + pixels_width = models.PositiveIntegerField(null=True, blank=True) + pixels_height = models.PositiveIntegerField(null=True, blank=True) + + +class CameraMode(models.Model): + """ + An operating mode for a specific Camera. + Mode-dependent fields: read noise, dark current (temp), frame rate, etc. + """ + + camera = models.ForeignKey(Camera, on_delete=models.CASCADE, related_name="modes") + name = models.CharField(max_length=128, help_text="e.g. 'HCG 12-bit fast', 'LCG 16-bit slow', 'EM gain 300'") + + # Typical toggles that affect performance + adc_bit_depth = models.PositiveIntegerField(null=True, blank=True, help_text="e.g. 12, 14, 16") + readout_rate_mhz = models.FloatField( + null=True, + blank=True, + validators=[MinValueValidator(0)], + help_text="Pixel clock / ADC rate (MHz) if applicable", + ) + + # Conditions + temperature_setpoint_c = models.FloatField( + null=True, blank=True, help_text="Cooling setpoint (°C). Dark current depends on this." + ) + + # Performance (mode-dependent) + dark_current = models.FloatField( + null=True, + blank=True, + validators=[MinValueValidator(0)], + help_text="Electrons/pixel/second at the stated temperature", + ) + read_noise_median = models.FloatField( + null=True, blank=True, validators=[MinValueValidator(0)], help_text="e- median" + ) + read_noise_rms = models.FloatField(null=True, blank=True, validators=[MinValueValidator(0)], help_text="e- RMS") + frame_rate = models.FloatField( + null=True, + blank=True, + validators=[MinValueValidator(0)], + help_text="Max full-chip FPS under this mode.", + ) + + class Meta: + constraints = [models.UniqueConstraint(fields=["camera", "name"], name="unique_camera_mode")] + indexes = [models.Index(fields=["camera", "name"])] + + def __str__(self): + return f"{self.camera} - {self.name}" class Light(SpectrumOwner, Product): diff --git a/backend/references/migrations/0008_alter_reference_year.py b/backend/references/migrations/0008_alter_reference_year.py new file mode 100644 index 00000000..8ff2d6e9 --- /dev/null +++ b/backend/references/migrations/0008_alter_reference_year.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.24 on 2025-09-04 16:00 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('references', '0007_alter_reference_year'), + ] + + operations = [ + migrations.AlterField( + model_name='reference', + name='year', + field=models.PositiveIntegerField(help_text='YYYY', validators=[django.core.validators.MinLengthValidator(4), django.core.validators.MaxLengthValidator(4), django.core.validators.MinValueValidator(1960), django.core.validators.MaxValueValidator(2026)]), + ), + ]