@@ -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 | |||
@@ -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') |
@@ -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 |
@@ -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) |