Browse Source

Pre-flash implentation, code cleaning

master
Carlos Reding 1 year ago
parent
commit
36c47e8851
4 changed files with 127 additions and 106 deletions
  1. 5
    19
      puppeteer/__init__.py
  2. 32
    65
      puppeteer/_core.py
  3. 5
    5
      puppeteer/_dataparser/__init__.py
  4. 85
    17
      puppeteer/_dataparser/data_handler.py

+ 5
- 19
puppeteer/__init__.py View File

@@ -8,7 +8,7 @@
"""

from ._core import SetupDevice, SetProtocol
from ._dataparser import export_img_data, export_queue, house_keeper
from ._dataparser import export_img_data, report_progress, house_keeper
import time, subprocess, pickle, os


@@ -38,7 +38,8 @@ def Controller(USR_INSTRUCTIONS, Connection, cancel_switch, ConnectionStatus,
# Configure protocol. Additional field: notes='' as **kwarg
Protocol = SetProtocol(with_device=Box, filters=user_filters,
and_wavelenghts=USR_INSTRUCTIONS.user_wavelengths,
well_number=96, protocol_name='default_protocol')
well_number=96, PREHEAT_TIME=15.0,
protocol_name='default_protocol')
if BoxQueue:
BoxQueue.put([Box, Protocol, USR_INSTRUCTIONS.QueueFile]) # Export `Protocol' before Run begins.
# Run Protocol. `Connection' is no longer a dependency of this method as
@@ -68,7 +69,7 @@ def ProtocolTest(Connection, cancel_switch, DoneFlag):
# Configure protocol. Additional field: notes='' as **kwarg
Protocol = SetProtocol(with_device=Box, filters=tuple(Box._filter_set),
and_wavelenghts=tuple(range(4)), well_number=96,
protocol_name='test_protocol')
PREHEAT_TIME=15.0, protocol_name='test_protocol')
# Run Protocol. `Connection' is no longer a dependency of this method as
# the log messages produced interfere with the GUI during a protocol run.
Protocol.Run(light_range=(255,),
@@ -128,7 +129,7 @@ def QueueStatus(QueueFile, Connection, BUFFER_SIZE=64):
Checks `QueueFile' to report the progress of an experimental protocol
back to the user.
"""
export_queue(QueueFile, Connection, BUFFER_SIZE) # TODO: change method name from export_queue to report_progress?
report_progress(QueueFile, Connection, BUFFER_SIZE)

def ExportData(Protocol, ProcessData, QueueFile, Connection, Status,
BUFFER_SIZE=64, Test=False):
@@ -177,18 +178,3 @@ def CancelProtocol(with_device=None, Connection=None, ConnectionStatus=None):
Routine=".cancel_protocol.sh"
subprocess.call(["bash", "-c", LiMO_Path + Routine])

def CameraRepetitions():
"""
Routine to detect the number of replicates defined by the user through
the graphical user interface (GUI). If this setting does not exist,
default to 3.
"""
if os.path.exists(Path + hw_fName):
hwSettings = pickle.load(open(Path + hw_fName, "rb"))
Replicates = hwSettings["cameraRepetitions"]
Lag_per_replicate = hwSettings["SHUTTER_SPEED"]
else:
Replicates = 3
Lag_per_replicate = 0.25
return Replicates, Lag_per_replicate


+ 32
- 65
puppeteer/_core.py View File

@@ -304,9 +304,6 @@ class SetupDevice:
Initialise the light modulator setting the servo in a predefined
location. Defaults to *Filter_2* (centred to avoid accidental
damage).
PREHEAT_TIME : float
Sets the time that LEDs will be switched _ON_ during preheating.
Defaults to 5s.
"""
def set_filter(self, selected_filter, current_pwm=0):
"""
@@ -720,8 +717,7 @@ class SetupDevice:
use_video_port=False, quality=90)
return self

