一、OpenIGTLink
OpenIGTLink,可以简称为IGTL。它是一个专供医疗应用的网络通信库。IGTL可以应用各种传感器、手术机器人和成像仪等的数据传输。OpenIGTLink是有一系列的协议在底层支持的,它是跨平台的,应用也非常简单。
OpenIGTLink迭代还是比较快的,目前已经到了3.0版本。更多的对其的技术相关资料,请查阅相关的资料或访问其官方地址及github。
二、IGTL的应用
IGTL在医疗行业应用还是比较多的,它的优点是相对简单,支持的场景也相对较多。缺点就是相对简单,无法处理一些复杂的网络应用。不过话又说回来,在医疗行业这种场景下,一般也没有复杂的网络应用,一般就是几台机器通信,能复杂到哪儿。
IGTL的应用重点还是在业务上,即它支持医疗行业的设备间的状态(STATUS)、图像(IMAGE)和命令(COMMAND)等各种消息格式。其实如果认真的向下看Message构成的情况就会发现,和传统的网络通信中定义的协议没有什么不同。封装起来的目的就是为了简单好用。
三、Supra中的应用
在初步简单了解了一下IGTL后,就看一下在Supra中如何使用这个库,相关代码如下:
1、做为服务端使用
void OpenIGTLinkOutputDevice::initializeOutput()
{
log_info("IGTL: server port: ", m_server->GetServerPort());
if (m_server->CreateServer(m_port) != 0) {
m_isReady = false;
}
else {
m_isReady = true;
//Wait asynchronously for the connection
waitAsyncForConnection();
}
}
template <typename T>
void OpenIGTLinkOutputDevice::sendImageMessageTemplated(shared_ptr<const USImage> imageData)
{
static_assert(
std::is_same<T, uint8_t>::value ||
std::is_same<T, int16_t>::value ||
std::is_same<T, float>::value,
"Image only implemented for uchar, short and float at the moment");
auto properties = imageData->getImageProperties();
if (
properties->getImageType() == USImageProperties::BMode ||
properties->getImageType() == USImageProperties::Doppler)
{
double resolution = properties->getImageResolution();
vec3s imageSize = imageData->getSize();
igtl::ImageMessage::Pointer pImageMsg = igtl::ImageMessage::New();
pImageMsg->SetDimensions((int)imageSize.x, (int)imageSize.y, (int)imageSize.z);
pImageMsg->SetSpacing(resolution, resolution, resolution);
if (is_same<T, uint8_t>::value)
{
pImageMsg->SetScalarTypeToUint8();
}
if (is_same<T, int16_t>::value)
{
pImageMsg->SetScalarTypeToInt16();
}
if (is_same<T, float>::value)
{
pImageMsg->SetScalarType(igtl::ImageMessage::TYPE_FLOAT32);
}
pImageMsg->SetEndian(igtl::ImageMessage::ENDIAN_LITTLE);
igtl::Matrix4x4 m;
igtl::IdentityMatrix(m);
m[0][0] = -1;
m[1][1] = -1;
pImageMsg->SetMatrix(m);
pImageMsg->SetNumComponents(1);
pImageMsg->SetDeviceName(m_streamName.c_str());
pImageMsg->AllocateScalars();
igtl::TimeStamp::Pointer pTimestamp = igtl::TimeStamp::New();
double timestampSeconds;
double timestampFrac = modf(imageData->getSyncTimestamp(), ×tampSeconds);
pTimestamp->SetTime((uint32_t)timestampSeconds, (uint32_t)(timestampFrac*1e9));
pImageMsg->SetTimeStamp(pTimestamp);
auto imageContainer = imageData->getData<T>();
if (!imageContainer->isHost())
{
imageContainer = make_shared<Container<T> >(LocationHost, *imageContainer);
}
size_t numElements = imageSize.x * imageSize.y * imageSize.z;
memcpy(pImageMsg->GetScalarPointer(), imageContainer->get(), numElements * sizeof(T));
pImageMsg->Pack();
int sendResult = m_clientConnection->Send(pImageMsg->GetPackPointer(), pImageMsg->GetPackSize());
if (sendResult == 0) //when it could not be sent
{
m_isConnected = false;
log_info("IGTL: Lost connection. Waiting for next connection.");
waitAsyncForConnection();
}
}
}
void OpenIGTLinkOutputDevice::waitAsyncForConnection()
{
if (m_pConnectionThread && m_pConnectionThread->joinable())
{
m_pConnectionThread->join();
}
m_pConnectionThread = unique_ptr<thread>(
new thread([this]() {
log_info("IGTL: waiting for connection");
m_clientConnection = m_server->WaitForConnection();
m_isConnected = true;
log_info("IGTL: got connection!");
}));
}
代码很简单,如果配合着IGTL自带的示例代码会更容易弄明白。
2、做为客户端使用
void TrackerInterfaceIGTL::startAcquisition() {
m_callFrequency.setName("TrIGTL");
while (getRunning()) {
if (!m_connected) {
lock_guard<mutex> lock(m_objectMutex);
connectToSever();
}
//------------------------------------------------------------
// Wait for a reply
if (m_connected) {
igtl::MessageHeader::Pointer headerMsg;
headerMsg = igtl::MessageHeader::New();
headerMsg->InitPack();
int rs = m_socket->Receive(headerMsg->GetPackPointer(),
headerMsg->GetPackSize());
{
lock_guard<mutex> lock(m_objectMutex);
if (rs == 0) {
logging::log_warn("TrackerInterfaceIGTL: Connection closed.");
closeSocket();
continue;
}
if (rs != headerMsg->GetPackSize()) {
logging::log_warn("TrackerInterfaceIGTL: Message size information "
"and actual data size don't match.");
closeSocket();
continue;
}
if (!m_frozen) {
headerMsg->Unpack();
if (strcmp(headerMsg->GetDeviceType(), "TDATA") == 0) {
receiveTrackingData(headerMsg);
} else {
m_socket->Skip(headerMsg->GetBodySizeToRead(), 0);
}
}
}
} else {
logging::log_warn(
"TrackerInterfaceIGTL: Could not reconnect to the server '",
m_hostname, ":", m_port, "'. Retrying in ", m_reconnectInterval,
"s.");
duration<long, std::milli> sleepDuration =
milliseconds((long long)round(m_reconnectInterval * 1e3));
this_thread::sleep_for(sleepDuration);
}
}
{
lock_guard<mutex> lock(m_objectMutex);
closeSocket();
}
}
void TrackerInterfaceIGTL::connectToSever() {
if (!m_connected) {
int r = m_socket->ConnectToServer(m_hostname.c_str(), m_port);
if (r != 0) {
m_connected = false;
logging::log_warn(
"TrackerInterfaceIGTL: Could not reconnect to the server '",
m_hostname, ":", m_port, "'");
} else {
m_connected = true;
logging::log_info("TrackerInterfaceIGTL: Connected to the server '",
m_hostname, ":", m_port, "'");
}
}
}
void TrackerInterfaceIGTL::closeSocket() {
m_connected = false;
logging::log_warn("TrackerInterfaceIGTL: Closing socket to the server '",
m_hostname, ":", m_port, "'");
m_socket->CloseSocket();
}
bool TrackerInterfaceIGTL::receiveTrackingData(
igtl::MessageHeader::Pointer &header) {
//------------------------------------------------------------
// Allocate TrackingData Message Class
igtl::TrackingDataMessage::Pointer trackingData;
trackingData = igtl::TrackingDataMessage::New();
trackingData->SetMessageHeader(header);
trackingData->AllocatePack();
// Receive body from the socket
m_socket->Receive(trackingData->GetPackBodyPointer(),
trackingData->GetPackBodySize());
// Deserialize the transform data
// If you want to skip CRC check, call Unpack() without argument.
int c = trackingData->Unpack(1);
bool crcFine = (c & igtl::MessageHeader::UNPACK_BODY) > 0;
if (crcFine) // if CRC check is OK
{
std::vector<TrackerData> trackerData;
// compute float timestamp format from IGTL representation
uint32_t timestampSeconds;
uint32_t timestampFrac;
trackingData->GetTimeStamp(×tampSeconds, ×tampFrac);
double timestamp = (double)timestampSeconds + ((double)timestampFrac) / 1e9;
int nElements = trackingData->GetNumberOfTrackingDataElements();
for (int i = 0; i < nElements; i++) {
igtl::TrackingDataElement::Pointer trackingElement;
trackingData->GetTrackingDataElement(i, trackingElement);
igtl::Matrix4x4 igtlMatrix;
trackingElement->GetMatrix(igtlMatrix);
TrackerData::Matrix matrix;
matrix[0 + 0] = igtlMatrix[0][0];
matrix[0 + 1] = igtlMatrix[0][1];
matrix[0 + 2] = igtlMatrix[0][2];
matrix[0 + 3] = igtlMatrix[0][3];
matrix[4 + 0] = igtlMatrix[1][0];
matrix[4 + 1] = igtlMatrix[1][1];
matrix[4 + 2] = igtlMatrix[1][2];
matrix[4 + 3] = igtlMatrix[1][3];
matrix[8 + 0] = igtlMatrix[2][0];
matrix[8 + 1] = igtlMatrix[2][1];
matrix[8 + 2] = igtlMatrix[2][2];
matrix[8 + 3] = igtlMatrix[2][3];
matrix[12 + 0] = igtlMatrix[3][0];
matrix[12 + 1] = igtlMatrix[3][1];
matrix[12 + 2] = igtlMatrix[3][2];
matrix[12 + 3] = igtlMatrix[3][3];
trackerData.push_back(
TrackerData(matrix, 100, 666, trackingElement->GetName(), timestamp));
}
auto pTrackingDataSet =
make_shared<TrackerDataSet>(trackerData, timestamp, timestamp);
addData<0>(pTrackingDataSet);
m_callFrequency.measure();
} else {
logging::log_warn(
"TrackerInterfaceIGTL: IGTL message CRC error, skipping message");
}
return crcFine;
}
void TrackerInterfaceIGTL::initializeDevice() {
// try to connect already here, so we are directly good to go!
lock_guard<mutex> lock(m_objectMutex);
m_socket = igtl::ClientSocket::New();
connectToSever();
}
这段代码是接收TrackerData的,代码也很容易理解。其实主要是要和IGTL中的相关格式对应,此处就是TDATA,所以按照其协议的说明一看就明白了。
四、总结
国外的框架库,一个比较让人头疼的就是里面用了非常多的其它相关的库。这样的好处当然很明显,就是完成具体的工作的效率会大幅提高。但对于学习者来说就比较麻烦了,需要不断的学习这个库那个库,然后才能把整个框架运行起来并初步掌握。
但真正掌握后会发现,写这方面的代码会简单很多,至少比自己想象的要简单很多!如果做应用开发的话,这确实是一个好的方法!