You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

__init__.py 8.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. """
  2. Puppeteer is the core library behind the light modulator, built to
  3. create a simple, low level interface with the device. It provides two
  4. classes that will let any user program the light modulator: SetupDevice()
  5. and SetProtocol().
  6. TODO: better documentation here, format as reStructured Text.
  7. """
  8. from ._core import SetupDevice, SetProtocol
  9. from ._dataparser import export_img_data, export_queue, house_keeper
  10. import time, subprocess, pickle, os
  11. # Location of hardware settings:
  12. hw_fName = "/hwsettings.hws"
  13. Path = os.getcwd() + "/.srv"
  14. def Controller(USR_INSTRUCTIONS, Connection, cancel_switch, ConnectionStatus,
  15. BoxQueue, ProtocolStatus):
  16. """
  17. Take user instructions and configure the instrument accordingly. If
  18. a connection is given, it will be used for synchronising reports and
  19. data transfer.
  20. Returns `Protocol', which is the protocol that has been programmed and
  21. carries the neccessary directory information to process and export all
  22. data.
  23. """
  24. # Initialise instrument.
  25. Box = SetupDevice(panel_type='rgb', default_filter='No_Filter')
  26. # Configure filters
  27. device_filters = tuple(Box._filter_set)
  28. user_filters = [device_filters[f] for f in USR_INSTRUCTIONS.user_filters]
  29. if len(user_filters) == 1:
  30. user_filters = tuple(user_filters) # make singleton (interable. `int' is not).
  31. # Configure protocol. Additional field: notes='' as **kwarg
  32. Protocol = SetProtocol(with_device=Box, filters=user_filters,
  33. and_wavelenghts=USR_INSTRUCTIONS.user_wavelengths,
  34. well_number=96, protocol_name='default_protocol')
  35. if BoxQueue:
  36. BoxQueue.put([Box, Protocol, USR_INSTRUCTIONS.QueueFile]) # Export `Protocol' before Run begins.
  37. # Run Protocol. `Connection' is no longer a dependency of this method as
  38. # the log messages produced interfere with the GUI during a protocol run.
  39. Protocol.Run(assay_length=USR_INSTRUCTIONS.length,
  40. read_every=USR_INSTRUCTIONS.frequency,
  41. temperature=USR_INSTRUCTIONS.temperature,
  42. light_range=USR_INSTRUCTIONS.light_intensities,
  43. recalculate_wells=False,
  44. process_data=USR_INSTRUCTIONS.process_data,
  45. Queue=(USR_INSTRUCTIONS.Queue,
  46. USR_INSTRUCTIONS.QueueInfo,
  47. USR_INSTRUCTIONS.QueueFile),
  48. event=cancel_switch)
  49. # Safely terminate instrument.
  50. Box.terminate_device(Connection, False) # False to forcibly avoid reporting in.
  51. # Data must be exported _outside_ this function to allow GUI <-> Box
  52. # communication during a protocol Run.
  53. ProtocolStatus.set()
  54. def ProtocolTest(Connection, cancel_switch, DoneFlag):
  55. """
  56. Run a mock protocol using _all_ filters and wavelengths.
  57. """
  58. # Initialise instrument.
  59. Box = SetupDevice(panel_type='rgb', default_filter='No_Filter')
  60. # Configure protocol. Additional field: notes='' as **kwarg
  61. Protocol = SetProtocol(with_device=Box, filters=tuple(Box._filter_set),
  62. and_wavelenghts=tuple(range(4)), well_number=96,
  63. protocol_name='test_protocol')
  64. # Run Protocol. `Connection' is no longer a dependency of this method as
  65. # the log messages produced interfere with the GUI during a protocol run.
  66. Protocol.Run(light_range=(255,),
  67. temperature=0,
  68. recalculate_wells=True,
  69. process_data=False,
  70. event=cancel_switch)
  71. # Safely terminate instrument.
  72. Box.terminate_device(Connection, False) # False to forcibly avoid reporting in.
  73. # Data must be exported _outside_ this function to allow GUI <-> Box
  74. # communication during a protocol Run.
  75. return Protocol
  76. def HardwareTest(Connection, ConnectionStatus):
  77. """
  78. Initialises the instrument, check for errors, and store the result in
  79. a log. Once the log is generated, release the hardware to allow future
  80. use.
  81. """
  82. Box = SetupDevice(panel_type='rgb', default_filter='No_Filter')
  83. Box.terminate_device(Connection, ConnectionStatus)
  84. def TemperatureTest(Connection, ConnectionStatus):
  85. """
  86. Routine to test whether the heating unit and temperature sensors operate
  87. correctly. By default, this routine will:\n
  88. 1) Measure current temperature.
  89. 2) Enable heater for a maximum of 10s.
  90. 3) Measure the temperature once more.\n
  91. Both temperatures (mean ± standard deviation) are reported back to the
  92. user, who will decide if the test past based on whether the clicky sound
  93. made by the heater can be heard and/or abnormalities in the temperature
  94. reported (NaNs, abnormal standard deviations or means).
  95. """
  96. # Initialise instrument.
  97. Box = SetupDevice(panel_type='rgb', default_filter='No_Filter')
  98. # Measure current temperature and store it in `Box' log.
  99. CurrentTemperature = Box.report_temperature()
  100. Box._status_list.append(tuple(["Current Temperature: ",
  101. str(CurrentTemperature.mean().round(decimals=2)) + "±" +
  102. str(CurrentTemperature.std().round(decimals=2))]))
  103. # Turn heater ON for 10.0 seconds to let `Box' warm up.
  104. Box._heater.write(Box._HEATER_PIN, 1) # ON
  105. time.sleep(10.0)
  106. Box._heater.write(Box._HEATER_PIN, 0) # OFF
  107. # Measure new temperature and store in `Box' log.
  108. NewTemperature = Box.report_temperature() # Retrieve temperature after heater ON.
  109. Box._status_list.append(tuple(["New Temperature: ",
  110. str(NewTemperature.mean().round(decimals=2)) + "±" +
  111. str(NewTemperature.std().round(decimals=2))]))
  112. # Release hardware and report.
  113. Box.terminate_device(Connection, ConnectionStatus)
  114. def QueueStatus(QueueFile, Connection, BUFFER_SIZE=64):
  115. """
  116. Checks `QueueFile' to report the progress of an experimental protocol
  117. back to the user.
  118. """
  119. export_queue(QueueFile, Connection, BUFFER_SIZE) # TODO: change method name from export_queue to report_progress?
  120. def ExportData(Protocol, ProcessData, QueueFile, Connection, Status,
  121. BUFFER_SIZE=64, Test=False):
  122. """
  123. Export IMG or numerical information. Use variable `Status' to detect
  124. wheter a user is connected through GUI or not.
  125. """
  126. # Is GUI connected? If so, send data. Otherwise, _WAIT_.
  127. if Status.is_set():
  128. log = str("GUI connected, proceeding.\n")
  129. print(log)
  130. elif not Status.is_set():
  131. log = str("No GUI connected. Waiting for one...\n")
  132. print(log)
  133. Status.wait() # This will pause the code until user connects.
  134. # Export IMG data
  135. if ProcessData:
  136. pass
  137. else:
  138. for Filter in Protocol._filters:
  139. export_img_data(Protocol._dir_info, Filter, Connection, BUFFER_SIZE)
  140. # Export temperature record _ONLY IF_ running a normal protocol.
  141. if Test is False:
  142. export_img_data(Protocol._dir_info, None, Connection, BUFFER_SIZE)
  143. if Status.is_set:
  144. print("All done.")
  145. Connection.sendall("::Protocol run successfully::".encode())
  146. # House keeping. Always. Test or not.
  147. house_keeper(Protocol._dir_info, QueueFile, Connection, BUFFER_SIZE)
  148. def CancelProtocol(with_device=None, Connection=None, ConnectionStatus=None):
  149. """
  150. Cancels a running protocol with IMMEDIATE effect. The use of flags with
  151. multithreading cannot cancel a running protocol until the next iteration
  152. be it within a minute or an hour. `CancelProtocol' soft-restarts the
  153. light modulator and, therefore, protocols are cancelled immediately.
  154. """
  155. Box = with_device
  156. # Stop hardware. Fixes heating being left on. Note that Connection and
  157. # ConnectionStatus are set to `None', meaning no logs will be reported back
  158. # to the GUI.
  159. if Box is not None:
  160. Box.terminate_device(Connection, ConnectionStatus)
  161. # Restart server (workaround, as python won't recognise BASH functions).
  162. LiMO_Path = "/home/client/.srv/"
  163. Routine=".cancel_protocol.sh"
  164. subprocess.call(["bash", "-c", LiMO_Path + Routine])
  165. def CameraRepetitions():
  166. """
  167. Routine to detect the number of replicates defined by the user through
  168. the graphical user interface (GUI). If this setting does not exist,
  169. default to 3.
  170. """
  171. if os.path.exists(Path + hw_fName):
  172. hwSettings = pickle.load(open(Path + hw_fName, "rb"))
  173. Replicates = hwSettings["cameraRepetitions"]
  174. Lag_per_replicate = hwSettings["SHUTTER_SPEED"]
  175. else:
  176. Replicates = 3
  177. Lag_per_replicate = 0.25
  178. return Replicates, Lag_per_replicate