def __init__(self, panel_type="rgb", default_filter="Filter_2",
PREHEAT_TIME=5.0):
def __init__(self, panel_type="rgb", default_filter="Filter_2"):
""" Initialise the class _SetupDevice_. """
self._pic_file_name = 'Calibration_Int_'
self._pic_ext = '.jpg' # JPEG is lossy but hardware-accelerated.
@@ -736,7 +732,6 @@ class SetupDevice:
self._FILTER_PIN = FILTER_PIN
self._HEATER_PIN = HEATER_PIN
self.__SHUTTER_SPEED__ = __SHUTTER_SPEED__
self.__PREHEAT_TIME__ = PREHEAT_TIME
# Initialise hardware.
self._light_panel, self._heater,\
@@ -747,10 +742,10 @@ class SetupDevice:
self._current_state = [] # Avoids moving the servo unnecessarily. PREV: self.set_filter(default_filter)
# Available filters.
self._filter_set = self.set_filter("expose_filters")
# Preheat LEDs
self._modulate_LED_intensity(255, channel=3) # channel 3 == WHITE (all LEDs).
t.sleep(PREHEAT_TIME)
self._modulate_LED_intensity(0, channel=3) # channel 3 == WHITE (all LEDs).
# Preheat LEDs # TODO: Implement this before _every_ read.
# self._modulate_LED_intensity(255, channel=3) # channel 3 == WHITE (all LEDs).
# t.sleep(PREHEAT_TIME)
# self._modulate_LED_intensity(0, channel=3) # channel 3 == WHITE (all LEDs).
class SetProtocol:
"""
@@ -774,6 +769,9 @@ class SetProtocol:
well_number : int
Number of wells for the microtitre plate used. Possible values are
6, 12, 24, 48, and 96. Defaults to *96*.
PREHEAT_TIME : float
Sets the time that LEDs will be switched _ON_ during preheating.
Defaults to *15s*.
protocol_name : string, optional
Name for the directory where data will be stored. Defaults to
*default_protocol*. Mostly useful if the light modulator is
@@ -782,53 +780,9 @@ class SetProtocol:
Additional comment or note to add to *`protocol_name`*. Mostly
useful if the light modulator is controlled from a CLI.
"""

def _data_acquisition(self, light_range, time, flt, wavelength,
referenceRead):
"""
Private method. Controls the data acquisition process.
Parameters
----------
light_range : ndarray
Light intensities set by the user.
time : float
Time since the initiation of the protocol, in seconds.
flt : string
Filter used during data acquisition.
wavelength : int
Value between 0 and 3, where 0 means *Filter_1*, 1 means
*Filter_2*, 2 means *Filter_3* and 3 means *No_Filter*.
referenceRead : bool, optional
Save an additional picture if the current light intensity is
also the highest light intensity set by the user. Defaults to
*False*.
"""
photo_path = self._dir_info.root_path + self._dir_info.protocol_path +\
self._dir_info.img_data_path + flt + "/"
# If no custom names provided, use a preset name.
if self._notes == '':
file_name = self._pic_file_name + str(time) + "s_"
else:
file_name = self._pic_file_name + str(time) + "s_" + self._notes

# This method optimises photography time by 1) using JPEG hardware
# acceleration, 2) avoiding the overheads of initialising cameras'
# still pipeline (init still port + encoder for every picture) and 3)
# eliminating overheads of initialising preview port. use_video_port
# can accelerate this further, but at the expense of a lower quality
# image.
self._device._camera.capture_sequence(img_acquisition_routine(self,
light_range,
wavelength,
photo_path,
file_name,
flag=referenceRead),
format='jpeg', burst=True,
use_video_port=False, quality=90)
return 0

