Skip to content

Commit a25d339

Browse files
Merge pull request #877 from TheDeanLab/add-new-device
Add new device documentation
2 parents f2f04c2 + 36ad863 commit a25d339

File tree

9 files changed

+418
-53
lines changed

9 files changed

+418
-53
lines changed

docs/source/add_new_device.rst

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
======================================
2+
Add a New Hardware Device (Advanced)
3+
======================================
4+
5+
**navigate** includes several standard hardware device types. These include:
6+
7+
- Cameras
8+
- Data Acquisition Cards
9+
- Filter Wheels
10+
- Galvo Scanners
11+
- Lasers
12+
- Deformable Mirrors
13+
- Remote Focusing Systems
14+
- Shutters
15+
- Stages
16+
- Zoom Devices
17+
18+
To add a new piece of hardware to one of these device types requires knowledge about
19+
the software's device abstraction layer. Here’s a detailed guide to help you
20+
integrate new ``CustomStage`` device into **navigate**. The same principles work for
21+
other device types.
22+
23+
.. note::
24+
A strong knowledge of Python and object-oriented programming is required to
25+
integrate new hardware devices into **navigate**.
26+
27+
----------------
28+
29+
What is the Device Abstraction Layer?
30+
-------------------------------------
31+
32+
To ensure compatibility and extendability, **navigate** utilizes a device abstraction
33+
layer, which allows the same commands to be used across different hardware devices.
34+
For example, all stages in **navigate** are programmed to include the `stop()`
35+
command, which can be used to stop the stage's movement. When someone
36+
hits the :guilabel:`Stop Stage` button on the GUI, this action is relayed from the
37+
``Controller`` to the ``Model`` and ultimately the ``CustomStage``, which communicates
38+
with the hardware in a device-specific format to stop the stage's movement.
39+
40+
--------------
41+
42+
Device Integration Approaches
43+
-----------------------------
44+
45+
There are two primary approaches to integrating new hardware into **navigate**:
46+
47+
- **Plugin**:
48+
If you want to continue to work with an up-to-date version of **navigate**, consider
49+
integrating your new hardware device as a plugin. This allows you to pull
50+
updates from the main repository without losing your custom hardware integration.
51+
It also allows you to integrate non-standard device types.
52+
Learn more about the plugin architecture :doc:`here <plugin/plugin_home>`, and
53+
how to write a custom plugin :doc:`here <advanced>`.
54+
- **Fork**:
55+
Alternatively, you can fork the **navigate** repository on GitHub and modify it
56+
directly. This is useful for custom, in-house developments. In select
57+
circumstances, you can contribute your changes back to the main repository
58+
through a pull request. Please contact the **navigate** development team for
59+
guidance on this approach.
60+
61+
--------------
62+
63+
Device Class Creation
64+
---------------------
65+
- New hardware devices must have a corresponding device class in navigate. To ensure
66+
consistency and reduce redundancy, each device must inherit the appropriate abstract
67+
base class. For instance, a ``CustomStage`` device would inherit from ``StageBase``.
68+
- Classes should follow CamelCase naming conventions and reflect the device they
69+
control (e.g., ``NewportStage`` for a stage from the manufacturer Newport).
70+
- Place the new device class within the appropriate device directory,
71+
`src/navigate/model/devices/`.
72+
- Place related API or hardware documentation within the appropriate manufacturer
73+
directory, typically under `src/navigate/model/devices/APIs/`.
74+
75+
--------------
76+
77+
Establish Device Communication
78+
------------------------------
79+
80+
- Each device requires a unique method to initialize a connection, which may
81+
involve APIs, serial communication, or other protocols. This method should be
82+
separate from the device class and is typically located at the beginning of the
83+
device file.
84+
- For example, a function named `build_custom_stage_connection()` would handle
85+
the connection setup for ``CustomStage`` class.
86+
- By separating the connection setup from the device class, you can easily
87+
interact with the hardware device outside of the larger **navigate** ecosystem,
88+
which can be useful for debugging and testing (e.g., within a Jupyter notebook).
89+
90+
--------------
91+
92+
Device Class Constructor
93+
------------------------
94+
95+
- The constructor for the device class (`__init__`) should accept parameters for
96+
the `microscope_name`, `device_connection`, `configuration_file`, and an optional
97+
`device_ID` (useful when multiple instances of the same device are used).
98+
- The constructor should load and enforce device settings from the
99+
`configuration_file`. For a new stage, this could be defining the axes mapping
100+
between **navigate** and the device, `{x:'X', y:'Y', z:'Z'}`.
101+
- Ensure the device class uses the connection established by your
102+
`build_custom_stage_connection` method.
103+
104+
--------------
105+
106+
Device Class Methods
107+
--------------------
108+
109+
- Implement any necessary device-specific methods within your device class.
110+
- Essential methods are inherited from the base class (e.g., ``StageBase`` for the
111+
``CustomStage``), but you can override them or add new methods as needed for
112+
specialized functionality.
113+
114+
--------------
115+
116+
Startup and Configuration
117+
-------------------------
118+
119+
- Utilize or modify methods within `src/navigate/model/device_startup_functions` to
120+
configure and start your device upon system initialization.
121+
- These functions should handle configuration parsing and the device communication
122+
setup.
123+
- Implement a retry mechanism, such as `auto_redial`, to handle communication
124+
issues robustly, attempting multiple times before failing.
125+
126+
--------------
127+
128+
Integration with Microscope Object Configurations
129+
------------------------------------------------
130+
131+
- Each microscope configuration in **navigate** that uses the new device should
132+
receive a reference to the established communication object.
133+
- This setup is defined in the *configuration.yaml* and handled within the
134+
`device_startup_functions`, ensuring each configuration has access to the
135+
necessary hardware.
136+
137+
--------------
138+
139+
Testing and Validation
140+
----------------------
141+
- Thoroughly test the new hardware integration to ensure it functions correctly
142+
within navigate, across all intended use cases and configurations.
143+
- The naming convention for test files is: `test_` + module name.
144+
- Device test files are located in `test\model\devices\`
145+
- Device testing utilizes the `pytest` package.
146+
147+
By following these steps, you can effectively integrate new hardware into the
148+
**navigate** platform, enhancing its functionality and ensuring it meets specific
149+
experimental needs.

docs/source/advanced.rst

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
==============================
2-
Write a Custom Plugin (Expert)
3-
==============================
1+
=======================================
2+
Write a Custom Device Plugin (Advanced)
3+
=======================================
44

55
**navigate**'s :doc:`plugin system <plugin/plugin_home>` enables users to
6-
easily incorporate new devices and integrate new features and acquisition modes. In
7-
this guide, we will add a new device, titled ``CustomDevice``, and a dedicated GUI
6+
easily incorporate new devices and integrate new features and acquisition modes. In
7+
this guide, we will add a new device type, titled ``CustomDevice``, and a dedicated GUI
88
window to control it. This hypothetical ``CustomDevice`` is capable of moving a certain
9-
distance, rotating a specified number of degrees, and applying a force to halt its
9+
distance, rotating a specified number of degrees, and applying a force to halt its
1010
movement.
1111

12-
**navigate** plugins are implemented using a Model-View-Controller architecture. The
12+
**navigate** plugins are implemented using a Model-View-Controller architecture. The
1313
model contains the device-specific code, the view contains the GUI code, and the
1414
controller contains the code that communicates between the model and the view.
1515

@@ -18,7 +18,7 @@ controller contains the code that communicates between the model and the view.
1818
Initial Steps
1919
-------------
2020

21-
To ease the addition of a new plugin, we have created a template plugin that can be
21+
To ease the addition of a new plugin, we have created a template plugin that can be
2222
used as a starting point.
2323

2424
* Go to `navigate-plugin-template <https://github.com/TheDeanLab/navigate-plugin-template>`_.
@@ -89,9 +89,9 @@ Create a new custom device using the following code.
8989
}
9090
9191
92-
All devices should be accompanied by synthetic versions, which enables the software
93-
to run without the hardware connected. Thus, in a manner that is similar to the
94-
``CustomDevice`` class, we edit the code in ``synthetic_device.py``, albeit without
92+
All devices should be accompanied by synthetic versions, which enables the software
93+
to run without the hardware connected. Thus, in a manner that is similar to the
94+
``CustomDevice`` class, we edit the code in ``synthetic_device.py``, albeit without
9595
any calls to the device itself.
9696

9797
.. code-block:: python
@@ -150,15 +150,15 @@ any calls to the device itself.
150150
}
151151
152152
153-
Edit ``device_startup_functions.py`` to tell **navigate** how to connect to and start
153+
Edit ``device_startup_functions.py`` to tell **navigate** how to connect to and start
154154
the ``CustomDevice``. This is the portion of the code that actually makes a connection
155-
to the hardware. ``load_device()`` should return an object that can control the
155+
to the hardware. ``load_device()`` should return an object that can control the
156156
hardware.
157157

158-
**navigate** establishes communication with each device independently, and passes the
159-
instance of that device to class that controls it (e.g., in this case, the
160-
`CustomDevice` class). This allows **navigate** to be initialized with multiple
161-
microscope :ref:`configurations <user_guide/software_configuration:configuration file>`,
158+
**navigate** establishes communication with each device independently, and passes the
159+
instance of that device to class that controls it (e.g., in this case, the
160+
`CustomDevice` class). This allows **navigate** to be initialized with multiple
161+
microscope :ref:`configurations <user_guide/software_configuration:configuration file>`,
162162
some of which may share devices.
163163

164164
.. code-block:: python
@@ -325,17 +325,17 @@ to ``custom_device_frame.py``, and edit the code as follows.
325325
.. tip::
326326

327327
**navigate** comes equipped with a large number of validated widgets,
328-
which prevent users from entering invalid values that can crash the program or
329-
result in undesirable outcomes. It is highly recommended that you use these,
328+
which prevent users from entering invalid values that can crash the program or
329+
result in undesirable outcomes. It is highly recommended that you use these,
330330
which include the following:
331331

332-
* The ``LabelInput`` widget conveniently combines a label and an input widget
332+
* The ``LabelInput`` widget conveniently combines a label and an input widget
333333
into a single object. It is used to create the ``step_size`` and ``angle``
334334
widgets in the code above.
335335
* The ``LabelInput`` widget can accept multiple types of ``input_class`` objects,
336-
which can include standard tkinter widgets (e.g., spinbox, entry, etc.) or
336+
which can include standard tkinter widgets (e.g., spinbox, entry, etc.) or
337337
custom widgets. In this example, we use the ``ttk.Entry`` widget.
338-
* Other examples of validated widgets include a ``ValidatedSpinbox``,
338+
* Other examples of validated widgets include a ``ValidatedSpinbox``,
339339
``ValidatedEntry``, ``ValidatedCombobox``, and ``ValidatedMixin``.
340340
* Please see the :any:`navigate.view.custom_widgets` module for more details.
341341

@@ -420,17 +420,17 @@ as follows.
420420
self.parent_controller.execute("stop_custom_device")
421421
422422
423-
In each case above, the sub-controller for the ``custom-device`` establishes what
424-
actions should take place once a button in the view is clicked. In this case, the
425-
methods ``move_device``, ``rotate_device``, and ``stop_device``. This triggers a sequence
423+
In each case above, the sub-controller for the ``custom-device`` establishes what
424+
actions should take place once a button in the view is clicked. In this case, the
425+
methods ``move_device``, ``rotate_device``, and ``stop_device``. This triggers a sequence
426426
of events:
427427

428-
* The sub-controller passes the command to the parent controller, which is the
428+
* The sub-controller passes the command to the parent controller, which is the
429429
main controller for the software.
430-
* The parent controller passes the command to the model, which is operating in
431-
its own sub-process, using an event queue. This eliminates the need for the
430+
* The parent controller passes the command to the model, which is operating in
431+
its own sub-process, using an event queue. This eliminates the need for the
432432
controller to know anything about the model and prevents race conditions.
433-
* The model then executes command, and any updates to the controller from the
433+
* The model then executes command, and any updates to the controller from the
434434
model are relayed using another event queue.
435435

436436
-----------------
@@ -459,18 +459,18 @@ the file ``plugin_acquisition_mode.py``. The plugin folder structure is as follo
459459
│ └── custom_device/
460460
│ ├── device_startup_functions.py
461461
│ ├── custom_device.py
462-
│ └── synthetic_device.py
462+
│ └── synthetic_device.py
463463
├── view/
464464
| └── custom_device_frame.py
465465
466-
└── plugin_config.yml
466+
└── plugin_config.yml
467467
468468
469469
Install the plugin using one of two methods:
470-
* Install a plugin by putting the whole plugin folder directly into
471-
``navigate/plugins/``. In this example, put ``custom_device`` folder
470+
* Install a plugin by putting the whole plugin folder directly into
471+
``navigate/plugins/``. In this example, put ``custom_device`` folder
472472
and all its contents into ``navigate/plugins``.
473-
* Alernatively, install this plugin through the menu
473+
* Alernatively, install this plugin through the menu
474474
:menuselection:`Plugins --> Install Plugin` by selecting the plugin folder.
475475

476476
The plugin is ready to use. For this plugin, you can now specify a CustomDevice in the
@@ -489,7 +489,7 @@ The plugin is ready to use. For this plugin, you can now specify a CustomDevice
489489
hardware:
490490
type: CustomDevice
491491
...
492-
492+
493493
494494
The ``custom_device`` will be loaded when **navigate** is launched, and it can be
495495
controlled through the GUI.

docs/source/conf.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
# -- Project information -----------------------------------------------------
2121

2222
project = "navigate"
23-
copyright = "2023, Dean Lab, UT Southwestern Medical Center"
23+
copyright = "2024, Dean Lab, UT Southwestern Medical Center"
2424
author = "Dean Lab, UT Southwestern Medical Center"
2525

2626
# The full version, including alpha/beta/rc tags
@@ -111,17 +111,25 @@
111111

112112
# -- LaTeX output options ----------------------------------------------------
113113

114-
latex_elements = {'preamble': r'''
114+
latex_elements = {
115+
"preamble": r"""
115116
\usepackage[utf8]{inputenc}
116117
\usepackage{enumitem}
117118
\setlistdepth{99}
118119
\DeclareUnicodeCharacter{03BC}{$\mu$}
119-
''',
120-
'extraclassoptions': 'openany,oneside'}
120+
""",
121+
"extraclassoptions": "openany,oneside",
122+
}
121123

122124
latex_documents = [
123-
('index', 'navigate.tex', 'navigate Documentation',
124-
'Dean Lab, UT Southwestern Medical Center', 'manual', True),
125+
(
126+
"index",
127+
"navigate.tex",
128+
"navigate Documentation",
129+
"Dean Lab, UT Southwestern Medical Center",
130+
"manual",
131+
True,
132+
),
125133
]
126134

127-
latex_domain_indices = False
135+
latex_domain_indices = False

docs/source/i_want_to.rst

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,17 @@
22
I Want To...
33
============
44

5-
This section contains how-to documents organized into three levels of difficulty:
5+
This section contains how-to documents organized into three levels of difficulty:
66
beginner, intermediate, and advanced.
77

88
- The beginner how-to is intended for users who have little to no experience with
99
computer programming and simply wish to acquire an image.
1010

11-
- The intermediate how-to is intended for users who want to learn how to use the
11+
- The intermediate how-to is intended for users who want to learn how to use the
1212
graphical user interface to create their own smart acquisition workflows.
1313

1414
- The advanced how-to is intended for users who have experience with computer
15-
programming and wish to extend the functionality of **navigate** by adding new
15+
programming and wish to extend the functionality of **navigate** by adding new
1616
devices or writing their own acquisition features.
1717

1818
Through this tiered approach, we hope to provide a gentle introduction to the software
@@ -23,4 +23,5 @@ while also reaching the maximum number of users.
2323

2424
beginner
2525
intermediate
26-
advanced
26+
advanced
27+
add_new_device

0 commit comments

Comments
 (0)