Examples

This section provides detailed examples of using the ndx-microscopy extension for various microscopy techniques and workflows.

Two-Photon Calcium Imaging

Complete example of two-photon calcium imaging with full optical path configuration:

from datetime import datetime
from uuid import uuid4
import matplotlib.pyplot as plt
import numpy as np
from pynwb import NWBFile, NWBHDF5IO
from ndx_microscopy import (
    MicroscopeModel,
    Microscope,
    MicroscopyRig,
    MicroscopyChannel,
    PlanarImagingSpace,
    PlanarMicroscopySeries,
    PlanarSegmentation,
    SummaryImage,
    MicroscopyResponseSeries,
    MicroscopyResponseSeriesContainer,
    LineScan,
    PlaneAcquisition,
    RandomAccessScan,
)
from ndx_ophys_devices import (
    ExcitationSourceModel,
    PulsedExcitationSource,
    BandOpticalFilterModel,
    BandOpticalFilter,
    DichroicMirrorModel,
    DichroicMirror,
    PhotodetectorModel,
    Photodetector,
    Indicator
)

# Create NWB file
nwbfile = NWBFile(
    session_description='Two-photon calcium imaging session',
    identifier=str(uuid4()),
    session_start_time=datetime.now(),
    lab='Neural Imaging Lab',
    institution='University of Neuroscience',
    experiment_description='GCaMP6f imaging in visual cortex'
)

# Set up microscope model
microscope_model = MicroscopeModel(
    name='2p-model',
    description='Two-photon microscope model for calcium imaging',
    model_number='2p-001',  # Example model number
    manufacturer='ImagingTech'
)
nwbfile.add_device(microscope_model)

# Set up microscope with technique
microscope = Microscope(
    name='2p-scope',
    serial_number='2p-serial-001',  # Example serial number
    model=microscope_model,
    technique='mirror scanning'  # Specify the technique used
)
nwbfile.add_device(microscope)

# Set up optical components
excitation_source_model = ExcitationSourceModel(
    name="excitation_source_model",
    manufacturer="Laser Manufacturer",
    model_number="ES-123",
    description="Excitation source model for green indicator",
    source_type="laser",
    excitation_mode="two-photon",
    wavelength_range_in_nm=[800.0, 1000.0]
)
nwbfile.add_device(excitation_source_model)

pulsed_excitation_source = PulsedExcitationSource(
    name="pulsed_excitation_source",
    description="Pulsed excitation source for red indicator",
    serial_number="PES-SN-123456",
    model=excitation_source_model,
    peak_power_in_W=0.7,
    peak_pulse_energy_in_J=0.7,
    intensity_in_W_per_m2=0.005,
    exposure_time_in_s=2.51e-13,
    pulse_rate_in_Hz=2.0e6
)
nwbfile.add_device(pulsed_excitation_source)

excitation_filter_model = BandOpticalFilterModel(
    name="excitation_filter_model",
    filter_type="Bandpass",
    manufacturer="Semrock",
    model_number="FF01-920/80",
    center_wavelength_in_nm=920.0,
    bandwidth_in_nm=80.0
)
nwbfile.add_device(excitation_filter_model)

excitation_filter = BandOpticalFilter(
    name="excitation_filter",
    model=excitation_filter_model,
    serial_number="EF-SN-123456"
)
nwbfile.add_device(excitation_filter)

dichroic_mirror_model = DichroicMirrorModel(
    name="primary_dichroic_model",
    manufacturer="Semrock",
    model_number="FF757-Di01",  # Common dichroic for GCaMP imaging
    cut_on_wavelength_in_nm=757.0,  # Transmits >757nm
    cut_off_wavelength_in_nm=750.0,  # Reflects <750nm
    transmission_band_in_nm=[757.0, 1100.0],  # Transmits NIR excitation light
    reflection_band_in_nm=(400.0, 750.0),  # Reflects emission light (including 510nm GCaMP6f emission)
    angle_of_incidence_in_degrees=45.0  # Standard angle for dichroic mirrors in microscopes
)
nwbfile.add_device(dichroic_mirror_model)

dichroic_mirror = DichroicMirror(
    name="dichroic_mirror",
    description="Dichroic mirror for green indicator",
    serial_number="DM-SN-123456",
    model=dichroic_mirror_model
)
nwbfile.add_device(dichroic_mirror)

