Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
9.2.2023: Due to updates GitLab will be unavailable for some minutes between 9:00 and 11:00.
Open sidebar
CAMP
campvis-public
Commits
7080e882
Commit
7080e882
authored
May 14, 2014
by
Jakob Weiss
Browse files
added support for multiple device names
parent
49d18265
Changes
3
Hide whitespace changes
Inline
Side-by-side
modules/openigtlink/pipelines/streamingoigtldemo.cpp
View file @
7080e882
...
...
@@ -24,12 +24,8 @@
#include
"streamingoigtldemo.h"
#include
"tgt/event/keyevent.h"
#include
"core/datastructures/imagedata.h"
#include
"core/classification/geometry1dtransferfunction.h"
#include
"core/classification/tfgeometry1d.h"
namespace
campvis
{
StreamingOIGTLDemo
::
StreamingOIGTLDemo
(
DataContainer
*
dc
)
...
...
@@ -51,7 +47,7 @@ namespace campvis {
_renderTargetID
.
setValue
(
"combine"
);
_igtlClient
.
p_address
.
setValue
(
"127.0.0.1"
);
_igtlClient
.
p_targetImage
ID
.
setValue
(
"combine"
);
_igtlClient
.
p_targetImage
Prefix
.
setValue
(
"combine"
);
_canvasSize
.
s_changed
.
connect
<
StreamingOIGTLDemo
>
(
this
,
&
StreamingOIGTLDemo
::
onRenderTargetSizeChanged
);
}
...
...
modules/openigtlink/processors/openigtlinkclient.cpp
View file @
7080e882
...
...
@@ -24,8 +24,8 @@
#include
"openigtlinkclient.h"
#include
"transformdata.h"
#include
"positiondata.h"
#include
"
../datastructures/
transformdata.h"
#include
"
../datastructures/
positiondata.h"
#include
<igtlTransformMessage.h>
#include
<igtlPositionMessage.h>
...
...
@@ -44,17 +44,14 @@ namespace campvis {
,
p_deviceName
(
"ServerDeviceName"
,
"Device Name (empty to accept all)"
)
,
p_connect
(
"Connect"
,
"Connect to Server"
)
,
p_receiveImages
(
"ReceiveImages"
,
"Receive IMAGE Messages"
,
false
)
,
p_targetImage
ID
(
"targetImageName"
,
"Target Image
ID"
,
"OpenIGTLinkClient.output"
,
DataNameProperty
::
WRITE
)
,
p_targetImage
Prefix
(
"targetImageName"
,
"Target Image
Prefix"
,
"IGTL.image."
)
,
p_receiveTransforms
(
"ReceiveTransforms"
,
"Receive TRANSFORM Messages"
,
true
)
,
p_targetTransform
ID
(
"targetTransform
Name
"
,
"Target Transform
ID"
,
"OpenIGTLinkClient.transform"
,
DataNameProperty
::
WRITE
)
,
p_targetTransform
Prefix
(
"targetTransform
Prefix
"
,
"Target Transform
Prefix"
,
"IGTL.transform."
)
,
p_imageOffset
(
"ImageOffset"
,
"Image Offset in mm"
,
tgt
::
vec3
(
0.
f
),
tgt
::
vec3
(
-
10000.
f
),
tgt
::
vec3
(
10000.
f
),
tgt
::
vec3
(
0.1
f
))
,
p_voxelSize
(
"VoxelSize"
,
"Voxel Size in mm"
,
tgt
::
vec3
(
1.
f
),
tgt
::
vec3
(
-
100.
f
),
tgt
::
vec3
(
100.
f
),
tgt
::
vec3
(
0.1
f
))
,
p_receivePositions
(
"ReceivePositions"
,
"Receive POSITION Messages"
,
true
)
,
p_targetPosition
ID
(
"targetPositions
ID
"
,
"Target Position
ID"
,
"OpenIGTLinkClient.position"
,
DataNameProperty
::
WRITE
)
,
p_targetPosition
Prefix
(
"targetPositions
Prefix
"
,
"Target Position
Prefix"
,
"IGTL.position."
)
{
_lastReceivedTransform
=
0
;
_lastReceivedImageMessage
=
0
;
addProperty
(
p_address
,
VALID
);
addProperty
(
p_port
,
VALID
);
addProperty
(
p_deviceName
,
VALID
);
...
...
@@ -63,12 +60,12 @@ namespace campvis {
addProperty
(
p_receiveTransforms
,
INVALID_RESULT
|
INVALID_PROPERTIES
);
addProperty
(
p_receiveImages
,
INVALID_RESULT
|
INVALID_PROPERTIES
);
addProperty
(
p_targetTransform
ID
,
VALID
);
addProperty
(
p_targetImage
ID
,
VALID
);
addProperty
(
p_targetTransform
Prefix
,
VALID
);
addProperty
(
p_targetImage
Prefix
,
VALID
);
addProperty
(
p_imageOffset
,
VALID
);
addProperty
(
p_voxelSize
,
VALID
);
addProperty
(
p_receivePositions
,
INVALID_RESULT
|
INVALID_PROPERTIES
);
addProperty
(
p_targetPosition
ID
,
VALID
);
addProperty
(
p_targetPosition
Prefix
,
VALID
);
}
OpenIGTLinkClient
::~
OpenIGTLinkClient
()
{
...
...
@@ -76,7 +73,7 @@ namespace campvis {
}
void
OpenIGTLinkClient
::
init
()
{
p_connect
.
s_clicked
.
connect
(
this
,
&
OpenIGTLinkClient
::
onBtnConnectClicked
);
p_connect
.
s_clicked
.
connect
(
this
,
&
OpenIGTLinkClient
::
connectToServer
);
}
void
OpenIGTLinkClient
::
deinit
()
{
...
...
@@ -88,25 +85,24 @@ namespace campvis {
if
(
p_receiveTransforms
.
getValue
())
{
tgt
::
mat4
*
newTransform
=
_lastReceivedTransform
.
fetch_and_store
(
nullptr
);
if
(
newTransform
)
{
TransformData
*
td
=
new
TransformData
(
*
newTransform
);
_transformMutex
.
lock
(
);
for
(
auto
it
=
_receivedTransforms
.
begin
(),
end
=
_receivedTransforms
.
end
();
it
!=
end
;
++
it
)
{
TransformData
*
td
=
new
TransformData
(
it
->
second
);
data
.
addData
(
p_targetTransformID
.
getValue
(),
td
);
delete
newTransform
;
//was allocated in the receiver thread - not needed anymore
data
.
addData
(
p_targetTransformPrefix
.
getValue
()
+
it
->
first
,
td
);
LDEBUG
(
"Transform data put into container. "
);
}
_receivedTransforms
.
clear
();
_transformMutex
.
unlock
();
}
if
(
p_receiveImages
.
getValue
())
{
_lastReceivedImageMessageMutex
.
lock
();
igtl
::
ImageMessage
::
Pointer
imageMessage
=
_lastReceivedImageMessage
;
_lastReceivedImageMessageMutex
.
unlock
();
if
(
imageMessage
)
_imageMutex
.
lock
();
for
(
auto
it
=
_receivedImages
.
begin
(),
end
=
_receivedImages
.
end
();
it
!=
end
;
++
it
)
{
igtl
::
ImageMessage
::
Pointer
imageMessage
=
it
->
second
;
WeaklyTypedPointer
wtp
;
wtp
.
_pointer
=
new
uint8_t
[
imageMessage
->
GetImageSize
()];
LDEBUG
(
"Image has "
<<
imageMessage
->
GetNumComponents
()
<<
" components and is of size "
<<
imageMessage
->
GetImageSize
());
...
...
@@ -135,40 +131,47 @@ namespace campvis {
tgt
::
vec3
imageOffset
(
0.
f
);
tgt
::
vec3
voxelSize
(
1.
f
);
tgt
::
ivec3
size_i
(
1
);
imageMessage
->
GetSpacing
(
voxelSize
.
elem
);
imageMessage
->
GetDimensions
(
size_i
.
elem
);
tgt
::
svec3
size
(
size_i
);
tgt
::
svec3
size
(
size_i
);
imageMessage
->
GetOrigin
(
imageOffset
.
elem
);
size_t
dimensionality
=
(
size_i
[
2
]
==
1
)
?
((
size_i
[
1
]
==
1
)
?
1
:
2
)
:
3
;
ImageData
*
image
=
new
ImageData
(
dimensionality
,
size
,
wtp
.
_numChannels
);
ImageRepresentationLocal
::
create
(
image
,
wtp
);
image
->
setMappingInformation
(
ImageMappingInformation
(
size
,
p_imageOffset
.
getValue
(),
voxelSize
*
p_voxelSize
.
getValue
()));
data
.
addData
(
p_targetImage
ID
.
getValue
(),
image
);
data
.
addData
(
p_targetImage
Prefix
.
getValue
()
+
it
->
first
,
image
);
}
_receivedImages
.
clear
();
_imageMutex
.
unlock
();
}
if
(
p_receivePositions
.
getValue
())
{
_lastReceivedPositionMutex
.
lock
();
PositionData
*
pd
=
new
PositionData
(
_lastReceivedPosition
,
_lastReceivedQuaternion
);
_lastReceivedPositionMutex
.
unlock
();
data
.
addData
(
p_targetPositionID
.
getValue
(),
pd
);
_positionMutex
.
lock
();
for
(
auto
it
=
_receivedPositions
.
begin
(),
end
=
_receivedPositions
.
end
();
it
!=
end
;
++
it
)
{
PositionData
*
pd
=
new
PositionData
(
it
->
second
.
_position
,
it
->
second
.
_quaternion
);
data
.
addData
(
p_targetPositionPrefix
.
getValue
()
+
it
->
first
,
pd
);
}
_receivedPositions
.
clear
();
_positionMutex
.
unlock
();
}
validate
(
INVALID_RESULT
);
}
void
OpenIGTLinkClient
::
updateProperties
(
DataContainer
&
dataContainer
)
{
p_targetImage
ID
.
setVisible
(
p_receiveImages
.
getValue
());
p_targetImage
Prefix
.
setVisible
(
p_receiveImages
.
getValue
());
p_imageOffset
.
setVisible
(
p_receiveImages
.
getValue
());
p_voxelSize
.
setVisible
(
p_receiveImages
.
getValue
());
p_targetTransform
ID
.
setVisible
(
p_receiveImages
.
getValue
()
||
p_receiveTransforms
.
getValue
());
p_targetTransform
Prefix
.
setVisible
(
p_receiveImages
.
getValue
()
||
p_receiveTransforms
.
getValue
());
}
void
OpenIGTLinkClient
::
onBtnConnectClicked
()
{
void
OpenIGTLinkClient
::
connectToServer
()
{
if
(
_socket
&&
_socket
->
GetConnected
())
{
LWARNING
(
"Already connected!"
);
...
...
@@ -181,17 +184,24 @@ namespace campvis {
if
(
r
!=
0
)
{
LERROR
(
"Cannot connect to the server "
<<
p_address
.
getValue
());
LERROR
(
"Cannot connect to the server "
<<
p_address
.
getValue
()
<<
":"
<<
p_port
.
getValue
());
_socket
=
nullptr
;
return
;
}
LINFO
(
"Connected to server
!"
);
LINFO
(
"Connected to server
"
<<
p_address
.
getValue
()
<<
":"
<<
p_port
.
getValue
()
);
start
();
//start receiving data in a new thread
validate
(
INVALID_RESULT
);
}
void
OpenIGTLinkClient
::
disconnect
()
{
stop
();
_socket
=
nullptr
;
LINFO
(
"Disconnected."
);
}
int
OpenIGTLinkClient
::
ReceiveTransform
(
igtl
::
Socket
*
socket
,
igtl
::
MessageHeader
::
Pointer
&
header
)
{
LDEBUG
(
"Receiving TRANSFORM data type."
);
...
...
@@ -211,14 +221,15 @@ namespace campvis {
if
(
c
&
igtl
::
MessageHeader
::
UNPACK_BODY
)
// if CRC check is OK
{
tgt
::
mat4
*
mtx
=
new
tgt
::
mat4
;
// Retrieve the transform data (this cast is a bit dubious but should be ok judging from the class internals)
transMsg
->
GetMatrix
(
*
reinterpret_cast
<
igtl
::
Matrix4x4
*>
(
mtx
->
elem
));
igtl
::
PrintMatrix
(
*
reinterpret_cast
<
igtl
::
Matrix4x4
*>
(
mtx
->
elem
));
tgt
::
mat4
mtx
;
// Retrieve the transform data (this cast is a bit dubious but should be ok judging from the
respective
class internals)
transMsg
->
GetMatrix
(
*
reinterpret_cast
<
igtl
::
Matrix4x4
*>
(
mtx
.
elem
));
igtl
::
PrintMatrix
(
*
reinterpret_cast
<
igtl
::
Matrix4x4
*>
(
mtx
.
elem
));
std
::
cerr
<<
std
::
endl
;
tgt
::
mat4
*
toDelete
=
_lastReceivedTransform
.
fetch_and_store
(
mtx
);
if
(
toDelete
)
delete
toDelete
;
_transformMutex
.
lock
();
_receivedTransforms
[
transMsg
->
GetDeviceName
()]
=
mtx
;
_transformMutex
.
unlock
();
invalidate
(
INVALID_RESULT
);
return
1
;
...
...
@@ -246,14 +257,19 @@ namespace campvis {
if
(
c
&
igtl
::
MessageHeader
::
UNPACK_BODY
)
// if CRC check is OK
{
_lastReceivedPositionMutex
.
lock
();
positionMsg
->
GetPosition
(
_lastReceivedPosition
.
elem
);
positionMsg
->
GetQuaternion
(
_lastReceivedQuaternion
.
elem
);
_lastReceivedPositionMutex
.
unlock
();
PositionMessageData
pmd
;
positionMsg
->
GetPosition
(
pmd
.
_position
.
elem
);
positionMsg
->
GetQuaternion
(
pmd
.
_quaternion
.
elem
);
std
::
cerr
<<
"position = ("
<<
_lastReceivedPosition
[
0
]
<<
", "
<<
_lastReceivedPosition
[
1
]
<<
", "
<<
_lastReceivedPosition
[
2
]
<<
")"
<<
std
::
endl
;
std
::
cerr
<<
"quaternion = ("
<<
_lastReceivedQuaternion
[
0
]
<<
", "
<<
_lastReceivedQuaternion
[
1
]
<<
", "
<<
_lastReceivedQuaternion
[
2
]
<<
", "
<<
_lastReceivedQuaternion
[
3
]
<<
")"
<<
std
::
endl
<<
std
::
endl
;
_positionMutex
.
lock
();
_receivedPositions
[
positionMsg
->
GetDeviceName
()]
=
pmd
;
_positionMutex
.
unlock
();
std
::
cerr
<<
"position = ("
<<
pmd
.
_position
[
0
]
<<
", "
<<
pmd
.
_position
[
1
]
<<
", "
<<
pmd
.
_position
[
2
]
<<
")"
<<
std
::
endl
;
std
::
cerr
<<
"quaternion = ("
<<
pmd
.
_quaternion
[
0
]
<<
", "
<<
pmd
.
_quaternion
[
1
]
<<
", "
<<
pmd
.
_quaternion
[
2
]
<<
", "
<<
pmd
.
_quaternion
[
3
]
<<
")"
<<
std
::
endl
<<
std
::
endl
;
invalidate
(
INVALID_RESULT
);
return
1
;
...
...
@@ -282,9 +298,9 @@ namespace campvis {
if
(
c
&
igtl
::
MessageHeader
::
UNPACK_BODY
)
// if CRC check is OK
{
// put the message pointer into our locked buffer
_
lastReceivedImageMess
ageMutex
.
lock
();
_
lastR
eceivedImage
Message
=
imgMsg
;
_
lastReceivedImageMess
ageMutex
.
unlock
();
_
im
ageMutex
.
lock
();
_
r
eceivedImage
s
[
imgMsg
->
GetDeviceName
()]
=
imgMsg
;
_
im
ageMutex
.
unlock
();
// Retrieve the image data
int
size
[
3
];
// image dimension
...
...
@@ -298,13 +314,13 @@ namespace campvis {
imgMsg
->
GetSpacing
(
spacing
);
imgMsg
->
GetSubVolume
(
svsize
,
svoffset
);
tgt
::
mat4
*
mtx
=
new
tgt
::
mat4
;
tgt
::
mat4
mtx
;
// Retrieve the transform data (this cast is a bit dubious but should be ok judging from the class internals)
imgMsg
->
GetMatrix
(
*
reinterpret_cast
<
igtl
::
Matrix4x4
*>
(
mtx
->
elem
));
std
::
cerr
<<
std
::
endl
;
tgt
::
mat4
*
toDelete
=
_lastReceivedTransform
.
fetch_and_store
(
mtx
)
;
if
(
toDelete
)
delete
toDelete
;
imgMsg
->
GetMatrix
(
*
reinterpret_cast
<
igtl
::
Matrix4x4
*>
(
mtx
.
elem
));
_transformMutex
.
lock
();
_receivedTransforms
[
imgMsg
->
GetDeviceName
()]
=
mtx
;
_transformMutex
.
unlock
()
;
std
::
cerr
<<
"Device Name : "
<<
imgMsg
->
GetDeviceName
()
<<
std
::
endl
;
std
::
cerr
<<
"Scalar Type : "
<<
scalarType
<<
std
::
endl
;
...
...
@@ -317,7 +333,8 @@ namespace campvis {
std
::
cerr
<<
"Sub-Volume offset : ("
<<
svoffset
[
0
]
<<
", "
<<
svoffset
[
1
]
<<
", "
<<
svoffset
[
2
]
<<
")"
<<
std
::
endl
<<
std
::
endl
;
igtl
::
PrintMatrix
(
*
reinterpret_cast
<
igtl
::
Matrix4x4
*>
(
mtx
->
elem
));
igtl
::
PrintMatrix
(
*
reinterpret_cast
<
igtl
::
Matrix4x4
*>
(
mtx
.
elem
));
std
::
cout
<<
std
::
endl
;
invalidate
(
INVALID_RESULT
);
return
1
;
...
...
@@ -335,7 +352,7 @@ namespace campvis {
headerMsg
=
igtl
::
MessageHeader
::
New
();
ts
=
igtl
::
TimeStamp
::
New
();
while
(
!
_stopExecution
)
while
(
!
_stopExecution
&&
_socket
)
{
// Initialize receive buffer
headerMsg
->
InitPack
();
...
...
@@ -345,6 +362,7 @@ namespace campvis {
if
(
r
==
0
)
{
_socket
->
CloseSocket
();
_socket
=
nullptr
;
LINFO
(
"Socket Connection closed."
);
break
;
}
...
...
@@ -432,6 +450,8 @@ namespace campvis {
_socket
->
Skip
(
headerMsg
->
GetBodySizeToRead
(),
0
);
}
}
_socket
->
CloseSocket
();
if
(
_socket
)
_socket
->
CloseSocket
();
_socket
=
nullptr
;
}
}
\ No newline at end of file
modules/openigtlink/processors/openigtlinkclient.h
View file @
7080e882
...
...
@@ -26,6 +26,7 @@
#define OPENIGTLINKCLIENT_H__
#include
<string>
#include
<map>
#include
<igtlOSUtil.h>
#include
<igtlClientSocket.h>
...
...
@@ -48,8 +49,10 @@
namespace
campvis
{
/**
* Experimental demo implementation how to receive MHD files via CAMPCom, convert it to
* CAMPVis ImageData and store it into the DataContainer.
* OpenIGTLink Client processor. Connects to a specified server and receives all OpenIGTLink messages.
* Processes the messages according to the currently set properties p_receiveTransform, p_receivePositions
* and p_receiveImage and puts them into the received data into the respective data containers.
* This Class contains modified code from the OpenIGTLink ReceiveClient example.
*/
class
OpenIGTLinkClient
:
public
AbstractProcessor
,
public
Runnable
{
public:
...
...
@@ -84,19 +87,19 @@ namespace campvis {
BoolProperty
p_receiveImages
;
///< toggle receiving IMAGE messages
DataName
Property
p_targetImage
ID
;
///< image ID for read image
String
Property
p_targetImage
Prefix
;
///< image ID
prefix
for read image
BoolProperty
p_receiveTransforms
;
///< toggle receiving TRANSFORM messages
DataName
Property
p_targetTransform
ID
;
///< data ID for read transformation
String
Property
p_targetTransform
Prefix
;
///< data ID
prefix
for read transformation
Vec3Property
p_imageOffset
;
///< Image Offset in mm
Vec3Property
p_voxelSize
;
///< Voxel Size in mm
BoolProperty
p_receivePositions
;
///< toggle receiving IMAGE messages
DataName
Property
p_targetPosition
ID
;
///< image ID for read image
String
Property
p_targetPosition
Prefix
;
///< image ID
prefix
for read image
s
/**
* Updates the data container with the latest received
frame/
transformation
* Updates the data container with the latest received transformation
/position/image data
* \param dataContainer DataContainer to work on
*/
virtual
void
updateResult
(
DataContainer
&
dataContainer
);
...
...
@@ -104,30 +107,44 @@ namespace campvis {
/// \see AbstractProcessor::updateProperties
virtual
void
updateProperties
(
DataContainer
&
dataContainer
);
/// Callback slot for connect button
void
onBtnConnectClicked
();
/// Callback slot for connect button. can also be called from outside.
void
connectToServer
();
/// Callback slot for disconnect button. can also be called from outside.
void
disconnect
();
static
const
std
::
string
loggerCat_
;
protected:
int
OpenIGTLinkClient
::
ReceiveTransform
(
igtl
::
Socket
*
socket
,
igtl
::
MessageHeader
::
Pointer
&
header
);
int
OpenIGTLinkClient
::
ReceivePosition
(
igtl
::
Socket
*
socket
,
igtl
::
MessageHeader
::
Pointer
&
header
);
int
ReceiveImage
(
igtl
::
Socket
*
socket
,
igtl
::
MessageHeader
::
Pointer
&
header
);
/// Stores received data from a POSITION Message
struct
PositionMessageData
{
tgt
::
vec3
_position
;
tgt
::
vec4
_quaternion
;
};
/// Implements the \a Runnable::run() method to execute a new thread. The new thread will
/// go into a receive loop to receive the OpenIGTLink messages asynchronously
virtual
void
run
();
/// Receive a TRANSFORM message from the OpenIGTLink socket and put the data into the local buffers
int
ReceiveTransform
(
igtl
::
Socket
*
socket
,
igtl
::
MessageHeader
::
Pointer
&
header
);
/// Receive a POSITION message from the OpenIGTLink socket and put the data into the local buffers
int
ReceivePosition
(
igtl
::
Socket
*
socket
,
igtl
::
MessageHeader
::
Pointer
&
header
);
/// Receive a IMAGE message from the OpenIGTLink socket and put into the local buffers
int
ReceiveImage
(
igtl
::
Socket
*
socket
,
igtl
::
MessageHeader
::
Pointer
&
header
);
//connection
igtl
::
ClientSocket
::
Pointer
_socket
;
//data
tbb
::
atomic
<
tgt
::
mat4
*
>
_
lastR
eceivedTransform
;
///< the
last
transform that has been received by the igtl worker thread
igtl
::
ImageMessage
::
Pointer
_
lastR
eceivedImage
Message
;
///<
last received igtl image messag
e
tbb
::
mutex
_lastReceivedImageMessageMutex
;
///< mutex to control access to the _lastReceivedImageMessage pointer
std
::
map
<
std
::
string
,
tgt
::
mat4
>
_
r
eceivedTransform
s
;
///< the transform
s
that has been received by the igtl worker thread
, mapped by device name
std
::
map
<
std
::
string
,
igtl
::
ImageMessage
::
Pointer
>
_
r
eceivedImage
s
;
///<
the image messages received by the igtl worker thread, mapped by device nam
e
std
::
map
<
std
::
string
,
PositionMessageData
>
_receivedPositions
;
///< position message data received by the igtl worker thread, mapped by device name
tbb
::
mutex
_
lastReceivedPositionMutex
;
///< mutex to control access to
_lastReceivedPosition and _lastReceivedQuaternion
t
gt
::
vec3
_lastReceivedPosition
;
///< last received position in the position message
t
gt
::
vec4
_lastReceivedQuaternion
;
///< last received quaternion/orientation in the position message
tbb
::
mutex
_
transformMutex
;
///< mutex to control access to
the _receivedTransforms pointer
t
bb
::
mutex
_imageMutex
;
///< mutex to control access to the _receivedImages pointer
t
bb
::
mutex
_positionMutex
;
///< mutex to control access to _receivedPositions
static
const
std
::
string
loggerCat_
;
};
}
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment