Cytation 1 and 5#

Cytation is a plate reader / imager combination.

See installation instructions here.

%load_ext autoreload
%autoreload 2
import matplotlib.pyplot as plt
from pylabrobot.plate_reading import ImageReader, ImagingMode, Objective
from pylabrobot.plate_reading import Cytation5Backend
# for imaging, we need an environment variable to point to the Spinnaker GenTL file
import os
os.environ["SPINNAKER_GENTL64_CTI"] = "/usr/local/lib/spinnaker-gentl/Spinnaker_GenTL.cti"
pr = ImageReader(name="PR", size_x=0,size_y=0,size_z=0, backend=Cytation5Backend())
await pr.setup(use_cam=True)
await pr.backend.get_firmware_version()
'1320200  Version 2.07'
await pr.backend.get_current_temperature()
24.5
await pr.open(slow=False)

Before closing, assign a plate to the plate reader. This determines the spacing of the loading tray in the machine, as well as the positioning of wells where spectrophotometric measurements and pictures will be taken.

from pylabrobot.resources import CellVis_24_wellplate_3600uL_Fb
plate = CellVis_24_wellplate_3600uL_Fb(name="plate")
pr.assign_child_resource(plate)
await pr.close(slow=True)

Plate reading#

Note: these measurements were taken with a 96 well plate.

data = await pr.read_absorbance(wavelength=434)
plt.imshow(data)
<matplotlib.image.AxesImage at 0x1353cc790>
../_images/5eee8870eb453c15f3efb3faee31af79a34649e97ca88d5a88516c0e21050766.png
data = await pr.read_fluorescence(
  excitation_wavelength=485, emission_wavelength=528, focal_height=7.5
)
plt.imshow(data)
<matplotlib.image.AxesImage at 0x16e144850>
../_images/c6bed6539984cc3e8c8ef03fb086557665c1b50f355ad89dfc39239bc993d2a1.png
data = await pr.read_luminescence(focal_height=4.5)
plt.imshow(data)
<matplotlib.image.AxesImage at 0x16e1a83d0>
../_images/e435cc3192c6734bc98ad01fc5e6fa6361820552e6d55feb97a96edcf5f3d4f7.png

Shaking#

await pr.backend.shake(
  shake_type=Cytation5Backend.ShakeType.LINEAR,
  frequency=4  # linear frequency in mm, 1 <= frequency <= 6
)
await pr.backend.stop_shaking()

Imaging#

Installation#

See Cytation 5 imager installation instructions.

Usage#

Supported objectives:

  • O_4x_PL_FL_PHASE

  • O_20x_PL_FL_PHASE

  • O_40x_PL_FL_PHASE

Supported imaging modes:

  • C377_647

  • C400_647

  • C469_593

  • ACRIDINE_ORANGE

  • CFP

  • CFP_FRET_V2

  • CFP_YFP_FRET

  • CFP_YFP_FRET_V2

  • CHLOROPHYLL_A

  • CY5

  • CY5_5

  • CY7

  • DAPI

  • GFP

  • GFP_CY5

  • OXIDIZED_ROGFP2

  • PROPOIDIUM_IODIDE

  • RFP

  • RFP_CY5

  • TAG_BFP

  • TEXAS_RED

  • YFP

ims = await pr.capture(
  well=(1, 2),
  mode=ImagingMode.BRIGHTFIELD,
  objective=Objective.O_4x_PL_FL_PHASE,
  focal_height=0.833,
  exposure_time=5,
  gain=16,
  led_intensity=10,
)
plt.imshow(ims[0], cmap="gray", vmin=0, vmax=255)
<matplotlib.image.AxesImage at 0x144295ed0>
../_images/84ec453a3d81e52d7a8e67f9821def6be938dc77782f0e875b3f9ad0b41ca8af.png

Autofocus#

Auto-focus can be configured with pr.backend.set_auto_focus_search_range where the parameters are the minimum and maximum focus heights in mm respectively.

pr.backend.set_auto_focus_search_range(0.6, 1)
ims = await pr.capture(
  well=(1, 2),
  mode=ImagingMode.BRIGHTFIELD,
  objective=Objective.O_4x_PL_FL_PHASE,
  focal_height="auto", # <----- auto focus
  exposure_time=5,
  gain=16,
  led_intensity=10
)
plt.imshow(ims[0], cmap="gray", vmin=0, vmax=255)
<matplotlib.image.AxesImage at 0x177d24400>
../_images/f15476c6b71ea708bacafef6f2b552264d3aa08e13f144991f3f8cd5e9a0e5e2.png

Exporting#

.capture returns a List[Image] where Image = List[List[float]] where each item is 0 <= x <= 255. You can export this to an image file in many ways. Here’s one example of exporting to a 16-bit tiff:

from PIL import Image
import numpy as np

array = np.array(ims[0], dtype=np.float32)
array_uint16 = (array * (65535 / 255)).astype(np.uint16)
Image.fromarray(array_uint16).save("test.tiff")

Coverage#

Use the coverage parameter to take multiple pictures of the same well. The coverage parameter is an tuple (num_rows, num_columns) or "full".

When we send the exact same commands as gen5.exe, with overlap = 0, we still get some overlap in the resulting images. This is probably because gen5.exe crops. For now, we don’t support stitching or cropping in PLR yet, but we will in the future.

num_rows = 4
num_cols = 4

ims = await pr.capture(
  well=(1, 2),
  mode=ImagingMode.BRIGHTFIELD,
  objective=Objective.O_4x_PL_FL_PHASE,
  focal_height=0.833,
  exposure_time=5,
  gain=16,
  coverage=(num_rows, num_cols),
  center_position=(-6, 0),
)
len(ims)
16
fig = plt.figure(figsize=(12, 8))
for row in range(num_rows):
  for col in range(num_cols):
    plt.subplot(num_rows, num_cols, row * num_cols + col + 1)
    plt.imshow(ims[row * num_cols + col], cmap="gray", vmin=0, vmax=255)
    plt.axis("off")
../_images/62314e3c25ace8ec1d024eeba6bd757668aa5358a0af47e328f3eb034969ee35.png

How long does it take to capture an image?#

import time
import numpy as np

exposure_time = 1904

# first time setting imaging mode is slower
_ = await pr.capture(well=(1, 1), mode=ImagingMode.BRIGHTFIELD, focal_height=3.3, exposure_time=exposure_time)

l = []
for i in range(10):
  t0 = time.monotonic_ns()
  _ = await pr.capture(well=(1, 1), mode=ImagingMode.BRIGHTFIELD, focal_height=3.3, exposure_time=exposure_time)
  t1 = time.monotonic_ns()
  l.append((t1 - t0) / 1e6)

print(f"{np.mean(l):.2f} ms ± {np.std(l):.2f} ms")
print(f"Overhead: {(np.mean(l) - exposure_time):.2f} ms")
2089.59 ms ± 15.72 ms
Overhead: 185.59 ms