emission_filter_model = BandOpticalFilterModel(
    name="emission_filter_model",
    filter_type="Bandpass",
    manufacturer="Semrock",
    model_number="FF01-510/84",
    center_wavelength_in_nm=510.0,
    bandwidth_in_nm=84.0
)
nwbfile.add_device(emission_filter_model)

emission_filter = BandOpticalFilter(
    name="emission_filter",
    description="Band optical filter for green indicator",
    serial_number="BOF-SN-123456",
    model=emission_filter_model
)
nwbfile.add_device(emission_filter)

photodetector_model = PhotodetectorModel(
    name="photodetector_model",
    detector_type="PMT",
    manufacturer="Hamamatsu",
    model_number="R6357",
    gain=1000000.0,  # 10^6 typical PMT gain
    gain_unit="V/A"  # Voltage/Current
)
nwbfile.add_device(photodetector_model)

photodetector = Photodetector(
    name="photodetector",
    description="Photodetector for green emission",
    serial_number="PD-SN-123456",
    model=photodetector_model
)
nwbfile.add_device(photodetector)

# Create indicator
indicator = Indicator(
    name="gcamp6f",
    label="GCaMP6f",
    description="Calcium indicator for two-photon imaging",
    manufacturer="Addgene",
    injection_brain_region="Visual cortex",
    injection_coordinates_in_mm=[-2.5, 3.2, 0.5]
)

# Create microscopy rig
microscopy_rig = MicroscopyRig(
    name='2p_rig',
    description='Two-photon microscopy rig',
    microscope=microscope,
    excitation_source=pulsed_excitation_source,
    excitation_filter=excitation_filter,
    dichroic_mirror=dichroic_mirror,
    photodetector=photodetector,
    emission_filter=emission_filter
)

# Define illumination pattern
line_scan = LineScan(
    name='line_scanning',
    description='Line scanning two-photon microscopy',
    scan_direction='horizontal',
    line_rate_in_Hz=1000.0,
    dwell_time_in_s=1.0e-6
)

# Create example imaging data
frames = 1000
height = 512
width = 512
data = np.random.rand(frames, height, width)

# Define imaging space with illumination pattern
imaging_space = PlanarImagingSpace(
    name='cortex_plane1',
    description='Layer 2/3 of visual cortex',
    pixel_size_in_um=[1.0, 1.0],
    dimensions_in_pixels=[height, width],
    origin_coordinates=[-1.2, -0.6, -2.0],
    location='Visual cortex, layer 2/3',
    reference_frame='bregma',
    orientation='RAS',  # Right-Anterior-Superior
    illumination_pattern=line_scan  # Include the illumination pattern
)

# Create microscopy channel
microscopy_channel = MicroscopyChannel(
    name='gcamp_channel',
    description='GCaMP6f channel',
    excitation_wavelength_in_nm=920.0,
    emission_wavelength_in_nm=510.0,
    indicator=indicator
)

# Create imaging series
imaging_series = PlanarMicroscopySeries(
    name='imaging_data',
    description='Two-photon calcium imaging',
    microscopy_channel=microscopy_channel,
    microscopy_rig=microscopy_rig,
    planar_imaging_space=imaging_space,
    data=data,
    unit='a.u.',
    rate=30.0,
    starting_time=0.0
)
nwbfile.add_acquisition(imaging_series)

# Create ophys processing module
ophys_module = nwbfile.create_processing_module(
    name='ophys',
    description='Optical physiology processing module'
)

# Create summary images
mean_image = SummaryImage(
    name='mean',
    description='Mean intensity projection',
    data=np.mean(data, axis=0)
)

max_image = SummaryImage(
    name='max',
    description='Maximum intensity projection',
    data=np.max(data, axis=0)
)

# Create segmentation
segmentation = PlanarSegmentation(
    name='rois',
    description='Manual ROI segmentation',
    planar_imaging_space=imaging_space,
    summary_images=[mean_image, max_image]
)

# Add ROIs using image masks
roi_mask = np.zeros((height, width), dtype=bool)
roi_mask[256:266, 256:266] = True  # 10x10 ROI
segmentation.add_roi(image_mask=roi_mask)

# OR Add ROIs using pixel masks
# pixel_mask = [
#     [100, 100, 1.0],  # x, y, weight
#     [101, 100, 1.0],
#     [102, 100, 1.0]
# ]
# segmentation.add_roi(pixel_mask=pixel_mask)

