Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 59 additions & 2 deletions OpenScanDeviceLib/include/OpenScanDeviceLib.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,20 @@ typedef struct OScDev_SettingImpl OScDev_SettingImpl;
// functions.
typedef struct OScInternal_Device OScDev_Device;
typedef struct OScInternal_Setting OScDev_Setting;

/// Handle to an acquisition, for use by one participating device.
/**
* Each device participating in an acquisition receives its own distinct
* handle to the same acquisition, in the call to its `Arm` function.
*
* The handle is owned by the host application. The device module may store
* it and use it from any of its threads, but only until the module indicates
* that the acquisition is finished on this device; that is, until it returns
* from a `Stop` call with the device idle, returns from `Wait`, or lets
* `IsRunning` report false. From that instant on, the host is allowed to
* free the handle, so no code in the module (including background threads)
* may access it.
*/
typedef struct OScInternal_AcquisitionForDevice OScDev_Acquisition;
typedef struct RERR_Error OScDev_RichError;
#define OScDev_RichError_OK ((OScDev_RichError *)NULL)
Expand Down Expand Up @@ -556,6 +570,13 @@ struct OScDev_DeviceImpl {
*/
OScDev_Error (*Open)(OScDev_Device *device);

/// Close the connection to the device.
/**
* If an acquisition is armed or running on this device, this function
* must stop it and wait for all acquisition activity (including any
* module threads that use the `OScDev_Acquisition` handle) to complete
* before returning, exactly as if `Stop` had been called.
*/
OScDev_Error (*Close)(OScDev_Device *device);

/// Return true if this device can provide the clock for scanning and
Expand Down Expand Up @@ -736,6 +757,15 @@ struct OScDev_DeviceImpl {
* and detector may not be clear. The implementation should behave _as if_
* the three components were separate and behaved as specified above.
*
* This function is called at most once per device per acquisition, even
* when the device serves multiple roles. The handle `acq` may be stored
* and used for the duration of the acquisition, subject to the validity
* window documented at `OScDev_Acquisition`.
*
* If the device is already armed or running a previous acquisition, this
* function must fail with an error, leaving the previous acquisition
* undisturbed. OpenScanLib does not currently prevent such calls.
*
* \todo Currently, state change after the device is armed is handled in a
* variable way depending on what triggers the state change (especially
* stop of acquisition). We should replace this with a simpler interface
Expand Down Expand Up @@ -782,6 +812,19 @@ struct OScDev_DeviceImpl {
* and return only when the device is ready to be armed again with a new
* acquisition.
*
* This function may be called multiple times in succession: OpenScanLib
* calls it once for each role (clock, scanner, detector) that the device
* serves in the acquisition, and also calls it to clean up when arming
* another device fails.
*
* This function must also handle the case where the device is armed but
* the acquisition was never started, by disarming and returning; it must
* not block waiting for a start trigger that will never arrive.
*
* Once this function returns with the device idle, the host may free the
* acquisition handle (see `OScDev_Acquisition`), so all use of the
* handle by the module must have ceased by that point.
*
* \todo This function should be split into two separate functions,
* `SoftwareTriggerStop()` and `Disarm()`, with very different roles.
* The former should merely be an asynchronous signal that the device
Expand All @@ -806,7 +849,11 @@ struct OScDev_DeviceImpl {
*
* In all cases, this function should not return `false` until all aspects
* of the acquisition have completed and the device is ready to arm for a
* new acquisition.
* new acquisition. In particular, reporting false is a release point for
* the acquisition handle: once this function returns `false`, the host
* may free the handle (see `OScDev_Acquisition`), so the module must not
* report false while any of its threads may still use the handle (for
* example, to deliver frame callbacks).
*
* \todo This function should be replaced with a callback-based interface
* to avoid polling and simplify device module implementation.
Expand All @@ -821,6 +868,10 @@ struct OScDev_DeviceImpl {
* This function should return immediately if `IsRunning()` is already
* false.
*
* After this function returns, the host may free the acquisition handle
* (see `OScDev_Acquisition`); no module thread may use the handle or
* deliver further frame callbacks.
*
* \todo This function should be removed, because it leads to reinvention
* of condition variable code in each device module. Better to report state
* changes using a callback interface.
Expand Down Expand Up @@ -1286,12 +1337,18 @@ OScDev_API void OScDev_Acquisition_GetROI(OScDev_Acquisition *acq,
* This function must be called during an acquisition by the device that owns
* the detector for the acquisition.
*
* It may be called from any thread of the device module, but only while the
* acquisition handle is valid (see `OScDev_Acquisition`).
*
* The data pointed to by `pixels` must not change during the call to this
* function.
* function. The call blocks while the application processes the frame data.
*
* \param[in] acq the acquisition that was given when arming this device
* \param[in] channel the channel index
* \param[in] pixels the raw pixel data for the channel
* \return `true` normally, or `false` if the application requests
* cancellation of the acquisition, in which case the module should stop the
* acquisition as soon as practical
*/
OScDev_API bool OScDev_Acquisition_CallFrameCallback(OScDev_Acquisition *acq,
uint32_t channel,
Expand Down
138 changes: 138 additions & 0 deletions OpenScanLib/include/OpenScanLib.h
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,20 @@ typedef struct OScInternal_AcqTemplate OSc_AcqTemplate;
/**
* \brief An acquisition object, managing the state and data transfer for
* data acquisition.
*
* An acquisition is created from an OSc_AcqTemplate by
* OSc_Acquisition_Create(). The only supported sequence of operations is
* OSc_Acquisition_Arm(), then OSc_Acquisition_Start(), then (once the
* acquisition has been stopped with OSc_Acquisition_Stop() or has completed
* on its own) OSc_Acquisition_Wait(), and finally OSc_Acquisition_Destroy().
*
* At most one acquisition may be live (armed or running) at any given time
* for a given device, and therefore in practice for a given LSM. OpenScanLib
* does not currently enforce this.
*
* The acquisition holds non-owning references to the LSM's devices, so the
* devices (and the LSM) must remain valid for the lifetime of the
* acquisition object.
*/
typedef struct OScInternal_Acquisition OSc_Acquisition;

Expand All @@ -231,6 +245,19 @@ typedef void (*OSc_SettingInvalidateFunc)(OSc_Setting *setting, void *data);

/**
* \brief Pointer to function that receives acquired frame data.
*
* The callback is invoked on device-module-internal threads, potentially
* concurrently with application calls into OpenScanLib and (when multiple
* detector devices or channels are involved) concurrently with itself. It
* must copy the pixel data before returning.
*
* The callback must not call OSc_Acquisition_Stop(), OSc_Acquisition_Wait(),
* or OSc_Acquisition_Destroy(); doing so may deadlock. To cancel the
* acquisition from the callback, return `false`.
*
* For a given acquisition, no calls to the callback occur after
* OSc_Acquisition_Stop() or OSc_Acquisition_Wait() has returned.
*
* \sa OSc_Acquisition_SetFrameCallback()
* \param acq the acquisition
* \param channel the channel number (zero-based)
Expand Down Expand Up @@ -361,6 +388,11 @@ OSc_API void OSc_SetDeviceModuleSearchPaths(const char **paths);
OSc_API OSc_RichError *OSc_LSM_Create(OSc_LSM **lsm);

/**
* \brief Destroy an LSM, closing all devices associated with it.
*
* The same precondition as OSc_Device_Close() applies: no device associated
* with the LSM may be participating in an armed or running acquisition.
*
* \todo Return value should be `void`.
*/
OSc_API OSc_RichError *OSc_LSM_Destroy(OSc_LSM *lsm);
Expand Down Expand Up @@ -449,6 +481,16 @@ OSc_API OSc_RichError *OSc_Device_GetDisplayName(OSc_Device *device,
OSc_API OSc_RichError *OSc_Device_Open(OSc_Device *device, OSc_LSM *lsm);

/**
* \brief Close a device.
*
* The device must not be participating in an armed or running acquisition;
* stop the acquisition (and have OSc_Acquisition_Stop() or
* OSc_Acquisition_Wait() return) before closing. As a safety net, device
* modules are required to stop and wait for any live acquisition when
* closing, but an OSc_Acquisition object that outlives this call then
* references a closed device, and the only operation that remains valid on
* it is OSc_Acquisition_Destroy().
*
* \todo Return value should be `void`.
*/
OSc_API OSc_RichError *OSc_Device_Close(OSc_Device *device);
Expand Down Expand Up @@ -595,8 +637,42 @@ OSc_API OSc_RichError *
OSc_AcqTemplate_GetBytesPerSample(OSc_AcqTemplate *tmpl,
uint32_t *bytesPerSample);

/**
* \brief Create a new acquisition from the current settings of a template.
*
* The acquisition takes a snapshot of the template's settings; subsequent
* changes to the template do not affect it.
*
* The acquisition holds non-owning references to the LSM's clock and scanner
* devices and its enabled detector devices. These devices, and the LSM, must
* remain valid (open and not destroyed) for the lifetime of the acquisition
* object.
*
* The caller owns the returned acquisition and must destroy it with
* OSc_Acquisition_Destroy().
*
* \param[out] acq location where the pointer to the new acquisition will be
* written
* \param tmpl the acquisition template
*/
OSc_API OSc_RichError *OSc_Acquisition_Create(OSc_Acquisition **acq,
OSc_AcqTemplate *tmpl);

/**
* \brief Destroy an acquisition.
*
* This frees the acquisition object, including the per-device handles that
* were passed to the participating device modules.
*
* The acquisition must not be armed or running: a call to
* OSc_Acquisition_Stop() (or, if the acquisition completed on its own,
* OSc_Acquisition_Wait()) must have returned before this function is called.
* Destroying an armed or running acquisition results in undefined behavior,
* because device modules may still access the acquisition from their
* internal threads. OpenScanLib does not currently detect this misuse.
*
* The participating devices are not affected by this function.
*/
OSc_API OSc_RichError *OSc_Acquisition_Destroy(OSc_Acquisition *acq);
OSc_API OSc_RichError *
OSc_Acquisition_SetNumberOfFrames(OSc_Acquisition *acq,
Expand All @@ -621,9 +697,71 @@ OSc_Acquisition_GetNumberOfChannels(OSc_Acquisition *acq,
OSc_API OSc_RichError *
OSc_Acquisition_GetBytesPerSample(OSc_Acquisition *acq,
uint32_t *bytesPerSample);
/**
* \brief Arm an acquisition, preparing all participating devices.
*
* Each device participating in the acquisition (clock, scanner, and
* detectors) is armed exactly once, even if it serves multiple roles. This
* function must be called, and succeed, before OSc_Acquisition_Start().
*
* If arming any device fails, the devices already armed for this acquisition
* are stopped before the error is returned, leaving all of the devices
* disarmed.
*
* No participating device may be armed or running for another acquisition.
* OpenScanLib does not currently detect violations of this precondition, and
* behavior in that case is undefined (dependent on the device modules).
*/
OSc_API OSc_RichError *OSc_Acquisition_Arm(OSc_Acquisition *acq);

/**
* \brief Start an armed acquisition.
*
* This delivers the software start trigger to the device providing the
* clock. Call it exactly once per acquisition, after OSc_Acquisition_Arm()
* has succeeded. OpenScanLib does not currently check this ordering;
* starting an acquisition that is not armed is rejected only by some device
* modules.
*/
OSc_API OSc_RichError *OSc_Acquisition_Start(OSc_Acquisition *acq);

/**
* \brief Stop an acquisition.
*
* This requests every participating device to stop, and blocks until all of
* them have fully stopped and are ready to be armed again. Once this
* function returns, no further frame callbacks occur and the acquisition may
* be destroyed.
*
* This function is idempotent, and is safe to call on an acquisition that
* was never armed.
*
* This function must not be called from within the frame callback (see
* OSc_FrameCallback).
*
* \attention Stopping acts on the participating devices, not on this
* acquisition object specifically. Calling this function on an acquisition
* whose devices are participating in a different live acquisition will stop
* that acquisition.
*/
OSc_API OSc_RichError *OSc_Acquisition_Stop(OSc_Acquisition *acq);

/**
* \brief Wait for an acquisition to finish.
*
* This blocks until the acquisition has finished on all participating
* devices, whether by a call to OSc_Acquisition_Stop() or by completing the
* requested number of frames; it returns immediately if the acquisition is
* not armed or running. Once this function returns, no further frame
* callbacks occur and the acquisition may be destroyed.
*
* This function does not itself request a stop, so waiting for an
* acquisition that does not finish on its own will not return until
* OSc_Acquisition_Stop() is called from another thread.
*
* This function must not be called from within the frame callback (see
* OSc_FrameCallback).
*/
OSc_API OSc_RichError *OSc_Acquisition_Wait(OSc_Acquisition *acq);

/** @} */ // addtogroup api
Expand Down
Loading