File: sight.md

package info (click to toggle)
sight 25.2.0-2
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 42,184 kB
  • sloc: cpp: 289,476; xml: 17,257; ansic: 9,878; python: 1,379; sh: 144; makefile: 33
file content (239 lines) | stat: -rw-r--r-- 7,193 bytes parent folder | download | duplicates (2)
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
---
description: Sight coding guidelines
---

This is Sight, the Surgical Image Guidance and Healthcare Toolkit.

Sight is based on a component architecture composed of C++ libraries. It uses an object-service concept approach.
Object and services communicates using signals and slots.

It is mainly written in C++20. Components and applications are described with XML files.

The documentation is available at https://sight.pages.ircad.fr/sight-doc/.

All code and comments are in english.

## File hierarchy

The codebase is organized as follows:
- /app: applications written in XML
- /cmake: build files with CMake
- /config: dynamic components containing only shared configurations in XML
- /example: more elaborated code than tutorials
- /lib: shared code
- /module: dynamic components that can be assigned to an application. Most services are implemented inside modules.
- /tutorial: simple samples that demonstrate basic features with an increasing difficulty
- /util: standalone executables

## Includes

Includes headers should always be minimal.
For header guards, we use the `#pragma once` directive.

## Namespaces

We use namespaces everywhere, and they follow the file tree layout from the root folders.
In C++, we specify namespaces on a single line


| Folder | Namespace | Example folder | Example namespace |
| --- | --- | --- | --- |
| app | sight::app_name | app/viewer | sight::viewer |
| config | sight::config::config_name | config/filter/image | sight::filter::image |
| lib | sight::lib_name | lib/filter/image | sight::filter::image |
| module | sight::module::module_name | lib/filter/image | sight::module::filter::image |

Creating a new module is described in https://sight.pages.ircad.fr/sight-doc/HowTos/ModuleCreation.html
Creating a new service is described in https://sight.pages.ircad.fr/sight-doc/HowTos/ServiceCreation.html#howtosservicecreation

## Testing

Unit-tests are present in each CMake target with C++ code. They lie in the target under the `test/ut` folder.
We use Doctest for unit testing. Each cpp file contains one or more TEST_SUITE(), named after the C++ class for instance
it tests (i.e. "sight::data::image"). The test name is free but must be comprehensive and not hold a redundant "test"
prefix or suffix.

## How-tos

### Create a new service

[//]: #cspell:ignore noexcept

To create a new service in a module, you must:

1. write a .hpp file with the following skeleton:

```cpp
namespace sight::module::my_module
{

class my_service final : public sight::service::base
{
public:
    SIGHT_DECLARE_SERVICE(my_service, sight::service::base);

    // Service constructor
    my_service() noexcept = default;

    // Service destructor.
    ~my_service() noexcept final = default;

protected:

    // To configure the service
    void configuring(const config_t& _config) final;

    // To start the service
    void starting() final;

    // To stop the service
    void stopping() final;

    // To update the service
    void updating() final;
};

} // namespace sight::module::my_module

```

A new service can be created by taking input from various data types, such as those found in the `lib/__/data`
directory. These data are always smart-pointed and registered with key name using `sight::data::ptr` for single data
or `sight::data::ptr_vector` for multiple data, for instance for `sight::data::image` :

```cpp
sight::data::ptr<sight::data::image> m_image { this, "image" };
```

See https://sight.pages.ircad.fr/sight-doc/SAD/src/ObjService.html for more details.

2. A corresponding .cpp file that implements the above methods.
3. Register the service in the `rc/plugin.xml` file, for example:

```xml
    <extension implements="::sight::service::extension::factory">
        <service>sight::module::my_module</service>
        <type>sight::service::base</type>
        <object>sight::data::image</object>
    </extension>
```

4. Create the associated unit-test in the `test/ut` folder.

### Create a unit-test for a service

Every service must-be unit-tested. It is recommended to use a test fixture to initialize and destroy the service, this
way it is properly unregistered if the test fails, allowing other tests to be launched. Here is an example with a
service that works on an image.

```cpp
#include <data/image.hpp>

#include <service/op.hpp>

#include <boost/property_tree/xml_parser.hpp>

#include <doctest/doctest.h>

//------------------------------------------------------------------------------

TEST_SUITE("sight::module::my_service")
{
    class service_fixture
    {
    public:

        service_fixture()
        {
            service = sight::service::add("sight::module::my_service");
            REQUIRE(service);
            REQUIRE(service->is_a("sight::module::my_service"));

            image = std::make_shared<sight::data::image>();
            service->set_input(image, "image");
        }

        ~service_fixture()
        {
            if(service->started())
            {
                service->stop().get();
            }
            sight::service::remove(service);
        }

        sight::service::base::sptr service;
        sight::data::image::sptr image;
    };

    TEST_CASE_FIXTURE(service_fixture, "test_1")
    {
        sight::service::config_t config;
        std::stringstream config_string;
        config_string
        << "<properties prop1=\"90\" prop2=\"30\"/>";

        boost::property_tree::read_xml(config_string, config);
        service->set_config(config);
        service->configure();
        service->start().get();
        service->update().get();

        CHECK(...);
    }

    TEST_CASE_FIXTURE(service_fixture, "test_2")
    {
        sight::service::config_t config;
        std::stringstream config_string;
        config_string
        << "<properties prop1=\"90\" prop2=\"30\"/>";

        boost::property_tree::read_xml(config_string, config);
        service->set_config(config);
        service->configure();
        service->start().get();
        service->update().get();

        CHECK(...);
    }
}
```

The filename must be `test/ut/my_service_test.cpp`.

In tests, services must be instantiated using the factory:

```cpp
auto srv = sight::service::add("sight::module::my_service");
```
and removed using:

```cpp
sight::service::remove(srv);
```

Services data (input/inout) must be setup as usual following the instructions in
https://sight.pages.ircad.fr/sight-doc/SAD/src/ObjService.html#object-service-registration.

Sight services always protect their member variables so they can't be tested. Instead we usually monitor the input/inout
data. We can also bind local slots in the test to services signals.

Here is an example to test for instance to test the internal state of a service, assuming the `updating()` method of
the service triggers the `sample_signal`.

```cpp
    bool call_slot = false;
    auto slot_called = sight::core::com::new_slot(
        [&call_slot]()
        {
            call_slot = true;
        });

    slot_called->set_worker(sight::core::thread::get_default_worker());
    srv->signal("sample_signal")->connect(slot_called);

    srv->update().get();

    SIGHT_TEST_WAIT(call_slot == true);
```