# Create ROI responses
roi_region = segmentation.create_roi_table_region(
    description='All ROIs',
    region=list(range(len(segmentation.id)))
)

# Extract responses (example calculation)
num_rois = len(segmentation.id)
responses = np.zeros((frames, num_rois))

for i, roi_mask in enumerate(segmentation.image_mask[:]):
    roi_data = data[:, roi_mask]
    responses[:, i] = np.mean(roi_data, axis=1)

# Create response series
response_series = MicroscopyResponseSeries(
    name='roi_responses',
    description='Fluorescence responses from ROIs',
    data=responses,
    rois=roi_region,
    unit='n.a.',
    rate=30.0,
    starting_time=0.0
    microscopy_series=imaging_series
)

# Create container for response series
response_container = MicroscopyResponseSeriesContainer(
    name='responses',
    microscopy_response_series=[response_series]
)

# Add segmentation and responses to ophys module
ophys_module.add(segmentation)
ophys_module.add(response_container)

# Save file
with NWBHDF5IO('calcium_imaging.nwb', 'w') as io:
    io.write(nwbfile)

# Read file and access data
with NWBHDF5IO('calcium_imaging.nwb', 'r') as io:
    nwbfile = io.read()

    # Access imaging data
    imaging = nwbfile.acquisition['imaging_data']
    raw_data = imaging.data[:]

    # Access ROI data
    ophys = nwbfile.processing['ophys']
    rois = ophys['rois']
    roi_masks = rois.image_mask[:]

    # Access responses
    responses = ophys['responses']
    roi_data = responses['roi_responses'].data[:]

Volumetric Imaging

Example of volumetric imaging with 3D ROI segmentation:

from datetime import datetime
from uuid import uuid4
import numpy as np
from pynwb import NWBFile, NWBHDF5IO
from ndx_microscopy import (
    MicroscopeModel,
    Microscope,
    MicroscopyRig,
    VolumetricImagingSpace,
    VolumetricMicroscopySeries,
    VolumetricSegmentation,
    SummaryImage,
    MicroscopyResponseSeries,
    MicroscopyResponseSeriesContainer,
    RandomAccessScan
)
from ndx_ophys_devices import (
    ExcitationSourceModel,
    ExcitationSource,
    BandOpticalFilterModel,
    BandOpticalFilter,
    DichroicMirrorModel,
    DichroicMirror,
    PhotodetectorModel,
    Photodetector,
    Indicator
)

# Create NWB file
nwbfile = NWBFile(
    session_description='Volumetric imaging session',
    identifier=str(uuid4()),
    session_start_time=datetime.now(),
    lab='Neural Dynamics Lab',
    institution='University of Neuroscience',
    experiment_description='Volumetric imaging in cortex'
)

# Set up microscope model
microscope_model = MicroscopeModel(
    name='volume-model',
    description='Volumetric imaging microscope model',
    model_number='volume-001',
    manufacturer='ImagingTech'
)
nwbfile.add_device(microscope_model)

# Set up microscope with technique
microscope = Microscope(
    name='volume-scope',
    description='Custom volumetric imaging microscope',
    serial_number='volume-serial-001',
    model=microscope_model,
    technique='acousto-optical deflectors'  # Specify the technique used
)
nwbfile.add_device(microscope)

# Set up optical components
excitation_source_model = ExcitationSourceModel(
    name="excitation_source_model",
    manufacturer="Coherent",
    model_number="Chameleon",
    description="Excitation source model for volumetric imaging",
    source_type="laser",
    excitation_mode="two-photon",
    wavelength_range_in_nm=[800.0, 1000.0]
)
nwbfile.add_device(excitation_source_model)

laser = ExcitationSource(
    name='laser',
    description="Excitation source for volumetric imaging",
    serial_number="ES-SN-123456",
    model=excitation_source_model,
    intensity_in_W_per_m2=1000.0,
    exposure_time_in_s=0.001
)
nwbfile.add_device(laser)

excitation_filter_model = BandOpticalFilterModel(
    name="excitation_filter_model",
    filter_type="Bandpass",
    manufacturer="Semrock",
    model_number="FF01-920/80",
    center_wavelength_in_nm=920.0,
    bandwidth_in_nm=80.0
)
nwbfile.add_device(excitation_filter_model)

