From 628159e39a06da2a75b3b17f8dbf05cfc57de834 Mon Sep 17 00:00:00 2001 From: Blaise Thompson Date: Tue, 10 Apr 2018 09:55:59 -0500 Subject: 2018-04-10 09:55 --- acquisition/chapter.tex | 172 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 139 insertions(+), 33 deletions(-) (limited to 'acquisition/chapter.tex') diff --git a/acquisition/chapter.tex b/acquisition/chapter.tex index 4446c0f..5981e22 100644 --- a/acquisition/chapter.tex +++ b/acquisition/chapter.tex @@ -181,6 +181,8 @@ In this case, the displayed pixel (index 6, 40) took 2.448 seconds to acquire. \end{figure} \end{landscape} +% TODO: queue figure + \section{Internal structure} % =================================================================== In this section I discuss the internal structure of PyCMDS. % @@ -221,7 +223,7 @@ Without any special protection, two threads have no reason not to simultaneously same location in memory. % If a delay stage is writing its position to memory as a 64-bit double at the same time as the acquisition thread reads that memory address, the acquisition thread will read in nonsense (or -worse), it will crash). % +worse, PyCMDS will crash). % So some strategy is needed to ensure that threads respect each other. % The Mutex design allows threads to ``lock'' an object such that it cannot be modified by a different thread. % @@ -255,7 +257,7 @@ The Qt signals and slots system massively simplifies programming within PyCMDS. Note that multithreading is very different from multiprocessing. % -\subsection{High level objects} % ---------------------------------------------------------------- +\subsection{Abstraction and inheritance} % ------------------------------------------------------- Towards the goal of stability and extensability, PyCMDS makes heavy use of abstraction and inheritance. % @@ -302,14 +304,35 @@ Hardware # implements basic thread control ├── Thorlabs └── Newport \end{codefragment} -The powerful thing about this strategy is that the three driver-specific classes (...) need only -implement minimal driver-specific code, typically start, set close... -This means that code is more maintainable and less buggy... -Easier to change high-level behavior without redoing low-level code... -Only implemented once... - -At it's most basic PyCMDS defines the following simple data types (derived from -\python{PyCMDS_object}): +The powerful thing about this strategy is that the three driver-specific classes +(\python{Homemade}, \python{Thorlabs}, and \python{Newport}) need only implement minimal +driver-specific code, typically \python{start}, \python{set} and \python{close}. % +This means that code is more maintainable and less repeated. % +For example, when I added the autonomic system to PyCMDS, I edited the parent \python{Delay} class +to respect a new method \python{set_offset}. % +I did \python{not} need to modify any of the child classes, because nothing about communicating +with the particular delay stages, in native units, had changed. % +This allowed me to implement the core of the autonomic system in just one weekend, something that +probably would have taken weeks to do without inheritance. % + +\subsection{Core classes of PyCMDS} % ------------------------------------------------------------ + +Now we can see that PyCMDS is going to use multi-threading, inheritance and abstraction as much as +possible, so let's get into some details about the \emph{actual} internal structure of the +software. % + +For those that want to dig deeper, most of these top level classes are defined in +\bash{PyCMDS/project/classes.py}. % + +\subsubsection{Data types} % --------------------------------------------------------------------- + +PyCMDS is made to be enhanced and extended by chemistry graduate students who may not have time or +energy to learn about Mutexes, signals, slots and threads. % +They probably also don't have time to read Qt documentation and learn the details of GUI design and +layout. % +PyCMDS does its very best to abstract these details away from developers by offering a set of +basic \emph{data type} classes which work seamlessly with every part of the program. % +These are simple classes, each meant to represent one kind of data: \begin{ditemize} \item Bool \item Combo @@ -317,19 +340,50 @@ At it's most basic PyCMDS defines the following simple data types (derived from \item Number \item String \end{ditemize} -These classes do multiple things. % -First, they \emph{are} Mutexes, with thread-safe \python{read} and \python{write} methods. % -Secondly, they support ``implicit'' storage in ini files. % -Third, they know how to participate in the GUI. % -They can display their value, and if modified they will propagate that modification to the internal -threads of outward... -Finally, they have special properties like units and limits etc... - -Without getting into details, let's investigate the key ``signals and slots'' that hardware and -sensors have. % -% TODO: elaborate +All are children of the parent \python{PyCMDS_object} class, which defines much of their shared +functionality. % -The following is the top-level hardware class, parent of all hardware and sensors. % +By using these classes to store and pass around bits of information within PyCMDS, users get the +following advantages: +\begin{ditemize} + \item Thread safety. These classes \emph{are} Mutexes. + \item Optional integration with the GUI (see section ...) + \item Optional storage of state within INI files, including through restart. % + \item Special conveniences, like limits, units, and labels. +\end{ditemize} +In short, developers should use these classes whenever possible for a worry-free development +experience. % +The only downside is that the value has to be accessed with \python{read} and \python{write} +methods, rather than directly. % +This is typical behavior for Mutexes, however. % + +\subsubsection{Hardware and driver} % ------------------------------------------------------------ + +Now that we have basic data types to work with, let's actually communicate with hardware. % +Every hardware and sensor have two classes: a driver class, which lives in the worker thread and +handles direct communication, and a hardware class which lives in the main thread and +``represents'' that device to the rest of PyCMDS. % +The idea is that all of PyCMDS communicates to that device \emph{only} via its hardware class, and +that hardware class only talks to that driver class. % +Designing it in this simple way keeps everything clean and easy to understand. % + +All hardware and driver classes are children of the same parent \python{Hardware} and +\python{Driver} classes. % +These parent classes know how to communicate in a thread safe way, and they know the specific +attributes (like \python{name}), and signals (like \python{update_ui}) that all hardware and +sensors must have. % +\autoref{aqn:fig:parent_hardware_class} shows the parent hardware class, and +\autoref{aqn:fig:parent_driver_class} shows the parent driver class. % + +Communication between the hardware and the driver goes via a queue, as mentioned previously. % +The hardware class has an attribute \python{q}, which is an instance of the \python{Q} class. % +To enqueue an operation, use \python{q.push(, )} where \python{} is a +string corresponding to the name of the method you wish to run in the worker thread, and +\python{} is a list of arguments passed to that method. % +The \python{q} instance will hold the instruction until the \python{Driver} instance is ready, at +which point \python{Driver.dequeue} will be called in the worker thread. % +There is no way for the driver to command hardware to do something in the main thread, but the +driver can trigger signals like \python{update_ui} and modify Mutexes. % \begin{figure} \includepython{"acquisition/parent_hardware.py"} @@ -344,21 +398,73 @@ The following is the top-level hardware class, parent of all hardware and sensor \begin{figure} \includepython{"acquisition/driver.py"} \caption[TODO]{ - TODO + Parent class of all drivers. % } - \label{aqn:fig:driver} + \label{aqn:fig:parent_driver_class} \end{figure} -\subsection{Graphical user interface} % ---------------------------------------------------------- - -Made up of widgets... - -Table widget... - -Use of qt plots... - -pyqtgraph \cite{pyqtgraph} +\subsubsection{GUI components} % ----------------------------------------------------------------- + +The PyCMDS GUI must change depending on which exact hardware, sensors, and acquisition modules are +being used on a given instrument and given day. % +Internally, the GUI components are made to be modular and flexable to accommodate this +requirement. % + +[programmatically defined GUI] + +To keep things simple and easy to extend, PyCMDS is made up of only a few minimalist GUI +elements. % +Probably the most important graphical element is the fixed-width vertical scroll area. % +As seen in [PYCMDS SCREENSHOTS], these vertical scroll areas contain almost all of the interactive +elements within PyCMDS, with the only exceptions being the ``SHUT DOWN'' button and the interactive +graphs. % +The left-hand scroll area is always present, and it contains the principle display and control for +each hardware. % +There are also scroll areas inside the tabbed menus, typically only one per tab. % +Because the scroll areas can expand downwards infinitely, they are great at accommodating the +changing contents of the PyCMDS GUI. % + +Ignoring small decorative items, vertical scroll areas contain only two kinds of widgets: instances +of \python{Button} and \python{InputTable}. % +Buttons are fairly self-explanatory. % +Internally they work through signals and slots (the \python{clicked} signal), and they can have +different behaviors including changing their label and color when clicked and being disabled or +enabled. % + +Input tables are the two column GUI elements that are everywhere in PyCMDS. % +The great thing about input tables is that they accept PyCMDS ``data type'' objects directly. % +Given an instance of \python{Number} called \python{destination}, adding to the input table is as +easy as \python{input_table.add('Destination', destination)}. % +Internally, PyCMDS will do all of the work to make sure that \python{destination} is displayed in +the GUI. % +The \python{destination.updated} signal will fire whenever a user manually interacts with the +display. % +Attributes \python{display} and \python{disabled} change the behavior of the GUI element. % + +Vertical scroll areas contain the input tables, and (on the right hand side), tabs contain the +vertical scroll areas. % +Like the input tables, this tabbed structure is designed to be extended as needed. % +For example, in the autonomic system, there needs to be a tab for each and every hardware currently +loaded by PyCMDS. % +To accommodate this, the somatic system simply builds a tab for each hardware at PyCMDS startup. % + +In addition to the parent \python{Hardware} and \python{Driver} classes mentioned in the previous +section, each hardware type has a \python{GUI} class, itself a child of the parent \python{GUI} +class. % +The \python{GUI} class defines the ``ADVANCED'' interface that is unique to each hardware (see +HARDWARE SECTION). % +Like everywhere else, inheritance and abstraction are used to minimize unnecessary replication of +code. % +To a first approximation, every delay stage needs the same ``ADVANCED'' settings as every other +delay stage. % + +PyCMDS uses pyqtgraph \cite{pyqtgraph} for interactive plotting. % +pyqtgraph is great because it is optimized for speed and interactivity. % + +For those wanting to learn more, all graphical components are defined in +\bash{PyCMDS/project/widgets.py}. % +\clearpage \section{Hardware} \label{aqn:sec:hardware} % ==================================================== Hardware are things that 1) have a position, 2) can be set to a destination. % -- cgit v1.2.3