From e29a652fb4d2585242ae40548ce981b792a14a1e Mon Sep 17 00:00:00 2001 From: Dan Birman Date: Thu, 8 Jan 2026 15:19:00 -0800 Subject: [PATCH 1/7] feat: add barseq instrument --- examples/barseq_instrument.py | 248 ++++++++++++++++++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 examples/barseq_instrument.py diff --git a/examples/barseq_instrument.py b/examples/barseq_instrument.py new file mode 100644 index 000000000..c72d32ea5 --- /dev/null +++ b/examples/barseq_instrument.py @@ -0,0 +1,248 @@ +"""Example BarSEQ instrument schema""" + +from datetime import date + +from aind_data_schema_models.modalities import Modality +from aind_data_schema_models.organizations import Organization + +from aind_data_schema.components.coordinates import CoordinateSystemLibrary +from aind_data_schema.components.devices import ( + Camera, + DAQDevice, + Device, + Filter, + Laser, + Microscope, + Objective, + Cooling, + DataInterface, + CameraChroma, + BinMode +) +from aind_data_schema.core.instrument import Instrument + + +objectives = [ + Objective( + name="20x Objective", + numerical_aperture=0.70, + magnification=20, + immersion="air", + manufacturer=Organization.NIKON, + model="CFI S Plan Fluor LWD 20XC", + ), +] + +dichroics = [ + Filter( + name="D1", + filter_type="Multiband", + manufacturer=Organization.SEMROCK, + model="FF421/491/567/659/776-Di01", + notes="DAPI/GFP/RFP/Cy5/Cy7 dichroic", + center_wavelength=[421, 491, 567, 659, 776], + ), + Filter( + name="D2", + filter_type="Multiband", + manufacturer=Organization.CHROMA, + model="ZT405/514/635rpc", + notes="DAPI/YFP/Rs Cy5 dichroic", + center_wavelength=[405, 514, 635], + ), + Filter( + name="D3", + filter_type="Multiband", + manufacturer=Organization.SEMROCK, + model="FF421/491/572-Di01-25x36", + notes="DAPI/GFP/TxRed dichroic", + center_wavelength=[421, 491, 572], + ), +] + +emission_filters = [ + Filter( + name="E1", + filter_type="Multiband", + manufacturer=Organization.SEMROCK, + model="FF01-441/511/593/684/817", + notes="DAPI/GFP/Red/Cy5/Cy7 - used with D1 and 405nm laser", + center_wavelength=[441, 511, 593, 684, 817], + ), + Filter( + name="E2", + filter_type="Band pass", + manufacturer=Organization.SEMROCK, + model="FF01-565/24", + notes="YFP - used with D2 and 520nm laser; bandwidth 24nm", + center_wavelength=565, + ), + Filter( + name="E3", + filter_type="Band pass", + manufacturer=Organization.SEMROCK, + model="FF01-585/11", + notes="RFP - used with D1 and 546nm laser; bandwidth 11nm", + center_wavelength=585, + ), + Filter( + name="E4", + filter_type="Band pass", + manufacturer=Organization.SEMROCK, + model="FF01-676/29", + notes="FarRed - used with D1 and 638nm laser; bandwidth 29nm", + center_wavelength=676, + ), + Filter( + name="E5", + filter_type="Band pass", + manufacturer=Organization.SEMROCK, + model="FF01-775/140", + notes="RS Cy5 - used with D2 and 638nm laser; bandwidth 140nm", + center_wavelength=775, + ), + Filter( + name="E6", + filter_type="Multiband", + manufacturer=Organization.SEMROCK, + model="FF01-391/477/549/639/741-25", + notes="YFP/Rs Cy5", + center_wavelength=[391, 477, 549, 639, 741], + ), + Filter( + name="E7", + filter_type="Multiband", + manufacturer=Organization.CHROMA, + model="69401m", + notes="DAPI/GFP/TxRed", + center_wavelength=[440, 520, 600], + ), + Filter( + name="E8", + filter_type="Multiband", + manufacturer=Organization.CHROMA, + model="ZET532/640m", + notes="Alexa532/Cy5(wide)", + center_wavelength=[532, 640], + ), +] + +light_sources = [ + Laser( + name="Lumencor Celesta 365nm", + manufacturer=Organization.LUMENCOR, + model="Celesta", + wavelength=365, + ), + Laser( + name="Lumencor Celesta 440nm", + manufacturer=Organization.LUMENCOR, + model="Celesta", + wavelength=440, + ), + Laser( + name="Lumencor Celesta 488nm", + manufacturer=Organization.LUMENCOR, + model="Celesta", + wavelength=488, + ), + Laser( + name="Lumencor Celesta 514nm", + manufacturer=Organization.LUMENCOR, + model="Celesta", + wavelength=514, + ), + Laser( + name="Lumencor Celesta 561nm", + manufacturer=Organization.LUMENCOR, + model="Celesta", + wavelength=561, + ), + Laser( + name="Lumencor Celesta 640nm", + manufacturer=Organization.LUMENCOR, + model="Celesta", + wavelength=640, + ), + Laser( + name="Lumencor Celesta 730nm", + manufacturer=Organization.LUMENCOR, + model="Celesta", + wavelength=730, + ), +] + +camera = Camera( + name="Camera-1", + manufacturer=Organization.TELEDYNE_PHOTOMETRICS, + model="01-KINETIX-M-C", + data_interface=DataInterface.USB, + cooling=Cooling.AIR, + sensor_width=3200, + sensor_height=3200, + size_unit="px", + chroma=CameraChroma.BW, + immersion="air", + bin_mode=BinMode.NO_BINNING, + bit_depth=16, + sensor_format="20.8 x 20.8", + sensor_format_unit="mm", + frame_rate=500, +) + +microscope = Microscope( + name="Ti2-E__0", + manufacturer=Organization.NIKON, + model="Ti2-E", +) + +spinning_disk = Device( + name="XLIGHT Spinning Disk", + manufacturer=Organization.CRESTOPTICS, + model="X-Light V3", + notes="Nipkow spinning disk confocal module", +) + +daq = DAQDevice( + name="DigitalIO", + manufacturer=Organization.NATIONAL_INSTRUMENTS, + model="NI-DAQ Dev1", + data_interface="USB", + computer_name="Acquisition Computer", +) + +instrument = Instrument( + location="243", + instrument_id="Dogwood", + modification_date=date(2024, 7, 9), + coordinate_system=CoordinateSystemLibrary.IMAGE_XYZ, + modalities=[Modality.BARSEQ], + notes=( + "BarSEQ imaging system with Nikon Ti2-E inverted microscope, X-Light V3 spinning disk confocal, " + "and Lumencor Celesta light engine. Used for multi-channel fluorescence imaging of genetically " + "modified tissue samples. Channel configurations: geneseq01/bcseq01 cycles use G/T/A/C/DAPI channels, " + "other geneseq/bcseq cycles use GTAC channels, hyb cycles use GFP/G/TxRed/Cy5/DAPI/DIC channels. " + "Camera pixel size: 0.33um. Stitch overlap: 23%. Hyb registration radius: 100um. " + "Configuration date corresponds to chprofile20x_dogwood_20240709.mat and chshift20x_dogwood_20240709.mat" + ), + temperature_control=False, + components=[ + microscope, + *objectives, + *dichroics, + *emission_filters, + *light_sources, + camera, + spinning_disk, + daq, + ], +) + +if __name__ == "__main__": + import json + + serialized = instrument.model_dump_json(indent=3) + print(serialized) + + with open("barseq_instrument.json", "w") as f: + f.write(serialized) From 6f193b8a5c7a17e189da2ee7473020e2862bc8f3 Mon Sep 17 00:00:00 2001 From: Dan Birman Date: Thu, 15 Jan 2026 14:48:46 -0800 Subject: [PATCH 2/7] refactor: SlapPlane -> Slap2Plane --- src/aind_data_schema/components/configs.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/aind_data_schema/components/configs.py b/src/aind_data_schema/components/configs.py index 5e8dba66a..d7defd6da 100644 --- a/src/aind_data_schema/components/configs.py +++ b/src/aind_data_schema/components/configs.py @@ -219,19 +219,27 @@ class CoupledPlane(Plane): power_ratio: float = Field(..., title="Power ratio") -class SlapPlane(Plane): +class Slap2Plane(Plane): """Configuration of an imagine plane on a Slap microscope""" - dmd_dilation_x: int = Field(..., title="DMD Dilation X (pixels)") - dmd_dilation_y: int = Field(..., title="DMD Dilation Y (pixels)") - dilation_unit: SizeUnit = Field(default=SizeUnit.PX, title="Dilation unit") - + name: Optional[str] = Field(default=None, title="Plane name") slap_acquisition_type: SlapAcquisitionType = Field(..., title="Slap experiment type") target_neuron: Optional[str] = Field(default=None, title="Target neuron") - target_branch: Optional[str] = Field(default=None, title="Target branch") - path_to_array_of_frame_rates: AssetPath = Field( + + mask_image_path: AssetPath = Field( + ..., title="Mask image path", description="Relative path from metadata json to file" + ) + + dilation_image_path: AssetPath = Field( + ..., title="Dilation image path", description="Relative path from metadata json to file" + ) + dilation_unit: SizeUnit = Field(default=SizeUnit.PX, title="Dilation unit") + + framerate_image_path: AssetPath = Field( ..., title="Array of frame rates", description="Relative path from metadata json to file" ) + framerate_unit: FrequencyUnit = Field(default=FrequencyUnit.HZ, title="Frame rate unit") + class Image(DataModel): From 37e3230f93e35a202e1ba16d1594c74a7224e9cb Mon Sep 17 00:00:00 2001 From: Dan Birman Date: Thu, 15 Jan 2026 14:49:07 -0800 Subject: [PATCH 3/7] Revert "refactor: SlapPlane -> Slap2Plane" This reverts commit 6f193b8a5c7a17e189da2ee7473020e2862bc8f3. --- src/aind_data_schema/components/configs.py | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/src/aind_data_schema/components/configs.py b/src/aind_data_schema/components/configs.py index d7defd6da..5e8dba66a 100644 --- a/src/aind_data_schema/components/configs.py +++ b/src/aind_data_schema/components/configs.py @@ -219,27 +219,19 @@ class CoupledPlane(Plane): power_ratio: float = Field(..., title="Power ratio") -class Slap2Plane(Plane): +class SlapPlane(Plane): """Configuration of an imagine plane on a Slap microscope""" - name: Optional[str] = Field(default=None, title="Plane name") - slap_acquisition_type: SlapAcquisitionType = Field(..., title="Slap experiment type") - target_neuron: Optional[str] = Field(default=None, title="Target neuron") - - mask_image_path: AssetPath = Field( - ..., title="Mask image path", description="Relative path from metadata json to file" - ) - - dilation_image_path: AssetPath = Field( - ..., title="Dilation image path", description="Relative path from metadata json to file" - ) + dmd_dilation_x: int = Field(..., title="DMD Dilation X (pixels)") + dmd_dilation_y: int = Field(..., title="DMD Dilation Y (pixels)") dilation_unit: SizeUnit = Field(default=SizeUnit.PX, title="Dilation unit") - framerate_image_path: AssetPath = Field( + slap_acquisition_type: SlapAcquisitionType = Field(..., title="Slap experiment type") + target_neuron: Optional[str] = Field(default=None, title="Target neuron") + target_branch: Optional[str] = Field(default=None, title="Target branch") + path_to_array_of_frame_rates: AssetPath = Field( ..., title="Array of frame rates", description="Relative path from metadata json to file" ) - framerate_unit: FrequencyUnit = Field(default=FrequencyUnit.HZ, title="Frame rate unit") - class Image(DataModel): From e26bf4d501f2c80439ea4ff71c7007274a15a785 Mon Sep 17 00:00:00 2001 From: Dan Birman Date: Tue, 20 Jan 2026 09:43:37 -0800 Subject: [PATCH 4/7] chore: lint --- examples/barseq_instrument.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/barseq_instrument.py b/examples/barseq_instrument.py index c72d32ea5..de63fb21a 100644 --- a/examples/barseq_instrument.py +++ b/examples/barseq_instrument.py @@ -239,7 +239,6 @@ ) if __name__ == "__main__": - import json serialized = instrument.model_dump_json(indent=3) print(serialized) From 53777313cc0986be219c7fb9effda4ebd48a5c28 Mon Sep 17 00:00:00 2001 From: Dan Birman Date: Tue, 20 Jan 2026 09:44:12 -0800 Subject: [PATCH 5/7] refactor: fix serialization code in example --- examples/barseq_instrument.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/examples/barseq_instrument.py b/examples/barseq_instrument.py index de63fb21a..dbf4e1c3d 100644 --- a/examples/barseq_instrument.py +++ b/examples/barseq_instrument.py @@ -239,9 +239,6 @@ ) if __name__ == "__main__": - - serialized = instrument.model_dump_json(indent=3) - print(serialized) - - with open("barseq_instrument.json", "w") as f: - f.write(serialized) + serialized = instrument.model_dump_json() + deserialized = Instrument.model_validate_json(serialized) + deserialized.write_standard_file(prefix="barseq") From 615dcb44a6f25f7f9f341b470f9171c04da607ff Mon Sep 17 00:00:00 2001 From: Dan Birman Date: Tue, 20 Jan 2026 12:17:27 -0800 Subject: [PATCH 6/7] fix: fix issues blocking build --- examples/barseq_instrument.py | 7 ++++--- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/barseq_instrument.py b/examples/barseq_instrument.py index dbf4e1c3d..e7010853d 100644 --- a/examples/barseq_instrument.py +++ b/examples/barseq_instrument.py @@ -4,6 +4,7 @@ from aind_data_schema_models.modalities import Modality from aind_data_schema_models.organizations import Organization +from aind_data_schema_models.units import SizeUnit from aind_data_schema.components.coordinates import CoordinateSystemLibrary from aind_data_schema.components.devices import ( @@ -174,13 +175,13 @@ camera = Camera( name="Camera-1", - manufacturer=Organization.TELEDYNE_PHOTOMETRICS, + manufacturer=Organization.TELEDYNE_VISION_SOLUTIONS, model="01-KINETIX-M-C", data_interface=DataInterface.USB, cooling=Cooling.AIR, sensor_width=3200, sensor_height=3200, - size_unit="px", + size_unit=SizeUnit.PX, chroma=CameraChroma.BW, immersion="air", bin_mode=BinMode.NO_BINNING, @@ -188,6 +189,7 @@ sensor_format="20.8 x 20.8", sensor_format_unit="mm", frame_rate=500, + frame_rate_unit="hertz", ) microscope = Microscope( @@ -208,7 +210,6 @@ manufacturer=Organization.NATIONAL_INSTRUMENTS, model="NI-DAQ Dev1", data_interface="USB", - computer_name="Acquisition Computer", ) instrument = Instrument( diff --git a/pyproject.toml b/pyproject.toml index b64afcd3d..b038f18c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ readme = "README.md" dynamic = ["version"] dependencies = [ - 'aind-data-schema-models>=5,<6', + 'aind-data-schema-models>=5.2.1,<6', 'pydantic>=2.7, <2.12', ] From 81d3aa3d0d310fbc5b092b1d88fa399ca4d9bbda Mon Sep 17 00:00:00 2001 From: Dan Birman Date: Tue, 5 May 2026 21:36:56 -0700 Subject: [PATCH 7/7] chore: fix write function for instrument example --- examples/barseq_instrument.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/examples/barseq_instrument.py b/examples/barseq_instrument.py index e7010853d..abb7e99b2 100644 --- a/examples/barseq_instrument.py +++ b/examples/barseq_instrument.py @@ -1,5 +1,6 @@ """Example BarSEQ instrument schema""" +import argparse from datetime import date from aind_data_schema_models.modalities import Modality @@ -240,6 +241,10 @@ ) if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--output-dir", default=None, help="Output directory for generated JSON file") + args = parser.parse_args() + serialized = instrument.model_dump_json() deserialized = Instrument.model_validate_json(serialized) - deserialized.write_standard_file(prefix="barseq") + deserialized.write_standard_file(prefix="barseq", output_directory=args.output_dir)