excitation_filter = BandOpticalFilter(
    name='excitation_filter',
    description="Excitation filter for volumetric imaging",
    serial_number="EF-SN-123456",
    model=excitation_filter_model
)
nwbfile.add_device(excitation_filter)

dichroic_mirror_model = DichroicMirrorModel(
    name="dichroic_mirror_model",
    manufacturer="Semrock",
    model_number="FF695-Di02",
    cut_on_wavelength_in_nm=695.0
)
nwbfile.add_device(dichroic_mirror_model)

dichroic = DichroicMirror(
    name='primary_dichroic',
    description="Dichroic mirror for volumetric imaging",
    serial_number="DM-SN-123456",
    model=dichroic_mirror_model
)
nwbfile.add_device(dichroic)

emission_filter_model = BandOpticalFilterModel(
    name="emission_filter_model",
    filter_type="Bandpass",
    manufacturer="Semrock",
    model_number="FF01-510/84",
    center_wavelength_in_nm=510.0,
    bandwidth_in_nm=84.0
)
nwbfile.add_device(emission_filter_model)

emission_filter = BandOpticalFilter(
    name='emission_filter',
    description="Emission filter for volumetric imaging",
    serial_number="EF-SN-123456",
    model=emission_filter_model
)
nwbfile.add_device(emission_filter)

photodetector_model = PhotodetectorModel(
    name="photodetector_model",
    detector_type="PMT",
    manufacturer="Hamamatsu",
    model_number="R6357",
    gain=70.0,
    gain_unit="dB"
)
nwbfile.add_device(photodetector_model)

detector = Photodetector(
    name='pmt',
    description="Photodetector for volumetric imaging",
    serial_number="PD-SN-123456",
    model=photodetector_model
)
nwbfile.add_device(detector)

# Create indicator
indicator = Indicator(
    name='gcamp6f',
    label='GCaMP6f',
    description='Calcium indicator for volumetric imaging',
    manufacturer='Addgene',
    injection_brain_region='Visual cortex',
    injection_coordinates_in_mm=[-2.5, 3.2, 0.5]
)

# Create microscopy rig
microscopy_rig = MicroscopyRig(
    name='volume_rig',
    description='Volumetric microscopy rig',
    microscope=microscope,
    excitation_source=laser,
    excitation_filter=excitation_filter,
    dichroic_mirror=dichroic,
    photodetector=detector,
    emission_filter=emission_filter
)

# Define illumination pattern for volumetric imaging
random_access_scan = RandomAccessScan(
    name='random_access',
    description='Targeted imaging of specific neurons',
    max_scan_points=1000,
    dwell_time_in_s=1.0e-6,
    scanning_pattern='spiral'
)

# Create example volumetric data
frames = 100
height = 512
width = 512
depths = 10
data = np.random.rand(frames, height, width, depths)

# Define volumetric imaging space with illumination pattern
volume_space = VolumetricImagingSpace(
    name='cortex_volume',
    description='Visual cortex volume',
    voxel_size_in_um=[1.0, 1.0, 2.0],  # Higher spacing in z
    dimensions_in_voxels=[height, width, depths],
    origin_coordinates=[-1.2, -0.6, -2.0],
    location='Visual cortex',
    reference_frame='bregma',
    orientation='RAS',  # Right-Anterior-Superior
    illumination_pattern=random_access_scan  # Include the illumination pattern
)

# Create microscopy channel
microscopy_channel = MicroscopyChannel(
    name='gcamp_channel',
    description='GCaMP6f channel',
    excitation_wavelength_in_nm=920.0,
    emission_wavelength_in_nm=510.0,
    indicator=indicator
)

# Create volumetric series
volume_series = VolumetricMicroscopySeries(
    name='volume_data',
    description='Volumetric imaging series',
    microscopy_channel=microscopy_channel,
    microscopy_rig=microscopy_rig,
    volumetric_imaging_space=volume_space,
    data=data,
    unit='a.u.',
    rate=5.0,  # Lower rate for volumetric imaging
    starting_time=0.0
)
nwbfile.add_acquisition(volume_series)

# Create ophys processing module
ophys_module = nwbfile.create_processing_module(
    name='ophys',
    description='Optical physiology processing module'
)

