Welcome to Gadgetron’s documentation!¶
Release Notes¶
4.0¶
New interface for Gadgets, based on Channels, which makes it significantly easier to write new Gadgets. Old style Gadget interface still supported and are 100% compatible with the new interface.
Branching chains now supported
Vastly better support for distributed computing, which now allows for any Gadget which receives and sends standard Gadgetron datatypes to be distributed accross a many Gadgets.
Improved error handling. Gadgetron should now produce more meaningful errors and single Gadgets can no longer crash the Gadgetron instance.
Removed ACE. A compatiblity layer has been added to provide a standin for ACE_Message_Block. Importing ACE headers in any shape or form is no longer supported.
Added support for NumPy-like slicing for hoNDArrays.
Writing a Gadget¶
A Gadget is a Node
in the Gadgetron chain,
which processes data comming in through an GenericInputChannel
and
sends the processed data to the next Node
in the chain using an
The simplest Gadgets to write are PureGadget
and
ChannelGadget
.
PureGadget¶
A PureGadget
is a Gadget which processes Messages one at a time,
and holds no state. Examples could be a Gadget which removes oversampling on Acquisitions
,
or one which takes an Image
and performs autoscaling.
A PureGadget inheritss from PureGadget<OUTPUT,INPUT>
,
where OUTPUT and INPUT are the output type and input type of the Gadget.
AutoScaleGadget.h
#ifndef AUTOSCALEGADGET_H_
#define AUTOSCALEGADGET_H_
#include "Gadget.h"
#include "hoNDArray.h"
#include "gadgetron_mricore_export.h"
#include <ismrmrd/ismrmrd.h>
namespace Gadgetron{
class EXPORTGADGETSMRICORE AutoScaleGadget:
public Gadget2<ISMRMRD::ImageHeader,hoNDArray< float > >
{
public:
GADGET_DECLARE(AutoScaleGadget);
AutoScaleGadget();
virtual ~AutoScaleGadget();
protected:
GADGET_PROPERTY(max_value, float, "Maximum value (after scaling)", 2048);
virtual int process(GadgetContainerMessage<ISMRMRD::ImageHeader>* m1,
GadgetContainerMessage< hoNDArray< float > >* m2);
virtual int process_config(ACE_Message_Block *mb);
unsigned int histogram_bins_;
std::vector<size_t> histogram_;
float current_scale_;
float max_value_;
};
}
#endif /* AUTOSCALEGADGET_H_ */
The NODE_PROPERTY macro defines a variable on the AutoScaleGadget which can be set from the XML file defining the chain.
AutoScaleGadget.cpp
/*
* AutoScaleGadget.cpp
*
* Created on: Dec 19, 2011
* Author: Michael S. Hansen
*/
#include "AutoScaleGadget.h"
namespace Gadgetron{
AutoScaleGadget::AutoScaleGadget()
: histogram_bins_(100)
, current_scale_(1.0)
, max_value_(2048)
{
}
AutoScaleGadget::~AutoScaleGadget() {
// TODO Auto-generated destructor stub
}
int AutoScaleGadget::process_config(ACE_Message_Block* mb) {
max_value_ = max_value.value();
return GADGET_OK;
}
int AutoScaleGadget::process(GadgetContainerMessage<ISMRMRD::ImageHeader> *m1, GadgetContainerMessage<hoNDArray<float> > *m2)
{
if (m1->getObjectPtr()->image_type == ISMRMRD::ISMRMRD_IMTYPE_MAGNITUDE) { //Only scale magnitude images for now
float max = 0.0f;
float* d = m2->getObjectPtr()->get_data_ptr();
for (unsigned long int i = 0; i < m2->getObjectPtr()->get_number_of_elements(); i++) {
if (d[i] > max) max = d[i];
}
if (histogram_.size() != histogram_bins_) {
histogram_ = std::vector<size_t>(histogram_bins_);
}
for (size_t i = 0; i < histogram_bins_; i++) {
histogram_[i] = 0;
}
for (unsigned long int i = 0; i < m2->getObjectPtr()->get_number_of_elements(); i++) {
size_t bin = static_cast<size_t>(std::floor((d[i]/max)*histogram_bins_));
if (bin >= histogram_bins_) {
bin = histogram_bins_-1;
}
histogram_[bin]++;
}
//Find 99th percentile
long long cumsum = 0;
size_t counter = 0;
while (cumsum < (0.99*m2->getObjectPtr()->get_number_of_elements())) {
cumsum += (long long)(histogram_[counter++]);
}
max = (counter+1)*(max/histogram_bins_);
GDEBUG("Max: %f\n",max);
current_scale_ = max_value_/max;
for (unsigned long int i = 0; i < m2->getObjectPtr()->get_number_of_elements(); i++) {
d[i] *= current_scale_;
}
}
if (this->next()->putq(m1) < 0) {
GDEBUG("Failed to pass on data to next Gadget\n");
return GADGET_FAIL;
}
return GADGET_OK;
}
GADGET_FACTORY_DECLARE(AutoScaleGadget)
}
Note the GADGETRON_GADGET_EXPORT declaration, which produces the code causing the AutoScaleGadget to be loadable by Gadgetron.
ChannelGadget¶
PureGadget
can’t hold any state between different messages and must send
one message per input.
This makes it easier to reason about and implement, but is also limiting in cases where we want to accumulate multiple
messages for processing. In this case, ChannelGadget
should be used.
If we want to create a Gadget which takes several Acquisitions
and
reconstruct them, we inherit from ChannelGadget<Acquisition>
.
using namespace Gadgetron;
using namespace Gadgetron::Core;
class SimpleRecon : public ChannelGadget<Acquisition>{
public:
void process(InputChannel<Acquisition>& in, OutputChannel& out) override {
...
}
}
- We can take the messages from the channel either by calling
InputChannel::pop()
directly, or by using it in a for loop.
Channels are ranges, meaning they can be used directly with for loops and with algorithm from standard library, such as
std::transform()
and std::accumulate()
.
void process(InputChannel<Acquisition>& in, OutputChannel& out) override {
for (auto acquisition : in ) {
auto& header = std::get<ISMRMRD::AcquisitionHeader>(acquisition);
auto& data = std::get<hoNDArray<std::complex<float>>>(acquisition);
auto& trajectory = std::get<optional<hoNDArray<float>>>(acquisition);
//Gather acquisitions here
}
}
Or if you’re using C++17, this would be
void process(InputChannel<Acquisition>& in, OutputChannel& out) override {
for (auto [header, data, trajectory] : in ) {
//Gather acquisitions here
}
}
- We want to gather acquisitions until we have enough for a (possibly undersampled) image. The AcquisitionHeader has the
ISMRMRD::_ACQ_LAST_IN_ENCODE_STEP1 flag which we can use as a trigger. By importing channel_algorithms.h, we can write
void process(InputChannel<Acquisition>& in, OutputChannel& out) override {
auto split_condition = [](auto& message){
return std::get<ISMRMRD::AcquisitionHeader>(message).isFlagSet(ISMRMRD::ISMRMRD_ACQ_LAST_IN_ENCODE_STEP1);
};
for (auto acquisitions : buffer(in,split_condition)) {
for (auto [header, data, trajectory] : acquisitions ) {
//Gather acquisitions here
}
}
#include <gadgetron/Gadget.h>
#include <gadgetron/hoNDFFT.h>
#include <gadgetron/mri_core_utility.h>
#include <gadgetron/ChannelAlgorithms.h>
#include <gadgetron/log.h>
#include <gadgetron/mri_core_coil_map_estimation.h>
using namespace Gadgetron;
using namespace Gadgetron::Core;
using namespace Gadgetron::Core::Algorithm;
class SimpleRecon : public ChannelGadget<Acquisition> {
public:
SimpleRecon(const Context& context, const GadgetProperties& params) : ChannelGadget<Acquisition>(params), header{context.header} {
}
void process(InputChannel<Acquisition>& in, OutputChannel& out){
auto recon_size = header.encoding[0].encodedSpace.matrixSize;
ISMRMRD::AcquisitionHeader saved_header;
auto split_condition = [](auto& message){
return std::get<ISMRMRD::AcquisitionHeader>(message).isFlagSet(ISMRMRD::ISMRMRD_ACQ_LAST_IN_ENCODE_STEP1);
};
for (auto acquisitions : buffer(in,split_condition)) {
auto data = hoNDArray<std::complex<float>>(recon_size.x,recon_size.y,recon_size.z,header.acquisitionSystemInformation->receiverChannels.get());
for ( auto [acq_header, acq_data, trajectories] : acquisitions){
saved_header = acq_header;
data(slice,acq_header.idx.kspace_encode_step_1,0,slice) = acq_data;
}
hoNDFFT<float>::instance()->fft2c(data);
auto coil_map = coil_map_Inati(data);
data = coil_combine(data,coil_map,3);
auto image_header = image_header_from_acquisition(saved_header,header,data);
out.push(image_header,data);
}
}
private:
const ISMRMRD::IsmrmrdHeader header;
};
GADGETRON_GADGET_EXPORT(SimpleRecon)
Type matching¶
PureGadget
, ChannelGadget
as well as several other classes in Gadgetron have a template arguement defining what type of data they accept.
In the simplest case, this is simply a list of types. For instance
ChannelGadget<ISMRMRD::AcquisitionHeader, hoNDArray<std::complex<float>>>
will match any message starting with a AcquisitionHeader
followed by a hoNDArray
.
If the message has more parts, these will simply be discarded. Also note that this is equivalent to
ChannelGadget<tuple<ISMRMRD::AcquisitionHeader, hoNDArray<std::complex<float>>>>
Optional¶
If we want to include an element that will only appear some times, we can define it as optional. For instance, acquisitions can have a trajectory attached to them. This would look like
ChannelGadget<ISMRMRD::AcquisitionHeader, hoNDArray<std::complex<float>>, optional<hoNDArray<float>>>
and in fact Types.h defines Acquisition
as
using Acquisition = tuple<ISMRMRD::AcquisitionHeader, hoNDArray<std::complex<float>>,optional<hoNDArray<float>>>;
Variant¶
What if you need to create a ChannelGadget that accepts multiple types? For instance, one which receives both Acquisition and Waveform. In this case we can use a variant.
In order to work with the data, you call Core::visit
, which is modelled from std::visit.
For instance, a toy example which counts the number of data points in all waveforms and acquisitions could look like
API¶
Core¶
Nodes and Gadgets¶
-
class Gadgetron::Core::Node¶
Node is the base class for everything in a Gadgetron chain, including Gadgets and TypedChannelGadgets
Subclassed by Gadgetron::Core::GenericChannelGadget, Gadgetron::LegacyGadgetNode
Public Functions
-
virtual void process(GenericInputChannel &in, OutputChannel &out) = 0¶
The function which processes the data comming from the InputChannel. Conceptually a coroutine.
- Parameters
in – Channel from which messages are received from upstream
out – Channel in which messages are sent on downstream
-
virtual void process(GenericInputChannel &in, OutputChannel &out) = 0¶
-
class GenericChannelGadget : public Gadgetron::Core::Node, public Gadgetron::Core::PropertyMixin¶
Subclassed by Gadgetron::Core::ChannelGadget< TYPELIST >, Gadgetron::Core::GenericPureGadget
-
template<class ...TYPELIST>
class Gadgetron::Core::ChannelGadget : public Gadgetron::Core::GenericChannelGadget¶ A Node providing typed access to input data. Messages not matching the TYPELIST are simply passed to the next Node in the chain. Should be the first choice for writing new Gadgets.
- tparam TYPELIST
The type(s) of the messages to be received
Public Functions
-
inline virtual void process(GenericInputChannel &in, OutputChannel &out) final¶
The function which processes the data comming from the InputChannel. Conceptually a coroutine.
- Parameters
in – Channel from which messages are received from upstream
out – Channel in which messages are sent on downstream
-
virtual void process(InputChannel<TYPELIST...> &in, OutputChannel &out) = 0¶
The process function to be implemented when inheriting from this class.
- Parameters
in – A channel of the types specified in TYPELIST
out – Channel of output
-
class Gadgetron::Core::GenericPureGadget : public Gadgetron::Core::GenericChannelGadget¶
Subclassed by Gadgetron::Core::PureGadget< RETURN, INPUT >
Public Functions
-
inline virtual void process(GenericInputChannel &in, OutputChannel &out) final¶
The function which processes the data comming from the InputChannel. Conceptually a coroutine.
- Parameters
in – Channel from which messages are received from upstream
out – Channel in which messages are sent on downstream
-
inline virtual void process(GenericInputChannel &in, OutputChannel &out) final¶
-
template<class RETURN, class INPUT>
class PureGadget : public Gadgetron::Core::GenericPureGadget¶
Channels¶
-
class MessageChannel : public Gadgetron::Core::Channel¶
-
class Gadgetron::Core::OutputChannel¶
The end of a channel which provides output. Only constructible through make_channel(args)
Public Functions
-
OutputChannel(OutputChannel &&other) noexcept = default¶
-
OutputChannel &operator=(OutputChannel &&other) noexcept = default¶
-
void push_message(Message)¶
Pushes a message to the channel.
-
OutputChannel(OutputChannel &&other) noexcept = default¶
-
class Gadgetron::Core::GenericInputChannel : public ChannelRange<GenericInputChannel>¶
The end of a channel which provides input
Public Functions
-
GenericInputChannel(GenericInputChannel &&other) noexcept = default¶
-
GenericInputChannel &operator=(GenericInputChannel &&other) noexcept = default¶
-
Message pop()¶
Blocks until it can take a message from the channel.
-
GenericInputChannel(GenericInputChannel &&other) noexcept = default¶
-
template<class ...TYPELIST>
class InputChannel : public ChannelRange<InputChannel<TYPELIST...>>¶
Core Types¶
-
using Gadgetron::Core::Acquisition = tuple<ISMRMRD::AcquisitionHeader, hoNDArray<std::complex<float>>, optional<hoNDArray<float>>>¶
An Acquisition consists of a data header, the kspace data itself and optionally an array of kspace trajectories.
-
using Gadgetron::Core::Image = tuple<ISMRMRD::ImageHeader, hoNDArray<T>, optional<ISMRMRD::MetaContainer>>¶
An image consists of a header, an array of image data and optionally some metadata.
-
using Gadgetron::Core::Waveform = tuple<ISMRMRD::WaveformHeader, hoNDArray<uint32_t>>¶
A Waveform consiste of a header, followed by the raw Waveform data. See the MRD documentation page for more details.
-
using Gadgetron::Core::optional = boost::optional<T>¶
Warning
doxygentypedef: Cannot find typedef “Gadgetron::Core::variant” in doxygen xml output for project “Gadgetron” from directory: doc/xml
Warning
doxygenfunction: Cannot find function “Gadgetron::Core::visit” in doxygen xml output for project “Gadgetron” from directory: doc/xml