aboutsummaryrefslogtreecommitdiff
path: root/acquisition/chapter.tex
blob: 0a7d7b71066723561ab74f2385ea308b72b0dcbc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
\chapter{Acquistion} \label{cha:acq}

\begin{dquote}
  The question of software correctness ultimately boils down to, “Does it do what we have in our
  minds, even the things we have not gotten around to thinking about yet?”

  \dsignature{Alistair Cockburn}
\end{dquote}

\clearpage

\section{Introduction}  % =========================================================================

At its core, MR-CMDS is about delivering multiple pulses of light to a sample.  %
The frequency and relative arrival time (delay) of each pulse must be scanned in the context of a
basic multidimensional experiment.  %
Scanning frequency requires using motors to change crystal angles and other optics within Optical
Parametric Amplifiers (OPAs).  %
Scanning delay typically involves moving mirrors very small distances such that the optical path
length of certain beam-lines changes.  %
In addition to these ``first-order'' controls, the power of MR-CMDS is enhanced with additional
control of pulse intensity and polarization.  % TODO: citations to motivate this 'enhancement'
An automated monchromator is typically used to spectrally resolve or isolate output signal.  %
Each of these features is an optomechanical device: a piece of hardware that must be controlled in
the context of an MR-CMDS experiment.  %

A scan in MR-CMDS typically means sending hardware to a whole series of different positions.  %
As an example, a three-dimensional ``movie'' might be collected in the following way:
\begin{codefragment}{python, label=acq:lst:psuedoaqn}
w1_points = [14300, 14400, 14500, 14600, 14700]  # wn
w2_points = [14100, 14200, 14300, 14400, 14500, 14600, 14700]  # wn
d2_points = [100, 75, 50, 25, 0, 50, 75, 100, 125, 150, 175, 200]  # fs
for w2 in w2_points:
    set_w2(w2)
    for w1 in w1_points:
        set_w1(w1)
        for d2 in d2_points:
            set_d2(d2)
            measure_signal()
\end{codefragment}
In this simple example, there are 5 \python{w1} destinations, 7 \python{w2} destinations, and 12
\python{d2} destinations, so there are a total of $5\times7\times12=420$ pixels in the
three-dimensional scan.  %
The acquisition software must set the hardware to each of these points and acquire data at each of
them.  %
Each hardware motion and signal measurement takes roughly one second, so this acquisition would
take roughly 7 minutes to complete.  %
In practice, real scans are composed of $\sim$100 to $\sim$100,000 pixels, and take between 1
minute and one day to acquire.  %

Because of the highly specialized nature of these experiments, MR-CMDS instruments typically
require custom software to address all of the simultaneous, repeated motor motion that a scan
requires.  %
Because MR-CMDS is really a family of techniques which require different kinds of motor motion,
this acqusition software should be flexable enough to meet the creativity of its users.  %
Furthermore, because MR-CMDS is a, rapidly evolving technique the instrumental
software must be built in an extendable way to accommodate the ever-changing hardware
configurations.  %
In this chapter I describe how I built such an acquisition software, PyCMDS.  %

For context, some description of the acquisition software that PyCMDS replaced is warranted.  %

On the ``ps table'' (focus on mixed vibrational-electronic spectroscopy of molecular systems),
PyCMDS replaces `ps\_control', an older acquisition software first developed prior to 2004 by Kent
Meyer \cite{MeyerKentA2004b}.  %
ps\_control was very-much not modular, designed by generations of graduate students each getting to
``minimum viable'' as quickly as possible.  %
When I joined the group, ps\_control had become unsustainable.  %
The ps table was being revamped with new hardware, and the old motherboard finally died, so new
software was desperately needed.  %

On the ``fs table'' (focus on semiconductor photophysics), PyCMDS replaces `Control for Lots of
Research in Spectroscopy' (COLORS), developed by Schuyler Kain \cite{KainSchuyler2017a}.  %
PyCMDS is, in many ways, inspired by COLORS.  %
Kain's design was modular in many ways.  %
Still, there were fundamental problems that COLORS could not address.  %
Chief among them was Kain's approach to the National Instruments DAQ card, which did not allow for
the flexibility required to introduce more interesting chopping schemes (see
\autoref{act:sec:chop}).  %

PyCMDS is a unified software for controlling hardware and collecting data in the Wright Group.  %
It is written almost entirely in Python, with a graphical user interface (GUI) made using Qt.  %
It is cross-platform, with a core capable of running on Linux, Windows and macOS.  %
It is open source, developed on GitHub. \cite{GitHub}  %
Today PyCMDS is used to drive both the ps and fs tables.   %

PyCMDS is best thought of as a core program with three kinds of modular ``plugins'' that can be
extended as needed.  %
The three plugin kinds are
\begin{ditemize}
  \item Hardware: things that can be set to a position (\autoref{acq:sec:hardware}).
  \item Sensors: things that can be used to measure a signal (\autoref{acq:sec:sensors}).
  \item Acquisition modules: things that can be used to define and carry out an acquisition, and
    associated post-processing (\autoref{acq:sec:somatic}).
\end{ditemize}
The first design rule for PyCMDS is that these three things should be easy for the average
(motivated) user to add by herself.  %
Modularity and extensibility is important for all scientific software projects (see
\autoref{cha:sof}), but it is of paramount importance for acquisition software simply because the
diversity of hardware and experimental configurations is so great.  %
It is conceivable to imagine 90\% overlap between the data processing and simulation needs of one
spectroscopist to the next, but there is almost no overlap between hardware configurations even in
the two primary instruments maintained by the Wright Group.  %
Besides the extendable modular pieces, the rest of PyCMDS is a mostly-static code-base that accepts
modules and does the necessary things to handle display of information from, and communication
between, them.  %

PyCMDS offers many ways to interact with component hardwares.  %
Hardware can be set directly, or it can be moved in the context of a scan.  %
Less obviously, hardware must also move in the context of ``active correction'', such as spectral
delay correction.  %
I love to use the analogy of the nervous system when thinking of these two kinds of
instructions.  %
I have borrowed the terms ``autonomic'' and ``somatic'':

\begin{dquote}
  The autonomic nervous system, which innervates primarily the smooth musculature of all organs,
  the heart and the glands, mediates the neuronal regulation of the internal milieu.  %
  The actions of this system, as its name implies, are in general not under direct voluntary
  control.  %
  These characteristics distinguish the autonomic nervous system from the somatic nervous system,
  which mediates afferent and efferent communication with the enviornment and, for the most part,
  is subject to voluntary control and accessible to consciousness.  %

  The autonomic and somatic systems operate hand in hand...

  The functions of the autonomic nervous system are to keep the internal milieu of the body constant
  (homeostasis...) or adjust it as required by changing circumstances (e.g., mechanical work, food
  intake, water deprivation, heat or cold).  %

  \dsignature{W. J\"{a}nig, Autonomic Nervous System (1989) \cite{JanigW1989a}}
\end{dquote}

Within PyCMDS, the autonomic system (\autoref{acq:sec:autonomic}) handles the ``reflex'' motion
that is part of active correction.  %
The somatic system (\autoref{acq:sec:somatic}), on the other hand, handles the ``voluntary'' motion
in the context of acquiring a multidimensional scan.  %

% BJT: consider a layout preview paragraph or two

\clearpage
\section{Graphical user interface}  % =============================================================

In this section I introduce the GUI of PyCMDS, with the goal of introducing the basic structure as
experienced by a first time user of the software.  %
When PyCMDS starts up, the GUI is constructed out of modules depending on which hardware and
sensors the user has instructed the program to address.  %
A screenshot of the PyCMDS GUI, running on the fs table, is shown in
\autoref{acq:fig:pycmds_screenshot}.  %

On the left hand there is a single column displaying the current positions for all loaded
hardware.  %
Users may enter new destinations and hit the ``SET'' button.  %
Positions have units, which are changeable through the pull-down menu next to the control and
display.  %
Each hardware knows its own limits, displayed in a tool tip when hovering over the control.  %
Users cannot type values outside of hardware limits into the controls.  %
Each hardware also has an ``ADVANCED'' button, which takes the user to a more extensive GUI to
control lots more features (\autoref{acq:sec:hardware}).  %
At the very top, on the left hand side, is the ``SHUT DOWN'' button.  %

On the right hand side there is a extensive set of nested tabs.  %
The top level tabs are ``Program'', ``Hardware'', ``Devices'', ``Autonomic'', ``Somatic'' and
``Plot''.  %
Under each of these tabs is an entire separate set of display and control elements.  %
Some of these elements are themselves tabbed, like the ``Somatic'' tab (active in
\autoref{acq:fig:pycmds_screenshot}), which has ``Queue'' and ``Scan'' sub-tabs.  %
At the top of the right hand side there is a progress bar and queue status display, which I will
discuss further in future sections.  %

When PyCMDS opens (\autoref{acq:fig:pycmds_screenshot}), the user is first greeted with the
``Somatic/Queue'' tab on the right hand side of the GUI.  %
This is where she may instruct PyCMDS to do acquisitions.  %
In PyCMDS, all acquisitions are done in a queue system.  %
A queue is just a list of acquisitions, where the acquisitions are carried out one by one in
order.  %

To instruct PyCMDS to do an acquisition, the user must first create a queue by entering in a chosen
name (default is ``queue''), and pressing the ``MAKE NEW QUEUE'' button.  %
Alternatively the user may open an existing queue using ``OPEN QUEUE''.  %
Next, the user must add the desired acquisition(s) to the queue.  %
There are several acquisition modules, each with a different purpose
(\autoref{acq:sec:somatic}).  %
Choose an acquisition module using the drop down menu.  %
Enter a name for your acquisition, and any additional info that you might want to keep track of.  %
Finally, fill out any additional information that acquisition module might require, and press
``APPEND TO QUEUE''.  %
Once there are acquisitions in the queue, the user can press ``RUN QUEUE''.  %

\autoref{acq:fig:pycmds_queue_screenshot} is a screenshot of the ``Somatic/Queue'' tab while a
queue is in progress.  %
In this screen shot there are seven enqueued items, and PyCMDS is in the process of acquiring the
final one (index 6).  %
For each item the status, start time, exit time, and description are shown.  %
Users may enqueue new items at any time, even while the queue is running.  %
They may delete items which are enqueued but not yet started.
Using the green ``LOAD'' button in each row, users may populate the right hand input menu with the
parameters from that acquisition.  %
This is useful when repeating an acquisition with slightly changed parameters, or simply when
inspecting what parameters were used in a given item.  %

\autoref{acq:fig:pycmds_screenshot_during_scan} is a screenshot of PyCMDS during a representative
acquisition.  %
This time, the ``Somatic/Scan'' tab is chosen.  %
On the left hand side, we can see that the ``SHUT DOWN'' and ``SET'' buttons are grayed out and
unclickable, since the somatic system is in control of PyCMDS.  %
At the moment when this screen shot was taken, PyCMDS is waiting for w3 (OPA-800CG) to finish
moving, as the ``BUSY'' indicator shows.  %
The progress bar, near the top of the GUI, is partially green to indicate the portion of the
current scan that is finished.  %
On the left hand side of the progress bar the time elapsed (fifteen minutes, thirty-seven seconds)
is shown, and on the right hand shows the time remaining (three hours, eight minutes, sixteen
seconds).  %
In the center of the progress bar are the current scan parameters.  %

The ``Somatic/Scan'' tab is built as an active display of currently acquiring data.  %
A large graph and number show the current signal levels.  %
On the far right-hand side, the device and channel to display can be chosen via drop-down menu.  %
Under ``Status'', the loop time and scan index help users gauge the progress of their scan.  %
In this case, the displayed pixel (index 6, 40) took 2.448 seconds to acquire.  %

The other tabs are for more advanced interactions, and will be discussed further in future
sections.  %
The ``Program'' tab contains various rarely-needed displays and inputs to configure PyCMDS.  %
The ``Hardware'' tab is where the advanced menu for each hardware appears
(\autoref{acq:sec:hardware}).  %
The ``Devices'' tab is where sensor settings live (\autoref{acq:sec:sensors}).  %
The ``Autonomic'' tab is where users configure the autonomic system for active correction
(\autoref{acq:sec:autonomic}).  %
The ``Somatic'' tab has already been described in this section.  %
Finally, the ``Plot'' tab was intended to be a interactive, graphical post processing
environment.  %
This functionality has not yet been implemented.  %

\begin{landscape}
\begin{figure}
	\includegraphics[scale=0.5]{"acquisition/screenshots/005"}
  \caption[PyCMDS at startup.]{
    PyCMDS at startup, on the fs system.  %
  }
  \label{acq:fig:pycmds_screenshot}
\end{figure}
\end{landscape}

\begin{landscape}
\begin{figure}
  \includegraphics[width=9in]{"acquisition/screenshots/004"}
  \caption[PyCMDS queue.]{
    PyCMDS queue while acquiring data, on the ps system.  %
  }
  \label{acq:fig:pycmds_queue_screenshot}
\end{figure}
\end{landscape}

\begin{landscape}
\begin{figure}
  \includegraphics[width=9in]{"acquisition/screenshots/000"}
  \caption[PyCMDS while scanning.]{
    PyCMDS scan tab while acquiring data, on the ps system.  %
  }
  \label{acq:fig:pycmds_screenshot_during_scan}
\end{figure}
\end{landscape}

\section{Internal structure}  % ===================================================================

In this section I describe the internal structure of PyCMDS.  %
While there are a huge number of details not worthy of discussion (at time of writing, PyCMDS
consists of 16,582 lines of source code), PyCMDS is made to be maintained and extended by future
graduate students, so some insight into the internal structure is warranted.  %

\subsection{Multithreading}  % --------------------------------------------------------------------

PyCMDS spends the vast majority of its runtime waiting---waiting for user input through mouse
clicks or keyboard presses, waiting for hardware to finish moving or for sensors to finish reading
and return signals.  %
Despite all of this downtime, it is crucial that the software respond very quickly when
instructions or signals are received.  %
To achieve this, PyCMDS is designed using \emph{multithreading}.  %
The main thread handles the graphical user interface (GUI) and other ``top level'' things.  %
Everything else happens in child threads.  %
Each hardware instance (e.g. a delay stage) lives in its own thread, as does each sensor.  %
Since only one scan happens at a time, all acquisition modules share a single thread that handles
the orchestration of hardware motion, sensor operation, and data processing that the chosen
acquisition requires. %

Threads are powerful because they allow for ``semi-synchronous'' operation.  %
Imagine PyCMDS is in the middle of a 2D delay-delay scan, and the scan thread has just told each of
the two delay stages to head to their destinations.  %
PyCMDS must sit in a tight loop to keep track of the position as closely as possible during motor
motion.  %
In a single-threaded configuration, this tight loop would only run for one delay at a time, such
that PyCMDS would have to finish shepherding one delay stage before turning its attention to the
second.  %
In a multi-threaded configuration, each thread will run simultaniously, switching off CPU cycles
quickly at a low level.  %
This switching is handled in an OS and hardware specific way---luckily it is all abstracted through
platform-agnostic Qt threads.  %

It is hard to pass information between threads.  %
Without any special protection, two threads have no reason not to simultaneously edit and read the
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, 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.  %
This lock is like the ``talking stick'' employed my many early child educators.  %
When the talking stick is being used, only the child that holds the stick is allowed to speak.  %
The stick must be passed to another child (as directed by the teacher) before they can share their
thought.  %
PyCMDS makes heavy use of Mutexes, in particular the \bash{QMutex} class \cite{QMutex}.  %

Mutexes handle basic information transfer (two threads can both safely modify and read a particular
object), but what about sending instructions between threads?  %
Here the problem is deciding what happens when multiple instructions are given simultaneously, or
an instruction is given while another instruction is being carried out.  %
Again, this is a classic problem in computer science, and the classic answer is the queue.  %
Queues are just like lines at the coffee shop---each person (instruction) is served (carried out)
in the order that they joined the line.  %
Queues are commonly referred to as FIFO (First In First Out) for this reason.  %
PyCMDS uses queues for almost all instructions.  %

Finally, PyCMDS makes extensive use of the ``signals and slots'' construct, which is somewhat
unique (and certainly original) to Qt.  %
Signals and slots are powerful because they allow threads without instruction to go completely
silent, making them essentially free in terms of CPU usage.  %
Normally, a thread needs to sit in a loop to listen for instructions.  %
Within the Qt framework, a thread can be ``woken'' by a signal without needing that thread to
explicitly ``listen''.  %
These concepts fit within the broader umbrella of ``event-driven programming'', a concept that has
been used in many languages and frameworks (notably high level LabVIEW tends to be very
event-driven).  %
The Qt signals and slots system massively simplifies programming within PyCMDS.  %

Note that multithreading is very different from multiprocessing.  %

\subsection{Abstraction and inheritance}  % -------------------------------------------------------

Towards the goal of stability and extensability, PyCMDS makes heavy use of abstraction and
inheritance.  %

Abstraction means that complex implementation details are hidden by simple interfaces.  %
For example, consider the simple case of setting an OPA to a particular color.  %
For OPAs in the Wright Group, this operation requires the following:
\begin{ditemize}
  \item Load the OPA tuning curve, find which motors must move.
  \item Interpolate the discrete tuning curve, and evaluate that interpolated curve at the desired
    destination.
  \item Send each motor towards their new destination.
  \item Wait for the motors to arrive, check that nothing has gone wrong.
\end{ditemize}
This is not even to mention the complexity of spawning and sending information between the main
thread and working threads.  %
Through abstraction, PyCMDS is able to wrap all this complexity into the \python{OPA} class and
its \python{set_position} method---so doing all of the operations above is as simple as
\python{opa.set_position(1300, 'nm')}.  %
Importantly, abstraction does not magically get rid of the complexity.  %
It simply \emph{hides} the complexity so that it becomes tractable to write simple interfaces to
accomplish complex things.  %

PyCMDS implements abstraction through inheritance.  %
In object oriented programming, inheritance is when one class is based on another.  %
The \emph{child} class acquires all of the properties of the \emph{parent} class.  %
The child class, then, can modify or extend the properties that it needs, without needing to
re-implement the properties that it shares with the parent.  %
Let's consider PyCMDS hardware again.  %
Every single unique hardware in PyCMDS lives in its own worker thread, so the basic problem of
information transfer through queues and Mutexes is shared between them.  %
A \python{Hardware} class which is parent to \emph{all} hardwares can define the methods and
attributes necessary to abstract this basic thread communication issue.  %
Every type of delay stage addressed by PyCMDS needs to handle the basic task of translating between
``natural'' units (femtosecond, picosecond, nanosecond) and ``native'' units (typically mm).  %
They all need an attribute \python{zero_position}, in native units, and a method to do the
conversion.  %
A class common to all delay stages can abstract away all of these conversion details.  %
In summary, an inheritance-based system for implementing delay stages might look like this:
\begin{codefragment}{bash}
Hardware  # implements basic thread control
└── Delay  # implements conversion between natural and native units
    ├── Homemade
    ├── Thorlabs
    └── Newport
\end{codefragment}
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 \emph{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,
energy or enthusiasm 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
  \item Filepath
  \item Number
  \item String
\end{ditemize}
All are children of the parent \python{PyCMDS_object} class, which defines much of their shared
functionality.   %

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 are made of 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{acq:fig:parent_hardware_class} shows the parent hardware class, and
\autoref{acq: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(<method>, <arguments>)} where \python{<method>} is a
string corresponding to the name of the method you wish to run in the worker thread, and
\python{<arguments>} 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"}
  \caption[Parent to hardware and sensors.]{
    Parent class of all hardware and sensors.  %
    For brevity, methods \python{close}, \python{update} and \python{wait_until_still} have been
    omitted.  %
  }
  \label{acq:fig:parent_hardware_class}
\end{figure}

\begin{figure}
	\includepython{"acquisition/driver.py"}
  \caption{
    Parent class of all drivers.  %
  }
  \label{acq:fig:parent_driver_class}
\end{figure}

\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.  %
Rather than having each piece placed ``by hand'', the PyCMDS GUI is defined programmatically and,
as such, the full power of abstraction and inheritance is available to the GUI-defining code.  %

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
\begin{codefragment}{python}
input_table.add('Destination', destination)
\end{codefragment}
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.  %
Currently only line plots are supported (through PyCMDS' \python{Plot1D} class),
but 2D plots are supported by pyqtgraph and could be added in future versions.  %

For those wanting to learn more, all GUI components are defined in
\bash{PyCMDS/project/widgets.py}.  %

\clearpage
\section{Hardware} \label{acq:sec:hardware}  % ====================================================

Hardware are things that 1. have a position, and 2. can be set to a destination.  %
Typically they also have associated units and limits.  %
They sometimes have an offset, as specified by the autonomic system
(\autoref{acq:sec:autonomic}).  %
Each hardware can be thought of as a dimension of the MR-CMDS experiment, and scans include a
specific traversal through this multidimensional space.  %

In this section I briefly discuss PyCMDS' implementation for each type of hardware.  %

\subsection{Hardware inheritance}  % --------------------------------------------------------------

All hardware classes are children of the parent \python{Hardware} class
(\autoref{acq:fig:hardware_class}), which is itself a child of the the global \python{Hardware}
class shown in \autoref{acq:fig:parent_hardware_class}.  %
By inspecting \autoref{acq:fig:hardware_class}, we can see that all hardware require the following
methods:
\begin{ditemize}
  \item \python{close}
  \item \python{get_destination}
  \item \python{get_position}
  \item \python{on_address_initialized}
  \item \python{poll}
  \item \python{set_offset}
  \item \python{set_position}
  \item \python{@property units}
\end{ditemize}

\autoref{acq:fig:hardware_inheritance} shows the full inheritance tree, including all nine types of
hardware currently supported by PyCMDS.  %
In general the nesting is type/model, although there can be additional levels of nesting when
required, as can be seen in the case of OPA/TOPAS/TOPAS-C and OPA/TOPAS/TOPAS-800.  %

The ``kind-defining'' classes (\python{Delay}, \python{Filter}, \python{OPA},
\python{Spectrometer}) are particularly important.  %
Each of these defines \emph{what it means} to be that kind of hardware, as far as PyCMDS is
concerned.  %
To put it another way, these classes are minimum viable versions of their hardware kind.  %
Each of these classes can be directly instantiated as a minimum-viable instance, which allows for
``virtual'' hardware and offline development.  %

\begin{figure}
	\includepython{"acquisition/hardware.py"}
  \caption[Parent hardware class.]{
    Parent class of all hardware.  %
    For brevity, methods \python{close}, \python{get_destination}, \python{get_position},
    \python{is_valid}, \python{on_address_initialized}, \python{poll}, and \python{@property units}
    have been omitted.  %
  }
  \label{acq:fig:hardware_class}
\end{figure}

\begin{figure}
	\includebash{"acquisition/hardware_inheritance"}
  \caption[Hardware inheritance.]{
    Full PyCMDS hardware inheritance tree.
  }
  \label{acq:fig:hardware_inheritance}
\end{figure}

\subsection{Delays}  % ----------------------------------------------------------------------------

Delays are the kind of thing that have a position in absolute units, a zero position, and a
relative position measured in fs or ps.  %
They can be offset by the autonomic system to accommodate spectral delay correction.  %

In addition to the inherited methods, delay hardware objects require the following:
\begin{ditemize}
  \item \python{set_motor_position}
\end{ditemize}

Delay driver objects require the following:
\begin{ditemize}
  \item \python{set_motor_position}
\end{ditemize}

The delay GUI, by default, offers
\begin{ditemize}
  \item \python{on_home}
  \item \python{on_set_motor}
  \item \python{on_set_zero}
\end{ditemize}
\autoref{acq:fig:delay_advanced} is a screenshot of the advanced panel for one of the Newport MFA
\cite{MFA} delay stages.  %
This advanced panel is pretty typical.  %
Users may directly set the motor position or zero position.  %
They may also change the label.  % BJT: because...
There is a ``factor'' argument, which can be any non-zero integer (positive or negative).  %
At its simplest this factor can accommodate different directions that the stage is placed, but it
can also account for double-passes and similar configurations.  %

\begin{landscape}
\begin{figure}
 	\includegraphics[width=9in]{"acquisition/screenshots/006"}
  \caption[Representative delay stage advanced menu.]{
    Advanced menu for one of the MFA-CC (SMC-100) delay stages, on the fs system.  %
  }
  \label{acq:fig:delay_advanced}
\end{figure}
\end{landscape}

\subsection{Spectrometers}  % ---------------------------------------------------------------------

Spectrometers are the kind of thing that can be set to a single color (typically native units are
in nm).  %
They often have turrets that allow for switching between gratings.  %
Other features are not currently supported as PyCMDS has only needed to drive one monochromator at
this time.  %

Spectrometer hardware instances do not need any features beyond the default hardware object.  %

Spectrometer driver instances require the following:
\begin{ditemize}
  \item \python{get_grating_details}
  \item \python{set_turret}
\end{ditemize}

\autoref{acq:fig:spectrometer_advanced} is a screenshot of the MicroHR advanced menu on the
fs system.  %
Nothing can be changed in the advanced menu except the label, and the offset can be seen.  %

\begin{landscape}
\begin{figure}
  \includegraphics[width=9in]{"acquisition/screenshots/007"}
  \caption{
    Representative spectrometer advanced menu.
  }
  \label{acq:fig:spectrometer_advanced}
\end{figure}
\end{landscape}

\subsection{OPAs}  % ------------------------------------------------------------------------------

OPAs are the most complicated piece of hardware addressed by PyCMDS.  %
They range between 3 and 6 motors.  %

In addition to what's inherited from the parent hardware class, OPA hardware instances require the
following:
\begin{ditemize}
  \item \python{@property curve}
  \item \python{@property curve_paths}
  \item \python{get_tune_points}
  \item \python{home_motor}
  \item \python{load_curve}
  \item \python{@property motor names}
  \item \python{run_auto_tune}
  \item \python{set_motor}
\end{ditemize}

In addition to what's inherited from the parent driver class, OPA driver instances require the
following:
\begin{ditemize}
  \item \python{bool poynting_correction}
  \item \python{home_motor}
  \item \python{home_all}
  \item \python{curve}
  \item \python{motor_names}
  \item \python{set_motor}
  \item \python{set_motors}
  \item \python{interaction}
  \item \python{load_curve}
  \item \python{set_position_except}
\end{ditemize}

Many of these additional features have to do with the tuning curve, a crucial feature of OPAs.  %
The tuning curve contains motor positions needed to achieve each valid output color.  %
Read more about my implementation of tuning curves in \autoref{cha:opa}.  %

\autoref{acq:fig:opa_advanced} is a screenshot of the advanced menu for one of the TOPAS-C OPAs on
the fs table.  %
A large central plot displays the currently loaded tuning curve for one of the motors (chosen in
the pull down menu).  %
Multiple filepath menus allow the user to specify each tuning curve.  %
Each motor can be independently set and homed.  %

\begin{landscape}
\begin{figure}
 	\includegraphics[width=9in]{"acquisition/screenshots/008"}
  \caption{
    Representative OPA advanced menu.
  }
  \label{acq:fig:opa_advanced}
\end{figure}
\end{landscape}

\section{Sensors (devices)} \label{acq:sec:sensors}  % ============================================

Sensors are the kind of things that actually measure data.  %
In spectroscopy these can be photodiodes, array detectors, photo-multiplier tubes etc.  %
In this section I describe the strategy that PyCMDS uses to represent sensors.  %

\subsection{The DAQ card}  % ----------------------------------------------------------------------

The National Instruments PCI-6251 card is capable of eight analog inputs and 1 million samples per
second (one sample per microsecond). \cite{PCI6251}  %
Both instruments operate at 1 KHz, so that leaves 1000 samples per shot.  %
It is important to be able to configure all of the timings within each shot.  %

\autoref{acq:fig:samples} shows the GUI designed to control the sample level timing of the NI
PCI-6251 card \cite{PCI6251}.  %
All 1000 samples for the current shot are displayed in the large central plot.  %
Users may allocate regions of samples to be assigned to particular channels.  %
A second region may be allocated for explicit baseline subtraction.  %
For things like PMTs, make sure these two regions are the same size.  % BJT: explain why
For each region, a simple processing method is applied before the baseline is (optionally)
subtracted and the signal is (optionally) inverted.  %
All of this results in a single processed value for each channel on each shot.  %
Users may also allocate exactly one sample for chopper monitoring.  %

\autoref{acq:fig:shots} is a screenshot of the shots GUI.  %
Only one channel is shown, chosen by the pull down menu on the right hand side.  %
Each point is one processed value from all of the samples designated for that particular shot.  %
Because this instrument is using dual chopping at this time, only one out of four shots has a large
amount of light, so most shots are near zero in \autoref{acq:fig:shots}.  %

Finally, the shot-level information is passed through a ``shots processing'' script that is
entirely user defined.  %
\autoref{acq:fig:shots_processing} shows a typical shots processing script for a dual chopping
experiment.  %
Because the script is entirely user defined, exciting statistical treatments are possible moving
forward.  %

% BJT: more about the power of digital processing
%Old boxcar: 300 ns window, ~10 micosecond delay. Onset of saturation ~2 V.

\begin{landscape}
\begin{figure}
  \includegraphics[width=9in]{"acquisition/screenshots/003"}
  \caption[PCI-6251 samples tab.]{
    Screenshot of PCI-6251 samples tab.
  }
  \label{acq:fig:samples}
\end{figure}
\end{landscape}

\begin{landscape}
\begin{figure}
  \includegraphics[width=9in]{"acquisition/screenshots/002"}
  \caption{
    PCI-6251 shots tab.
  }
  \label{acq:fig:shots}
\end{figure}
\end{landscape}

\begin{figure}
  \includepython{acquisition/shots_processing.py}
  \caption{
    Simple shots processing script for dual chopping.
  }
  \label{acq:fig:shots_processing}
\end{figure}

\subsection{Multidimensional sensors}  % ----------------------------------------------------------

Sensors need not be single valued at each hardware coordinate.  %
Sometimes, a sensor returns an entire array of information.  %
In cases like these, PyCMDS expands the dimensionality of the scan to accommodate the many-valued
sensor.  %

For example, consider \autoref{acq:fig:array_as_axis}.  %
This simple tune test was taken with an array detector, rather than using a scanning
monochromator.  %
Although only one piece of hardware was scanned, the data is considered to be
\emph{two}-dimensional, with the second dimension being $\bar{\nu}_a$, the differential color axis
for the array vs the OPA setpoint.  %

The data in \autoref{acq:fig:array_as_axis} does not occupy a rectangular region in this
parameterization.  %
This is because the range of colors covered at each monochromator setpoint is different, with a
smaller dispersion (more colors across the finite array) at higher energies.  %
This relationship is somewhat complex, and requires terms like angle of deviation, focal length,
and focal plane tilt to solve.  %
It has been derived previously by \textcite{KainSchuyler2017a}.  %

\begin{figure}
  \includegraphics[scale=0.5]{"acquisition/tune_test"}
  \caption[Array detector serving as an axis.]{
    Array detector serving as an axis. Data collected 2017-11-06.  % OPA2
  }
  \label{acq:fig:array_as_axis}
\end{figure}

\section{Autonomic} \label{acq:sec:autonomic}  % ==================================================

The autonomic system is used to define ``reflexes'' for PyCMDS---operations that are automatically
applied when certain conditions are met.  %
Currently the autonomic system has only functionality: \emph{offset} certain hardware as functional
of other hardware's positions.  %

The classic example of autonomic offsets comes from spectral delay correction (see section
[ACT]).  %
Spectral delay refers to the small delay changes that occur when OPAs change output frequency.  %
To correct for spectral delay, the appropriate delay stages can simply be \emph{offset}.  %

In PyCMDS, the autonomic system is fully general---that is to say, any hardware can be offset
according to any other hardware.  %
This means that spectral delay correction is possible for arbitrarily complex laser setups, and it
means that PyCMDS is prepared for corrections that have not yet been fully implemented, such as
automated power correction.  %

Simple plain-text \bash{.coset} files define the offset arrays.  %
They are automatically generated using processing scripts in \python{attune}.  %
Their headers prevent them from being loaded in the wrong spot.  %
These files are internally represented as instances of the \python{CoSet} class, which is capable
of linear interpolation and extrapolation at the edges.  %

A single hardware can be offset by multiple other hardwares.  %
In such cases, \emph{offsets always add}.  %

\autoref{acq:fig:autonomic} displays the GUI of the autonomic system, in this case for ``d1'' on
the picosecond system.  %
Note the triple-nested tab structure---there are one of these tabs for each hardware loaded by
PyCMDS.  %
A large display occupies the middle of the program, displaying the offsets as applied by ``w1'' in
this case.  %
On the right-hand side users can add as many \bash{.coset} files as desired.  %

\begin{landscape}
\begin{figure}
  \includegraphics[width=9in]{"acquisition/screenshots/009"}
  \caption{
    Autonomic tab.
  }
  \label{acq:fig:autonomic}
\end{figure}
\end{landscape}

\section{Somatic} \label{acq:sec:somatic} % =======================================================

In contrast with the autonomic system (\autoref{acq:sec:autonomic}), the somatic system is all
about voluntary, user specified motion.  %
This is where the fun stuff happens---the acquisitions!

PyCMDS uses the words ``scan'' and ``acquisition'' in very careful ways.  %
\begin{ditemize}
  \item An acquisition is a single user-defined, enqueable, instruction.
  \item A scan is a single traversal in the multidimensional hardware space.  %
\end{ditemize}
Each scan corresponds to one \bash{.data} file, and one WrightTools \python{Data} instance.  %
There can be many scans within a single acquisition.  %
And there can be many acquisitions in a queue.  %

PyCMDS saves the data that it is collecting within a nested folder structure
queue/acquisition/scan.  %
A \bash{.queue} file holds everything needed to recreate the queue, \python{.aqn} files define each
acquisition in plain text and \python{.data} files hold the multidimensional data itself.  %

In this section I describe each component of the somatic system in greater detail.  %

\subsection{Queue manager}  % ---------------------------------------------------------------------

The queue manager keeps track of all enqueued acquisitions, and tells each acquisition when to
begin.  %
A singleton \python{Queue} class lives in the main thread and handles interfacing to the
\bash{.queue} plain-text file.  %
When a user appends new acquisitions, or changes their order, the \python{queue} instance makes
sure that those changes are reflected in the GUI and the file.  %

A special singleton \python{QueueStatus} keeps track of, well, the queue status: a series of
booleans \python{go}, \python{going}, \python{pause}, \python{paused}, \python{stop}, and
\python{stopped}.  %
The verb booleans (\python{go}, \python{pause}, \python{paused}), are control flags, to be
written by the main thread.  %
The present particple booleans (\python{going}, \python{paused}, \python{stopped}) are flags to be
written by the worker thread to indicate status of the current acquisition.  %

A singleton \python{Worker} class lives in the acquisition worker thread and carries out the actual
operation.  %
The \python{queue} instance pushes operations to the \python{worker}, and the \python{worker} sends
a signal that causes \python{queue.on_action_complete} to be called.  %
If there are more enqueued acquisitions, and \python{queue_status.go} is true, the \python{queue}
instance pushes the next operation to the \python{worker} and the process starts over again.  %

The queue manager is capable of more than just acquisitions.  %
For example, users can enqueue a wait operation that simply pauses PyCMDS for a certain amount of
time or until some condition is met.  %

Those who wish to learn more can refer to \bash{PyCMDS/somatic/queue.py}.  %

\subsection{Scans}  % -----------------------------------------------------------------------------

Every single scan within PyCMDS is handled by one method of one class, \python{Worker.scan}
from the file \bash{PyCMDS/somatic/acquisition.py}.  %
This method understands how to any scan that PyCMDS can do: any stepwise multidimensional scan with
separate hardware and sensors.  %

The central scan method requires acquisition modules to provide all of the axes (instances of \\
\python{PyCMDS.somatic.acquisition.Axis}) that need to be broadcast across each-other.  %
Then something very simple happens.  %
For each hardware that will move during the scan, a destinations object (instance of
\python{PyCMDS.somatic.acquisition.Destinations}) is created.  %
This object has the same shape as the full multidimensional scan, and contains the destination for
that hardware for that pixel in the scan.  %
Then, a \bash{.data} file is created to accept the data that is about to be collected.  %
A bunch of signals go off, telling PyCMDS that it needs to yield to somatic control.  %
Then PyCMDS simply sits in a loop generated by \python{numpy.ndindex} \cite{ndindex} and visits
each pixel in turn.  %
\python{ndindex} is a n-dimensional iterator over a given array shape.  %
Written simply, it does the following:
\begin{codefragment}{python}
def ndindex(shape):
    rs = [range(s) for s in shape]
    pools = map(tuple, rs)
    result = [[]]
    for pool in pools:
        result = [x+[y] for x in result for y in pool]
    for prod in result:
        yield tuple(prod)
\end{codefragment}
So that when evaluated:
\begin{codefragment}{python}
>>> for idx in ndindex((4, 3, 2)): print idx
(0, 0, 0)
(0, 0, 1)
(0, 1, 0)
(0, 1, 1)
(0, 2, 0)
(0, 2, 1)
(1, 0, 0)
...
(3, 2, 0)
(3, 2, 1)
\end{codefragment}

This simple algorithm is used to visit each pixel in the entire PyCMDS scan space.  %
At each pixel, something much like the following happens.  %
\begin{codefragment}{python, label=acq:lst:loop_simple}
for idx in ndindex(shape):
  for hardware in hardwares::
      hardware.set(idx)
  for hardware in hardwares:
      hardware.wait_until_still()
  for sensor in sensors:
      sensor.read()
  for sensor in sensors:
      sensor.wait_until_done()
\end{codefragment}
The pattern is simple: launch hardware, wait for still, launch sensors, wait for done.  %
The simplicity of this central loop truly shows the power of abstraction in PyCMDS.  %

\subsection{Acquisition modules}  % ---------------------------------------------------------------

Acquisition modules are defined interfaces which know how to assemble a scan.  %

\autoref{acq:fig:aqn_file} shows an aqn file for an acquisition using SCAN.  %

\begin{figure}
  \includebash{"acquisition/example.aqn"}
  \caption{
    Representative aqn file.
  }
  \label{acq:fig:aqn_file}
\end{figure}

\subsubsection{SCAN}

SCAN is by far the most important acquisition module in PyCMDS---it handles acquisitions for almost
all of the CMDS experiments that PyCMDS has ever accomplished.  %
SCAN is capable of acquisitions of arbitrary dimensionality.  %
Users simply append as many axes as they want.  %
Acquistions are done with the trailing (highest index) axis as the innermost loop.  %
Arbitrary expressions (PyCMDS calls them ``constants'') are also possible, as can be seen in
\autoref{acq:fig:aqn_file}.  %

\subsubsection{TUNE TEST}

The TUNE TEST module does a simple thing: it sets a chosen OPA to each of the points in its tuning
curve and does a monochromator scan of set width about that setpoint.  %
In this way the tune (output color) agreement between the curve and the OPA can be determined.  %
A new point curve with remapped colors is automatically created.  %
See \autoref{cha:opa} for more information.  %

\subsubsection{MOTORTUNE}

MOTORTUNE is capable of arbitrary acquisitions in OPA motor space.  %
Users are free to choose any set of positions for any set of motors, and PyCMDS will broadcast all
of those positions against each-other to form a multidimensional acquisition.  %
Optionally, a leading dimension of \emph{setpoint} may be added, with motor positions being scanned
about the points in the old curve.  %

\subsubsection{AUTOTUNE}

AUTOTUNE does multiple scans to tune OPAs as automatically as possible.  %
The interface and procedure is different for each model of OPA.  %
The procedures are like those described in \autoref{cha:opa}.  %

\subsubsection{POYNTING TUNE}

POYNTING TUNE is much like AUTOTUNE, except that it deals with Poynting correction instead of
regular tuning. See \autoref{act:sec:poynting} for more information.  %

\section{Conditional validity}  % =================================================================

The central requirement of the PyCMDS modular hardware abstraction is that experiments can be
boiled down to a set of orthogonal axes that can be set separately from other hardware and sensor
status.  %
This requirement is loosened by the autonomic and expression systems, such that any experiment
could \emph{probably} be forced into PyCMDS, but still the requirement stands---PyCMDS is probably
\emph{not} the correct framework if your experiment cannot be reduced in this way.  %
From this we can see that it is useful to talk about the conditional validity of the modular
hardware abstraction.  %

The important axis is hardware complexity vs measurement complexity.

For hardware-complex problems, the challenge is coordination.  %
MR-CMDS is the perfect example of a hardware-complex problem.  %
MR-CMDS is composed of a collection (typically 5 to 10 members) of relatively simple hardware
components.  %
The challenge is that experiments involve complex ``dances'', including expressions, of the
component hardwares.  %

For measurement-complex problems, the challenge is, well, the measurement.  %
These are experiments where every little piece of the instrument is tied together into a complex
network of inseparable parts.  %
These are often time-domain or ``single shot'' measurements.  %
Such instruments are typically much faster at data acquisition and more reliable.  %
This comes at a price of flexibility: often such instruments cannot be modified or enhanced without
touching everything.  %

From an acquisition software perspective, measurement-complex problems are not amenable to general
purpose modular software design.  %
The instrument is so custom that it certainly requires entirely custom software.  %

Measurements can be neither hardware-complex nor software-complex (simple) or both (expensive).  %
Thus PyCMDS can be proud to try and generalize the hardware-complex part of acquisition software
because indeed that is all that can be generalized.  %

\section{Integrations}  % =========================================================================

PyCMDS has several \emph{integrations} to assist users.  %

PyCMDS automatically uploads all collected data to Google Drive.  %
This provides an easy way for remote users to access the newest data, and it provides a data backup
in case something goes wrong.  %

PyCMDS interacts with users through a custom slack bot, the Wright Instrument Technology
Communication Handler (WITCH).  %
WITCH automatically posts when scans are done, and responds to simple commands like ``status''.  %
This allows users to be aware of what PyCMDS is doing without physically being in the laser lab.  %
\autoref{acq:fig:slack} is a screenshot of a typical interaction with WITCH.  %
Note Kyle Sunden asking for the status of the queue.  %

These integrations are particularly useful because MR-CMDS scans take so long.  %
When the lasers are working and everything is tuned, PyCMDS is typically \emph{constantly} scanning
to get as much use as possible out of the available time.  %

\begin{figure}
	\frame{\includegraphics[width=\textwidth]{"acquisition/slack"}}
	\caption{
    Slack screenshot.
  }
  \label{acq:fig:slack}
\end{figure}

\section{Future directions}  % ====================================================================

\subsection{Spectral delay correction module}  % --------------------------------------------------

Currently spectral delay correction is done ``manually''.  %
Relevant Wigner scans are taken using the SCAN module, processed using WrightTools, and generated
coset files are manually applied in the autonomic menu.  %
Ideally, ``check'' Wigners are then taken to verify the veracity of the corrections.  %
In the future, it would be preferable to have a dedicated SDC module that did all of these things
automatically.  %

It would start from existing corrections (for that table geometry) and iterate until the applied
Wigners are all correct.  %

\subsection{``Headless'' hardware, sensors}  % ----------------------------------------------------

The abstraction of hardware complexity that PyCMDS offers is really convenient, but currently
these convenient classes can only be used within PyCMDS itself.  %
Since code in PyCMDS is inseparable from the GUI, it is not possible to \python{import PyCMDS} and
use the abstract tools in other programs or scripts.  %
This design is not necessary, and doing the work to free hardware and sensor code from the GUI code
would allow for all kinds of creative experiments that are not currently possible within the
central conceit of PyCMDS.  %

\subsection{Ideal Axis Positions} \label{acq:sec:ideal_axis_positions}  % -------------------------

Frequency domain multidimensional spectroscopy is a time-intensive process.  %
A typical pixel takes between one-half second and three seconds to acquire.  %
Depending on the exact hardware being scanned and signal being detected, this time may be mostly
due to hardware motion or signal collection.  %
Due to the curse of dimensionality, a typical three-dimensional CMDS experiment contains
roughly 100,000 pixels.  %
CMDS hardware is transiently-reliable, so speeding up experiments is a crucial component of
unlocking ever larger dimensionalities and higher resolutions.  %

One obvious way to decrease the scan-time is to take fewer pixels.  %
Traditionally, multidimensional scans are done with linearly arranged points in each axis---this is
the simplest configuration to program into the acquisition software.  %
Because signal features are often sparse or slowly varying (especially so in high-dimensional
scans) linear stepping means that \emph{most of the collected pixels} are duplicates or simply
noise.  %
A more intelligent choice of axis points can capture the same nonlinear spectrum in a fraction of
the total pixel count.  %

An ideal distribution of pixels is linearized in \emph{signal}, not coordinate.  %
This means that every signal level (think of a contour in the N-dimensional case) has roughly the
same number of pixels defining it.  %
If some generic multidimensional signal goes between 0 and 1, one would want roughly 10\% of the
pixels to be between 0.9 and 1.0, 10\% between 0.8 and 0.9 and so on.  %
If the signal is sparse in the space explored (imagine a narrow two-dimensional Lorentzian in the
center of a large 2D-Frequency scan) this would place the majority of the pixels near the narrow
peak feature(s), with only a few of them defining the large (in axis space) low-signal floor.  %
In contrast linear stepping would allocate the vast majority of the pixels in the low-signal 0.0 to
0.1 region, with only a few being used to capture the narrow peak feature.  %
Of course, linearizing pixels in signal requires prior expectations about the shape of the
multidimensional signal---linear stepping is still an appropriate choice for low-resolution
``survey'' scans.  %

CMDS scans often contain correlated features in the multidimensional space.  %
In order to capture such features as cheaply as possible, one would want to define regions of
increased pixel density along the correlated (diagonal) lineshape.  %
As a concession to reasonable simplicity, our acquisition software (PyCMDS) assumes that all scans
constitute a regular array with-respect-to the scanned axes.  %
We can acquire arbitrary points along each axis, but not for the multidimensional scan.  %
This means that we cannot achieve strictly ideal pixel distributions for arbitrary datasets.  %
Still, we can do much better than linear spacing.

Almost all CMDS lineshapes (in frequency and delay) can be described using just a few lineshape
functions:
\begin{ditemize}
	\item exponential
	\item Gaussian
	\item Lorentzian
	\item bimolecular
\end{ditemize}

Exponential and bimolecular dynamics fall out of simple first and second-order kinetics (I will
ignore higher-order kinetics here).  %
Gaussians come from our Gaussian pulse envelopes or from normally-distributed inhomogeneous
broadening.  %
The measured line-shapes are actually convolutions of the above.  %
I will ignore the convolution except for a few illustrative special cases.  %
More exotic lineshapes are possible in CMDS---quantum beating and breathing modes, for example---I
will also ignore these.  %
Derivation of the ideal pixel positions for a single exponential appear below.

Simple exponential decays are typically used to describe population and coherence-level dynamics in
CMDS.  %
For some generic exponential signal $S$ with time constant $\tau$,
\begin{equation} \label{eq:simple_exponential_decay}
S(t) = \me^{-\frac{t}{\tau}}.
\end{equation}
We can write the conjugate equation to \ref{eq:simple_exponential_decay}, asking ``what $t$ do I
need to get a cerain signal level?'':
\begin{eqnarray}
\log{(S)} &=& -\frac{t}{\tau} \\
t &=& -\tau\log{(S)}.
\end{eqnarray}
So to step linearly in $t$, my step size has to go as $-\tau\log{(S)}$.

We want to go linearly in signal, meaning that we want to divide $S$ into even sections.  %
If $S$ goes from 0 to 1 and we choose to acquire $N$ points,
\begin{eqnarray}
t_n &=& -\tau\log{\left(\frac{n}{N}\right)}.
\end{eqnarray}
Note that $t_n$ starts at long times and approaches zero delay.  %
So the first $t_1$ is the smallest signal and $t_N$ is the largest.  %

Now we can start to consider realistic cases, like where $\tau$ is not quite known and where some
other longer dynamics persist (manifested as a static offset).  %
Since these values are not separable in a general system, I'll keep $S$ normalized between 0 and
1.  %
\begin{eqnarray}
S &=& (1-c)\me^{-\frac{t}{\tau_{\mathrm{actual}}}} + c \\
S_n &=& (1-c)\me^{-\frac{-\tau_{\mathrm{step}}\log{\left(\frac{n}{N}\right)}}{\tau_{\mathrm{actual}}}} + c \\
S_n &=& (1-c)\me^{-\frac{\tau_{\mathrm{step}}}{\tau_{\mathrm{actual}}} \log{\left(\frac{N}{n}\right)}} + c \\
S_n &=& (1-c)\left(\frac{N}{n}\right)^{-\frac{\tau_{\mathrm{step}}}{\tau_{\mathrm{actual}}}} + c \\
S_n &=& (1-c)\left(\frac{n}{N}\right)^{\frac{\tau_{\mathrm{step}}}{\tau_{\mathrm{actual}}}} + c
\end{eqnarray}

\begin{figure}
	\includegraphics[scale=0.5]{"processing/PyCMDS/ideal axis positions/exponential"}
	\caption{
    Ideal axis positions, exponential decay.
  }
  \label{acq:fig:exponential_steps}
\end{figure}

\subsection{Simultanious acquisitions}  % ---------------------------------------------------------

Sometimes PyCMDS needs to do multiple scans that are completely orthogonal.  %
For example, certain OPA tuning operations only require signals from pyroelectric detectors that
are each permanently installed to monitor a single OPA.  %
In such cases, theoretically multiple OPAs could be tuned simultaneously.  %
The coordination of such operations would be more difficult than what currently exists, but could
be possible within PyCMDS' multithreaded design.  %

\subsection{Hotswappable hardware}  % -------------------------------------------------------------

One of the most important basic capabilities of PyCMDS is the ability to reconfigure itself
depending on what hardware is loaded.  %
However controlling the hardware configuration is currently more difficult than it should be.  %
Currently users need to shut down PyCMDS, manually edit INI files to configure which hardware are
included, and restart PyCMDS.  %
A better solution would allow users to ``hotswap''---load, or drop hardware and sensors
without shutting down PyCMDS.  %
The work of allowing this would also allow users to reload hardware, a great fallback option when
there are communication issues.  %

\subsection{wt5 savefile}  % ----------------------------------------------------------------------

The wt5 save format, introduced in WrightTools 3, is a huge improvement over the simple, plaintext
data format currently used by PyCMDS.  % TODO: link to discussion of wt5 format
wt5 files are smaller on disk, have richer metadata and self-descriptive properties, and multiple
scans can be stored in the same file using \python{Collections}.  % TODO: again, link
Because wt5 files are stored on disk but fully accessible through slicing, PyCMDS could have full
access to the scan arrays without risk of memory overflow, which promises to allow for new
potential visualizations during acquisition.
See \autoref{cha:pro} for more information about WrightTools.  %

Even more exciting, wt5 files can be instantiated as empty and \textit{then} filled.  %
This means that the wt5 file can be a self-describing set of destinations that actually defines
which pixels PyCMDS should visit.  %
% JCW: i don't think there is enough information provided to support these sentences