def _data_acquisition_triplicate(self, light_range, time, flt, wavelength,
referenceRead):
"""
Private method. Controls the data acquisition process using as many
replicates as specified by the user.
@@ -878,7 +832,6 @@ class SetProtocol:
str(light_intensity).zfill(3)+ self._notes +\
self._pic_ext
cv2.imwrite(photo_path + file_name, mean_img)
del mean_img # Free memory.
return 0

def _detect_well_info(self, recalculate_wells,
@@ -1011,6 +964,22 @@ class SetProtocol:
info.insert(0, current_time)
fOut.writelines(','.join(str(entry) for entry in info) + "\n")

def _preheat_channel(self, PREHEAT_TIME=None,
wavelength=None):
"""
Private method. Turn lights on for a pre-defined amount of time
to heat up the LEDs. This step is called before data acquisition,
which includes a step to turn LEDs back off.
..note::
LED are sensitive to changes in temperature. By heating
them up right before a read, we seek to harmonise the
working conditions of all LEDs.
"""
if wavelength is not None and PREHEAT_TIME is not None:
self._device._modulate_LED_intensity(255, channel=wavelength)
t.sleep(PREHEAT_TIME)

def Run(self, assay_length="00:00:00", read_every="00:00:00",
temperature=None, light_range=(0, 255), recalculate_wells=False,
process_data=True, Queue=None, event=None):
@@ -1107,7 +1076,7 @@ class SetProtocol:
if Queue[read] == 0.0:
t.sleep(Queue[read])
else:
t.sleep((Queue[read]) - reading_time)
t.sleep(Queue[read] - reading_time)
current_time = int(t.time() - initiation_time) # Makes t0 = ~0s.
# Update temperature (even with `ambient', to keep track of it).
@@ -1153,16 +1122,14 @@ class SetProtocol:
self._device._camera = _img_settings(self._device._camera,
Filter=flt)
servo_state = self._device.set_filter(flt, current_pwm=servo_state)
# self._data_acquisition(light_range, current_time, flt,
# wavelength, referenceRead)
self._data_acquisition_triplicate(light_range, current_time, flt,
wavelength, referenceRead)
self._preheat_channel(self.__PREHEAT_TIME__, wavelength) # Preheat LEDs.
self._data_acquisition(light_range, current_time, flt,
wavelength, referenceRead)
# Turn LED off
self._device._modulate_LED_intensity(0, channel=wavelength)
# Process data (inc. exporting data)
if process_data is True:
# TODO: Compare this to when flag is FALSE.
log = str("Data acquisition at " + str(current_time) +
"s completed. Exporting numeric data to client...")
print(log)
@@ -1180,13 +1147,12 @@ class SetProtocol:
current_time, connection,
BUFFER_SIZE, status)
else:
pass
reading_time = t.time() - init_time # Update reading_time
# Wait for the next read. Queue waiting time _already_ in seconds.
# Account for reading time, otherwise waits incrementally longer.
if ReadsNum > 1:
QueueInfo[read][StatusID] = "Completed" # Update Queue
self._update_queue(Queue, QueueInfo, QueueFile, read)
reading_time = t.time() - init_time
# PROTOCOL FINISHED:
if temperature != 0:
@@ -1198,13 +1164,14 @@ class SetProtocol:
return 0

def __init__(self, with_device=None, filters=("Filter_2",),
and_wavelenghts=(3,), well_number=96,
and_wavelenghts=(3,), well_number=96, PREHEAT_TIME=15.0,
protocol_name="default_protocol", **kwargs):
""" Initialise the class _SetupProtocol_. """
if with_device is not None:
self._device = with_device
self._filters = filters
self._wavelengths = and_wavelenghts
self.__PREHEAT_TIME__ = PREHEAT_TIME
# Additional notes, if they exist.
if 'notes' in kwargs.keys():
self._notes = kwargs.get('notes')

+ 5
- 5
puppeteer/_dataparser/__init__.py View File

@@ -2,9 +2,9 @@
The `dataparser' module is responsible for collecting biological data from
photographs stored in the light modulator. Currently this step is performed
on the GUI side, but the light modulator can also capable to perform this
task.\n
task.
PlateSniffer(dir_info, plate_reference, filter, debug=False)\n\t
PlateSniffer(dir_info, plate_reference, filter, debug=False)
Routine to detect well location from image data. Using maximally stable
extreme regions (MSRE) as core blob detection algorithm, this routine
will detect the centre of each well of a microtitre plate. NOTE THAT
@@ -13,10 +13,10 @@
image and re-calculates well locations. The method `detect_wells'
requires a directory structure (`dir_info'), plate of reference, and
filter as inputs. There's an optional `debug' flag, which defaults to
false.\n
false.
PlateParser(dir_info, wells, filter, row_labels, col_labels, img_limits,
time, debug=False)\n\t
time, debug=False)
Collect biolotical data from photographs. `wells' is an object that
contains wells (x,y) coordinates. The objects `row_labels' and
`col_labels' are used to classify the well location following
@@ -27,4 +27,4 @@
from .plate_sniffer import detect_wells as wells_id
from .plate_reader import PlateParser as read_plate
from .data_handler import export_numeric_data, export_img_data,\
export_queue, house_keeper
report_progress, house_keeper

+ 85
- 17
puppeteer/_dataparser/data_handler.py View File

@@ -4,18 +4,47 @@ import shutil as sh
import scipy.io as sio

def _get_response(Connection, BUFFER_SIZE):
"""
Private method. Retrieves data transferred from the graphical user
interface (GUI).
Parameters
----------
connection : callable
*Socket* object used to communicate with the graphical user
interface (GUI).
BUFFER_SIZE : int
Chunk of data transferred at once, in bytes. Defaults to *64*.
"""
return Connection.recv(BUFFER_SIZE)

def export_numeric_data(dir_info, raw_data, flt, time, connection):
"""
Take the above container raw_data container, containing the readings
at al inensities, as a matlab .mat file. The data is stored for each
pixel within each well, with format nxm where n is the number of
channels (RGB) and m the number of pixels per well (i.e. csv files
contain 3*intensities rows and as many columns as pixels per well).
The data is already sorted in the container, so that entry 0 for light
intensity corresponds to lightintensity = 0 and well entry 0 corresponds
with well 1 (A1). WARNING! requires '-mat' in matlab.
Store the numeric data (_resulting from processing the data locally in
the light modulator_) as a MATLAB .mat file and transfer it to the user.
.. note::
Requires '-mat' if importing into MATLAB.
Parameters
----------
dir_info : structure
Structure object that contains directory information: root
directory, protocol directory, data directory, etc.
raw_data : ndarray
Fifth-dimmensional array with structure *MxNxIxJxK*, where *M*
is the number of light intensities used, *N* the number of
wells, *I* the number of channels in the photographic data, *J* the
number of pixels per well, and *K* a matrix containing the raw
(numeric) data.
flt : string
Filter used. Options are *Filter_1*, *Filter_2*, *Filter_3*,
and *No_Filter*.
time : float
Time since the initiation of the protocol, in seconds.
connection : callable
*Socket* object used to communicate with the graphical user
interface (GUI).
"""
localPath = dir_info.root_path + dir_info.protocol_path +\
dir_info.numeric_data_path + "/"
@@ -51,14 +80,22 @@ def export_numeric_data(dir_info, raw_data, flt, time, connection):

def export_img_data(dir_info, flt, connection, BUFFER_SIZE):
"""
Take the above container raw_data container, containing the readings
at al inensities, as a matlab .mat file. The data is stored for each
pixel within each well, with format nxm where n is the number of
channels (RGB) and m the number of pixels per well (i.e. csv files
contain 3*intensities rows and as many columns as pixels per well).
The data is already sorted in the container, so that entry 0 for light
intensity corresponds to lightintensity = 0 and well entry 0 corresponds
with well 1 (A1). WARNING! requires '-mat' in matlab.
Transfers raw photographic data to the user's computer (i.e. laptop)
for data processing.
Parameters
----------
dir_info : structure
Structure object that contains directory information: root
directory, protocol directory, data directory, etc.
flt : string
Filter used. Options are *Filter_1*, *Filter_2*, *Filter_3*,
and *No_Filter*.
connection : callable
*Socket* object used to communicate with the graphical user
interface (GUI).
BUFFER_SIZE : int
Chunk of data transferred at once, in bytes. Defaults to *64*.
"""
if flt is None:
localPath = dir_info.root_path + dir_info.protocol_path
@@ -115,7 +152,21 @@ def export_img_data(dir_info, flt, connection, BUFFER_SIZE):
if status.is_set():
connection.sendall(log.encode())

def export_queue(QueueFile, connection, BUFFER_SIZE):
def report_progress(QueueFile, connection, BUFFER_SIZE):
"""
Generate a list with the status of the Queue, and send it to the
user. Useful to check run progress.
Parameters
----------
QueueFile : string
Absolute path to the file storing the queue data.
connection : callable
*Socket* object used to communicate with the graphical user
interface (GUI).
BUFFER_SIZE : int
Chunk of data transferred at once, in bytes. Defaults to *64*.
"""
BUFFER = list() # Create buffer to send.
with open(QueueFile, "r") as Queue: # Load QueueFile.
for job in Queue:
@@ -137,6 +188,23 @@ def export_queue(QueueFile, connection, BUFFER_SIZE):
print(log)

def house_keeper(dir_info, QueueFile, connection, BUFFER_SIZE):
"""
Method to wipe any data stored locally. Only called when the user
confirms all data have been transferred successfully.
Parameters
----------
dir_info : structure
Structure object that contains directory information: root
directory, protocol directory, data directory, etc.
QueueFile : string
Absolute path to the file storing the queue data.
connection : callable
*Socket* object used to communicate with the graphical user
interface (GUI).
BUFFER_SIZE : int
Chunk of data transferred at once, in bytes. Defaults to *64*.
"""
localPath = dir_info.root_path + dir_info.protocol_path + "/"
print("IMG data transferred successfuly. Erasing local data...")
cli = _get_response(connection, BUFFER_SIZE)

Loading…
Cancel
Save