# Create 3D summary images
mean_image = SummaryImage(
    name='mean',
    description='Mean intensity projection',
    data=np.mean(data, axis=0)
)

max_image = SummaryImage(
    name='max',
    description='Maximum intensity projection',
    data=np.max(data, axis=0)
)

# Create 3D segmentation
segmentation = VolumetricSegmentation(
    name='volume_rois',
    description='3D ROI segmentation',
    volumetric_imaging_space=volume_space,
    summary_images=[mean_image, max_image]
)

# Add 3D ROIs using image masks
roi_mask = np.zeros((height, width, depths), dtype=bool)
roi_mask[256:266, 256:266, 4:6] = True  # 10x10x2 ROI
segmentation.add_roi(volume_mask=roi_mask)

# Add ROIs using voxel masks
voxel_mask = [
    [100, 100, 5, 1.0],  # x, y, z, weight
    [101, 100, 5, 1.0],
    [102, 100, 5, 1.0]
]
segmentation.add_roi(voxel_mask=voxel_mask)

# Create ROI responses
roi_region = segmentation.create_roi_table_region(
    description='All 3D ROIs',
    region=list(range(len(segmentation.id)))
)

# Extract responses (example calculation)
num_rois = len(segmentation.id)
responses = np.zeros((frames, num_rois))

for i, roi_mask in enumerate(segmentation.volume_mask[:]):
    roi_data = data[:, roi_mask]
    responses[:, i] = np.mean(roi_data, axis=1)

# Create response series
response_series = MicroscopyResponseSeries(
    name='volume_responses',
    description='Fluorescence responses from 3D ROIs',
    data=responses,
    rois=roi_region,
    unit='n.a.',
    rate=5.0,
    starting_time=0.0
    microscopy_series=volume_series
)

# Create container for response series
response_container = MicroscopyResponseSeriesContainer(
    name='volume_responses',
    microscopy_response_series=[response_series]
)

# Add segmentation and responses to ophys module
ophys_module.add(segmentation)
ophys_module.add(response_container)

# Save file
with NWBHDF5IO('volumetric_imaging.nwb', 'w') as io:
    io.write(nwbfile)

# Read file and access data
with NWBHDF5IO('volumetric_imaging.nwb', 'r') as io:
    nwbfile = io.read()

    # Access volumetric data
    imaging = nwbfile.acquisition['volume_data']
    volume_data = imaging.data[:]

    # Access ROI data
    ophys = nwbfile.processing['ophys']
    rois = ophys['volume_rois']
    roi_masks = rois.volume_mask[:]

    # Access responses
    responses = ophys['volume_responses']
    roi_data = responses['volume_responses'].data[:]

Multi-Plane Imaging

Example of multi-plane imaging with an electrically tunable lens:

from datetime import datetime
from uuid import uuid4
import numpy as np
from pynwb import NWBFile, NWBHDF5IO
from ndx_microscopy import (
    MicroscopeModel,
    Microscope,
    MicroscopyRig,
    MicroscopyChannel,
    PlanarImagingSpace,
    PlanarMicroscopySeries,
    MultiPlaneMicroscopyContainer,
    PlanarSegmentation,
    SummaryImage,
    MicroscopyResponseSeries,
    MicroscopyResponseSeriesContainer,
    SegmentationContainer,
    PlaneAcquisition
)
from ndx_ophys_devices import (
    ExcitationSourceModel,
    ExcitationSource,
    BandOpticalFilterModel,
    BandOpticalFilter,
    DichroicMirrorModel,
    DichroicMirror,
    PhotodetectorModel,
    Photodetector,
    Indicator
)

# Create NWB file
nwbfile = NWBFile(
    session_description='Multi-plane imaging session',
    identifier=str(uuid4()),
    session_start_time=datetime.now(),
    lab='Neural Circuits Lab',
    institution='University of Neuroscience',
    experiment_description='Multi-plane imaging with ETL'
)

# Set up microscope model
microscope_model = MicroscopeModel(
    name='etl-model',
    description='Two-photon microscope model with electrically tunable lens',
    model_number='etl-001',  # Example model number
    manufacturer='ImagingTech'
)
nwbfile.add_device(microscope_model)

# Set up microscope with ETL and technique
microscope = Microscope(
    name='etl-scope',
    description='Two-photon microscope with electrically tunable lens',
    serial_number='etl-serial-001',  # Example serial number
    model=microscope_model,
    technique='electrically tunable lens'  # Specify the technique used
)
nwbfile.add_device(microscope)

# Set up optical components
excitation_source_model = ExcitationSourceModel(
    name="excitation_source_model",
    manufacturer="Coherent",
    model_number="Chameleon",
    description="Excitation source model for multi-plane imaging",
    source_type="laser",
    excitation_mode="two-photon",
    wavelength_range_in_nm=[800.0, 1000.0]
)
nwbfile.add_device(excitation_source_model)

laser = ExcitationSource(
    name='laser',
    description="Excitation source for multi-plane imaging",
    serial_number="ES-SN-123456",
    model=excitation_source_model,
    intensity_in_W_per_m2=1000.0,
    exposure_time_in_s=0.001
)
nwbfile.add_device(laser)

excitation_filter_model = BandOpticalFilterModel(
    name="excitation_filter_model",
    filter_type="Bandpass",
    manufacturer="Semrock",
    model_number="FF01-920/80",
    center_wavelength_in_nm=920.0,
    bandwidth_in_nm=80.0
)
nwbfile.add_device(excitation_filter_model)

excitation_filter = BandOpticalFilter(
    name='excitation_filter',
    description="Excitation filter for multi-plane imaging",
    serial_number="EF-SN-123456",
    model=excitation_filter_model
)
nwbfile.add_device(excitation_filter)

dichroic_mirror_model = DichroicMirrorModel(
    name="dichroic_mirror_model",
    manufacturer="Semrock",
    model_number="FF695-Di02",
    cut_on_wavelength_in_nm=695.0
)
nwbfile.add_device(dichroic_mirror_model)

dichroic = DichroicMirror(
    name='primary_dichroic',
    description="Dichroic mirror for multi-plane imaging",
    serial_number="DM-SN-123456",
    model=dichroic_mirror_model
)
nwbfile.add_device(dichroic)

emission_filter_model = BandOpticalFilterModel(
    name="emission_filter_model",
    filter_type="Bandpass",
    manufacturer="Semrock",
    model_number="FF01-510/84",
    center_wavelength_in_nm=510.0,
    bandwidth_in_nm=84.0
)
nwbfile.add_device(emission_filter_model)

emission_filter = BandOpticalFilter(
    name='emission_filter',
    description="Emission filter for multi-plane imaging",
    serial_number="EF-SN-123456",
    model=emission_filter_model
)
nwbfile.add_device(emission_filter)

photodetector_model = PhotodetectorModel(
    name="photodetector_model",
    detector_type="PMT",
    manufacturer="Hamamatsu",
    model_number="R6357",
    gain=70.0,
    gain_unit="dB"
)
nwbfile.add_device(photodetector_model)

detector = Photodetector(
    name='pmt',
    description="Photodetector for multi-plane imaging",
    serial_number="PD-SN-123456",
    model=photodetector_model
)
nwbfile.add_device(detector)

# Create indicator
indicator = Indicator(
    name='gcamp6f',
    label='GCaMP6f',
    description='Calcium indicator for multi-plane imaging',
    manufacturer='Addgene',
    injection_brain_region='Visual cortex',
    injection_coordinates_in_mm=[-2.5, 3.2, 0.5]
)

# Create microscopy rig
microscopy_rig = MicroscopyRig(
    name='etl_rig',
    description='Multi-plane microscopy rig with ETL',
    microscope=microscope,
    excitation_source=laser,
    excitation_filter=excitation_filter,
    dichroic_mirror=dichroic,
    photodetector=detector,
    emission_filter=emission_filter
)

# Create ophys processing module
ophys_module = nwbfile.create_processing_module(
    name='ophys',
    description='Optical physiology processing module'
)

# Create multiple imaging planes
planar_series_list = []
segmentation_list = []
response_series_list = []
depths = [-100, -50, 0, 50, 100]  # Depths in µm

# Create illumination pattern
plane_acquisition = PlaneAcquisition(
    name=f'plane_acquisition',
    description=f'Plane acquisition',
    point_spread_function_in_um="32 um ± 1.6 um"
)

for depth in depths:

    # Create example data for this plane
    frames = 1000
    height = 512
    width = 512
    data = np.random.rand(frames, height, width)

    # Create imaging space for this depth with illumination pattern
    plane_space = PlanarImagingSpace(
        name=f'plane_depth_{depth}',
        description=f'Imaging plane at {depth} µm depth',
        pixel_size_in_um=[1.0, 1.0],
        dimensions_in_pixels=[height, width],
        origin_coordinates=[-1.2, -0.6, depth/1000],  # Convert to mm
        location='Visual cortex',
        reference_frame='bregma',
        orientation='RAS',
        illumination_pattern=plane_acquisition  # Include the illumination pattern
    )

    # Create microscopy channel for this plane
    microscopy_channel = MicroscopyChannel(
        name=f'gcamp_channel_{depth}',
        description=f'GCaMP6f channel at {depth} µm depth',
        excitation_wavelength_in_nm=920.0,
        emission_wavelength_in_nm=510.0,
        indicator=indicator
    )

    # Create imaging series for this plane
    plane_series = PlanarMicroscopySeries(
        name=f'imaging_depth_{depth}',
        description=f'Imaging data at {depth} µm depth',
        microscopy_channel=microscopy_channel,
        microscopy_rig=microscopy_rig,
        planar_imaging_space=plane_space,
        data=data,
        unit='a.u.',
        conversion=1.0,
        offset=0.0,
        rate=30.0,
        starting_time=0.0
    )
    planar_series_list.append(plane_series)

    # Create summary images for this plane
    mean_image = SummaryImage(
        name=f'mean_{depth}',
        description=f'Mean intensity projection at {depth} µm',
        data=np.mean(data, axis=0)
    )

    max_image = SummaryImage(
        name=f'max_{depth}',
        description=f'Maximum intensity projection at {depth} µm',
        data=np.max(data, axis=0)
    )

    # Create segmentation for this plane
    segmentation = PlanarSegmentation(
        name=f'rois_{depth}',
        description=f'ROI segmentation at {depth} µm',
        planar_imaging_space=plane_space,
        summary_images=[mean_image, max_image]
    )

    # Add ROIs
    roi_mask = np.zeros((height, width), dtype=bool)
    roi_mask[256:266, 256:266] = True
    segmentation.add_roi(image_mask=roi_mask)

    segmentation_list.append(segmentation)

    # Create ROI responses
    roi_region = segmentation.create_roi_table_region(
        description=f'ROIs at {depth} µm',
        region=list(range(len(segmentation.id)))
    )

    # Extract responses
    num_rois = len(segmentation.id)
    responses = np.zeros((frames, num_rois))

    for i, roi_mask in enumerate(segmentation.image_mask[:]):
        roi_data = data[:, roi_mask]
        responses[:, i] = np.mean(roi_data, axis=1)

    # Create response series
    response_series = MicroscopyResponseSeries(
        name=f'responses_{depth}',
        description=f'Fluorescence responses at {depth} µm',
        data=responses,
        rois=roi_region,
        unit='n.a.',
        rate=30.0,
        starting_time=0.0
        microscopy_series=plane_series
    )
    response_series_list.append(response_series)

# Create containers
multi_plane_container = MultiPlaneMicroscopyContainer(
    name='multi_plane_data',
    planar_microscopy_series=planar_series_list
)
nwbfile.add_acquisition(multi_plane_container)

segmentation_container = SegmentationContainer(
    name='plane_segmentations',
    segmentations=segmentation_list
)
ophys_module.add(segmentation_container)

response_container = MicroscopyResponseSeriesContainer(
    name='plane_responses',
    microscopy_response_series=response_series_list
)
ophys_module.add(response_container)

# Save file
with NWBHDF5IO('multi_plane_imaging.nwb', 'w') as io:
    io.write(nwbfile)

# Read file and access data
with NWBHDF5IO('multi_plane_imaging.nwb', 'r') as io:
    nwbfile = io.read()

    # Access multi-plane data
    multi_plane = nwbfile.acquisition['multi_plane_data']

    # Access specific plane data
    plane_0 = multi_plane.planar_microscopy_series['imaging_depth_0']
    plane_data = plane_0.data[:]

    # Access ROI data
    ophys = nwbfile.processing['ophys']
    segmentations = ophys['plane_segmentations']
    rois_0 = segmentations['rois_0']
    roi_masks = rois_0.image_mask[:]

    # Access responses
    responses = ophys['plane_responses']
    responses_0 = responses['responses_0']
    roi_data = responses_0.data[:]