Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
F
fslpy
Manage
Activity
Members
Code
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Model registry
Operate
Environments
Analyze
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
FSL
fslpy
Commits
eb1b217b
Commit
eb1b217b
authored
9 years ago
by
Paul McCarthy
Browse files
Options
Downloads
Patches
Plain Diff
The melodic classification panel 'load labels' button can be used to
load a melodic overlay.
parent
e8f1d0ad
No related branches found
Branches containing commit
No related tags found
Tags containing commit
No related merge requests found
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
fsl/data/melodicresults.py
+33
-0
33 additions, 0 deletions
fsl/data/melodicresults.py
fsl/data/strings.py
+31
-8
31 additions, 8 deletions
fsl/data/strings.py
fsl/fsleyes/controls/melodicclassificationpanel.py
+238
-52
238 additions, 52 deletions
fsl/fsleyes/controls/melodicclassificationpanel.py
with
302 additions
and
60 deletions
fsl/data/melodicresults.py
+
33
−
0
View file @
eb1b217b
...
@@ -113,6 +113,11 @@ def getDataFile(meldir):
...
@@ -113,6 +113,11 @@ def getDataFile(meldir):
return
None
return
None
def
getMeanFile
(
meldir
):
"""
Return a path to the mean image of the meloidic input data.
"""
return
fslimage
.
addExt
(
op
.
join
(
meldir
,
'
mean
'
))
def
getICFile
(
meldir
):
def
getICFile
(
meldir
):
"""
Returns the path to the melodic IC image.
"""
"""
Returns the path to the melodic IC image.
"""
return
fslimage
.
addExt
(
op
.
join
(
meldir
,
'
melodic_IC
'
))
return
fslimage
.
addExt
(
op
.
join
(
meldir
,
'
melodic_IC
'
))
...
@@ -406,6 +411,7 @@ def loadMelodicLabelFile(filename):
...
@@ -406,6 +411,7 @@ def loadMelodicLabelFile(filename):
to be of the format generated by FIX or Melview; such a file should
to be of the format generated by FIX or Melview; such a file should
have a structure resembling the following::
have a structure resembling the following::
filtered_func_data.ica
filtered_func_data.ica
1, Signal, False
1, Signal, False
2, Unclassified Noise, True
2, Unclassified Noise, True
...
@@ -417,6 +423,7 @@ def loadMelodicLabelFile(filename):
...
@@ -417,6 +423,7 @@ def loadMelodicLabelFile(filename):
8, Signal, False
8, Signal, False
[2, 5, 6, 7]
[2, 5, 6, 7]
The first line of the file contains the name of the melodic directory.
The first line of the file contains the name of the melodic directory.
Then, one line is present for each component, containing the following,
Then, one line is present for each component, containing the following,
separated by commas:
separated by commas:
...
@@ -427,10 +434,29 @@ def loadMelodicLabelFile(filename):
...
@@ -427,10 +434,29 @@ def loadMelodicLabelFile(filename):
- ``
'
True
'
`` if the component has been classified as *bad*,
- ``
'
True
'
`` if the component has been classified as *bad*,
``
'
False
'
`` otherwise.
``
'
False
'
`` otherwise.
The last line of the file contains the index (starting from 1) of all
The last line of the file contains the index (starting from 1) of all
*bad* components, i.e. those components which are not classified as
*bad* components, i.e. those components which are not classified as
signal or unknown.
signal or unknown.
:arg filename: Name of the label file to load.
:returns: A tuple containing the path to the melodic directory
as specified in the label file, and a list of lists, one
list per component, with each list containing the labels for
the corresponding component.
.. note:: This function will also parse files which only contain a
bad component list, e.g.::
[2, 5, 6, 7]
In this case, the returned melodic directory path will be
``None``.
"""
"""
filename
=
op
.
abspath
(
filename
)
with
open
(
filename
,
'
rt
'
)
as
f
:
with
open
(
filename
,
'
rt
'
)
as
f
:
lines
=
f
.
readlines
()
lines
=
f
.
readlines
()
...
@@ -462,6 +488,13 @@ def loadMelodicLabelFile(filename):
...
@@ -462,6 +488,13 @@ def loadMelodicLabelFile(filename):
melDir
=
lines
[
0
]
melDir
=
lines
[
0
]
noisyComps
=
map
(
int
,
lines
[
-
1
][
1
:
-
1
].
split
(
'
,
'
))
noisyComps
=
map
(
int
,
lines
[
-
1
][
1
:
-
1
].
split
(
'
,
'
))
# The melodic directory path should
# either be an absolute path, or
# be specified relative to the location
# of the label file.
if
not
op
.
isabs
(
melDir
):
melDir
=
op
.
join
(
op
.
dirname
(
filename
),
melDir
)
# Parse the labels for every component
# Parse the labels for every component
# We dot not add the labels as we go
# We dot not add the labels as we go
# as, if something is wrong with the
# as, if something is wrong with the
...
...
This diff is collapsed.
Click to expand it.
fsl/data/strings.py
+
31
−
8
View file @
eb1b217b
...
@@ -141,12 +141,35 @@ messages = TypeDict({
...
@@ -141,12 +141,35 @@ messages = TypeDict({
'
is necessary for editing.
'
,
'
is necessary for editing.
'
,
'
MelodicClassificationPanel.disabled
'
:
'
Choose a melodic image.
'
,
'
MelodicClassificationPanel.disabled
'
:
'
Choose a melodic image.
'
,
'
MelodicClassificationPanel.loadError
'
:
'
An error occurred while
'
'
loading the file {}.
'
'
\n\n
Details: {}
'
,
'
MelodicClassificationPanel.noMelDir
'
:
'
The label file {} does not
'
'
specify a path to a Melodic
'
'
directory!
'
,
'
MelodicClassificationPanel.saveError
'
:
'
An error occurred while
'
'
saving the file {}.
'
'
\n\n
Details: {}
'
,
'
MelodicClassificationPanel.wrongNComps
'
:
'
The mumber of components in
'
'
the label file {} is greater
'
'
than the number of components
'
'
in the overlay {}!
'
,
'
MelodicClassificationPanel.diffMelDir
'
:
'
The label file {} does not
'
'
refer to the melodic
'
'
directory of the selected
'
'
overlay ({}). What do you
'
'
want to do?
'
,
'
MelodicClassificationPanel.diffMelDir.labels
'
:
'
Load the overlay in
'
'
the label file
'
,
'
MelodicClassificationPanel.diffMelDir.overlay
'
:
'
Apply the labels to
'
'
the current overlay
'
'
MelodicClassificationPanel.loadError
'
:
'
An error occurred while loading
'
'
An error occurred while
'
'
the file {}.
\n\n
Details: {}
'
,
'
saving the file {}.
'
'
MelodicClassificationPanel.saveError
'
:
'
An error occurred while saving
'
'
\n\n
Details: {}
'
,
'
the file {}.
\n\n
Details: {}
'
,
})
})
...
@@ -387,9 +410,9 @@ labels = TypeDict({
...
@@ -387,9 +410,9 @@ labels = TypeDict({
'
MelodicClassificationPanel.componentTab
'
:
'
Components
'
,
'
MelodicClassificationPanel.componentTab
'
:
'
Components
'
,
'
MelodicClassificationPanel.labelTab
'
:
'
Labels
'
,
'
MelodicClassificationPanel.labelTab
'
:
'
Labels
'
,
'
MelodicClassificationPanel.loadButton
'
:
'
Load
from file
'
,
'
MelodicClassificationPanel.loadButton
'
:
'
Load
labels
'
,
'
MelodicClassificationPanel.saveButton
'
:
'
Save
to file
'
,
'
MelodicClassificationPanel.saveButton
'
:
'
Save
labels
'
,
'
MelodicClassificationPanel.clearButton
'
:
'
Clear
all
labels
'
,
'
MelodicClassificationPanel.clearButton
'
:
'
Clear labels
'
,
'
ComponentGrid.componentColumn
'
:
'
IC #
'
,
'
ComponentGrid.componentColumn
'
:
'
IC #
'
,
'
ComponentGrid.labelColumn
'
:
'
Labels
'
,
'
ComponentGrid.labelColumn
'
:
'
Labels
'
,
...
...
This diff is collapsed.
Click to expand it.
fsl/fsleyes/controls/melodicclassificationpanel.py
+
238
−
52
View file @
eb1b217b
...
@@ -10,16 +10,22 @@ of a :class:`.MelodicImage`.
...
@@ -10,16 +10,22 @@ of a :class:`.MelodicImage`.
"""
"""
import
os
import
os.path
as
op
import
logging
import
logging
import
wx
import
wx
import
pwidgets.notebook
as
notebook
import
pwidgets.notebook
as
notebook
import
fsl.utils.settings
as
fslsettings
import
fsl.data.strings
as
strings
import
fsl.data.strings
as
strings
import
fsl.data.image
as
fslimage
import
fsl.data.melodicresults
as
fslmelresults
import
fsl.data.melodicimage
as
fslmelimage
import
fsl.data.melodicimage
as
fslmelimage
import
fsl.fsleyes.colourmaps
as
fslcm
import
fsl.fsleyes.colourmaps
as
fslcm
import
fsl.fsleyes.panel
as
fslpanel
import
fsl.fsleyes.panel
as
fslpanel
import
fsl.fsleyes.autodisplay
as
autodisplay
import
melodicclassificationgrid
as
melodicgrid
import
melodicclassificationgrid
as
melodicgrid
...
@@ -30,25 +36,7 @@ class MelodicClassificationPanel(fslpanel.FSLEyesPanel):
...
@@ -30,25 +36,7 @@ class MelodicClassificationPanel(fslpanel.FSLEyesPanel):
"""
The ``MelodicClassificationPanel``
"""
The ``MelodicClassificationPanel``
"""
"""
#
# File format:
# First line: ica directory name
# Lines 2-(N+1): One line for each component
# Last line: List of bad components
#
# A component line:
# Component index, Label1, Label2, True|False
#
#
#
# Save to a FSLeyes label file:
#
# Save to a FIX/MELview file:
# - Component has 'Signal' label
# - Component has 'Unknown' label
# - All other labels are output as 'Unclassified Noise'
# (these are added to the list on the last line of the file)
#
def
__init__
(
self
,
parent
,
overlayList
,
displayCtx
):
def
__init__
(
self
,
parent
,
overlayList
,
displayCtx
):
"""
Create a ``MelodicClassificationPanel``.
"""
Create a ``MelodicClassificationPanel``.
...
@@ -109,10 +97,10 @@ class MelodicClassificationPanel(fslpanel.FSLEyesPanel):
...
@@ -109,10 +97,10 @@ class MelodicClassificationPanel(fslpanel.FSLEyesPanel):
self
.
__btnSizer
.
Add
(
self
.
__clearButton
,
flag
=
wx
.
EXPAND
,
proportion
=
1
)
self
.
__btnSizer
.
Add
(
self
.
__clearButton
,
flag
=
wx
.
EXPAND
,
proportion
=
1
)
self
.
__mainSizer
.
Add
(
self
.
__notebook
,
flag
=
wx
.
EXPAND
,
proportion
=
1
)
self
.
__mainSizer
.
Add
(
self
.
__notebook
,
flag
=
wx
.
EXPAND
,
proportion
=
1
)
self
.
__mainSizer
.
Add
(
self
.
__btnSizer
,
flag
=
wx
.
EXPAND
)
self
.
__sizer
.
Add
(
self
.
__disabledText
,
flag
=
wx
.
EXPAND
,
proportion
=
1
)
self
.
__sizer
.
Add
(
self
.
__disabledText
,
flag
=
wx
.
EXPAND
,
proportion
=
1
)
self
.
__sizer
.
Add
(
self
.
__mainSizer
,
flag
=
wx
.
EXPAND
,
proportion
=
1
)
self
.
__sizer
.
Add
(
self
.
__mainSizer
,
flag
=
wx
.
EXPAND
,
proportion
=
1
)
self
.
__sizer
.
Add
(
self
.
__btnSizer
,
flag
=
wx
.
EXPAND
)
self
.
SetSizer
(
self
.
__sizer
)
self
.
SetSizer
(
self
.
__sizer
)
...
@@ -152,6 +140,9 @@ class MelodicClassificationPanel(fslpanel.FSLEyesPanel):
...
@@ -152,6 +140,9 @@ class MelodicClassificationPanel(fslpanel.FSLEyesPanel):
self
.
__sizer
.
Show
(
self
.
__disabledText
,
not
enable
)
self
.
__sizer
.
Show
(
self
.
__disabledText
,
not
enable
)
self
.
__sizer
.
Show
(
self
.
__mainSizer
,
enable
)
self
.
__sizer
.
Show
(
self
.
__mainSizer
,
enable
)
self
.
__saveButton
.
Enable
(
enable
)
self
.
__clearButton
.
Enable
(
enable
)
self
.
Layout
()
self
.
Layout
()
...
@@ -173,35 +164,131 @@ class MelodicClassificationPanel(fslpanel.FSLEyesPanel):
...
@@ -173,35 +164,131 @@ class MelodicClassificationPanel(fslpanel.FSLEyesPanel):
def
__onLoadButton
(
self
,
ev
):
def
__onLoadButton
(
self
,
ev
):
"""
"""
Called when the *Load labels* button is pushed. Prompts the user
to select a label file to load, then does the following:
1. If the selected label file refers to the currently selected
melodic_IC overlay, the labels are applied to the overlay.
2. If the selected label file refers to a different melodic_IC
overlay, the user is asked whether they want to load the
different melodic_IC file (the default), or whether they
want the labels applied to the existing overlay.
3. If the selected label file does not refer to any overlay
(it only contains the bad component list), the user is asked
whether they want the labels applied to the current melodic_IC
overlay.
If the number of labels in the file is less than the number of
melodic_IC components, thew remaining components are labelled
as unknown. If the number of labels in the file is greater than
the number of melodic_IC components, an error is shown, and
nothing is done.
"""
"""
lut
=
self
.
__lut
# The aim of the code beneath this function
overlay
=
self
.
_displayCtx
.
getSelectedOverlay
()
# is to load a set of component labels, and
melclass
=
overlay
.
getICClassification
()
# to figure out which overlay they should
dlg
=
wx
.
FileDialog
(
# be added to. When it has done this, it
# calls this function.
def
applyLabels
(
labelFile
,
overlay
,
allLabels
):
lut
=
self
.
__lut
melclass
=
overlay
.
getICClassification
()
ncomps
=
overlay
.
numComponents
()
nlabels
=
len
(
allLabels
)
# Error: number of labels in the
# file is greater than the number
# of components in the overlay.
if
ncomps
<
nlabels
:
msg
=
strings
.
messages
[
self
,
'
wrongNComps
'
].
format
(
labelFile
,
overlay
.
dataSource
)
title
=
strings
.
titles
[
self
,
'
loadError
'
]
wx
.
MessageBox
(
msg
,
title
,
wx
.
ICON_ERROR
|
wx
.
OK
)
return
# Number of labels in the file is
# less than number of components
# in the overlay - we pad the
# labels with 'Unknown'
elif
ncomps
>
nlabels
:
for
i
in
range
(
nlabels
,
ncomps
):
allLabels
.
append
([
'
Unknown
'
])
# Disable notification while applying
# labels so the component/label grids
# don't confuse themselves. We'll
# manually refresh them below.
melclass
.
disableNotification
(
'
labels
'
)
lut
.
disableNotification
(
'
labels
'
)
melclass
.
clear
()
for
comp
,
lbls
in
enumerate
(
allLabels
):
for
lbl
in
lbls
:
melclass
.
addLabel
(
comp
,
lbl
)
# Make sure a colour in the melodic
# lookup table exists for all labels
for
comp
,
labels
in
enumerate
(
melclass
.
labels
):
for
label
in
labels
:
label
=
melclass
.
getDisplayLabel
(
label
)
lutLabel
=
lut
.
getByName
(
label
)
if
lutLabel
is
None
:
log
.
debug
(
'
New melodic classification
'
'
label: {}
'
.
format
(
label
))
lut
.
new
(
label
)
melclass
.
enableNotification
(
'
labels
'
)
lut
.
enableNotification
(
'
labels
'
)
# If we have just loaded a MelodicImage,
# make sure it is selected. If we loaded
# labels for an existing MelodicImage,
# this will have no effect.
self
.
_displayCtx
.
disableListener
(
'
selectedOverlay
'
,
self
.
_name
)
self
.
_displayCtx
.
selectOverlay
(
overlay
)
self
.
_displayCtx
.
enableListener
(
'
selectedOverlay
'
,
self
.
_name
)
self
.
__selectedOverlayChanged
()
# If the current overlay is a MelodicImage,
# the open file dialog starting point will
# be the melodic directory.
overlay
=
self
.
_displayCtx
.
getSelectedOverlay
()
selectedIsMelodic
=
isinstance
(
overlay
,
fslmelimage
.
MelodicImage
)
if
selectedIsMelodic
:
loadDir
=
overlay
.
getMelodicDir
()
# Otherwise it will be the most
# recent overlay load directory.
else
:
loadDir
=
fslsettings
.
read
(
'
loadOverlayLastDir
'
,
os
.
getcwd
())
# Ask the user to select a label file
dlg
=
wx
.
FileDialog
(
self
,
self
,
message
=
strings
.
titles
[
self
,
'
loadDialog
'
],
message
=
strings
.
titles
[
self
,
'
loadDialog
'
],
defaultDir
=
overlay
.
getMelodic
Dir
()
,
defaultDir
=
load
Dir
,
style
=
wx
.
FD_OPEN
)
style
=
wx
.
FD_OPEN
)
# User cancelled the dialog
if
dlg
.
ShowModal
()
!=
wx
.
ID_OK
:
if
dlg
.
ShowModal
()
!=
wx
.
ID_OK
:
return
return
# Load the specified label file
filename
=
dlg
.
GetPath
()
filename
=
dlg
.
GetPath
()
# Disable notification during the load,
# so the component/label grids don't
# confuse themselves. We'll manually
# refresh them below.
melclass
.
disableNotification
(
'
labels
'
)
lut
.
disableNotification
(
'
labels
'
)
try
:
try
:
mel
class
.
clear
(
)
mel
Dir
,
allLabels
=
fslmelresults
.
loadMelodicLabelFile
(
filename
)
melclass
.
load
(
filename
)
# Problem loading the file
except
Exception
as
e
:
except
Exception
as
e
:
e
=
str
(
e
)
e
=
str
(
e
)
msg
=
strings
.
messages
[
self
,
'
loadError
'
].
format
(
filename
,
e
)
msg
=
strings
.
messages
[
self
,
'
loadError
'
].
format
(
filename
,
e
)
title
=
strings
.
titles
[
self
,
'
loadError
'
]
title
=
strings
.
titles
[
self
,
'
loadError
'
]
...
@@ -209,24 +296,123 @@ class MelodicClassificationPanel(fslpanel.FSLEyesPanel):
...
@@ -209,24 +296,123 @@ class MelodicClassificationPanel(fslpanel.FSLEyesPanel):
'
({}), ({})
'
.
format
(
filename
,
e
),
exc_info
=
True
)
'
({}), ({})
'
.
format
(
filename
,
e
),
exc_info
=
True
)
wx
.
MessageBox
(
msg
,
title
,
wx
.
ICON_ERROR
|
wx
.
OK
)
wx
.
MessageBox
(
msg
,
title
,
wx
.
ICON_ERROR
|
wx
.
OK
)
# Make sure a colour in the melodic
return
# lookup table exists for all labels
for
comp
,
labels
in
enumerate
(
melclass
.
labels
):
# Ok we've got the labels, now
for
label
in
labels
:
# we need to figure out which
# overlay to add them to.
label
=
melclass
.
getDisplayLabel
(
label
)
# If the label file does not refer
lutLabel
=
lut
.
getByName
(
label
)
# to a Melodic directory, and the
# current overlay is a melodic
if
lutLabel
is
None
:
# image, apply the labels to the image.
log
.
debug
(
'
New melodic classification
'
if
selectedIsMelodic
and
(
melDir
is
None
):
'
label: {}
'
.
format
(
l
abel
)
)
applyLabels
(
filename
,
overlay
,
allL
abel
s
)
lut
.
new
(
label
)
return
melclass
.
enableNotification
(
'
labels
'
)
# If the label file refers to a
lut
.
enableNotification
(
'
labels
'
)
# Melodic directory, and the
# current overlay is a melodic
# image.
if
selectedIsMelodic
and
(
melDir
is
not
None
):
lut
.
notify
(
'
labels
'
)
overlayDir
=
overlay
.
getMelodicDir
()
melclass
.
notify
(
'
labels
'
)
# And both the current overlay and
# the label file refer to the same
# melodic directory, then we apply
# the labels to the curent overlay.
if
op
.
abspath
(
melDir
)
==
op
.
abspath
(
overlayDir
):
applyLabels
(
filename
,
overlay
,
allLabels
)
return
# Otherwise, if the overlay and the
# label file refer to different
# melodic directories...
# Ask the user whether they want to
dlg
=
wx
.
MessageDialog
(
self
,
strings
.
messages
[
self
,
'
diffMelDir
'
].
format
(
melDir
,
overlayDir
),
style
=
wx
.
ICON_QUESTION
|
wx
.
YES_NO
|
wx
.
CANCEL
)
dlg
.
SetYesNoLabels
(
strings
.
messages
[
self
,
'
diffMelDir.labels
'
],
strings
.
messages
[
self
,
'
diffMelDir.overlay
'
])
response
=
dlg
.
ShowModal
()
# User cancelled the dialog
if
response
==
wx
.
ID_CANCEL
:
return
# User chose to load the melodic
# image specified in the label
# file. We'll carry on with this
# processing below.
elif
response
==
wx
.
ID_YES
:
pass
# Apply the labels to the current
# overlay, even though they are
# from different analyses.
else
:
applyLabels
(
filename
,
overlay
,
allLabels
)
return
# If we've reached this far, we are
# going to attempt to identify the
# melodic image associated with the
# label file, load that image, and
# then apply the labels.
# The label file does not
# specify a melodic directory
if
melDir
is
None
:
msg
=
strings
.
messages
[
self
,
'
noMelDir
'
].
format
(
filename
)
title
=
strings
.
titles
[
self
,
'
loadError
'
]
wx
.
MessageBox
(
msg
,
title
,
wx
.
ICON_ERROR
|
wx
.
OK
)
return
# Try loading the melodic_IC image
# specified in the label file. We'll
# load the mean image as well, as an
# underlay.
try
:
overlay
=
fslmelimage
.
MelodicImage
(
melDir
)
mean
=
fslmelresults
.
getMeanFile
(
melDir
)
mean
=
fslimage
.
Image
(
mean
)
log
.
debug
(
'
Adding {} and {} to overlay list
'
.
format
(
overlay
,
mean
))
self
.
_overlayList
.
disableListener
(
'
overlays
'
,
self
.
_name
)
self
.
_displayCtx
.
disableListener
(
'
selectedOverlay
'
,
self
.
_name
)
self
.
_overlayList
.
extend
([
mean
,
overlay
])
self
.
_overlayList
.
enableListener
(
'
overlays
'
,
self
.
_name
)
self
.
_displayCtx
.
enableListener
(
'
selectedOverlay
'
,
self
.
_name
)
if
self
.
_displayCtx
.
autoDisplay
:
for
o
in
[
overlay
,
mean
]:
autodisplay
.
autoDisplay
(
o
,
self
.
_overlayList
,
self
.
_displayCtx
)
fslsettings
.
write
(
'
loadOverlayLastDir
'
,
op
.
abspath
(
melDir
))
except
Exception
as
e
:
e
=
str
(
e
)
msg
=
strings
.
messages
[
self
,
'
loadError
'
].
format
(
filename
,
e
)
title
=
strings
.
titles
[
self
,
'
loadError
'
]
log
.
debug
(
'
Error loading classification file
'
'
({}), ({})
'
.
format
(
filename
,
e
),
exc_info
=
True
)
wx
.
MessageBox
(
msg
,
title
,
wx
.
ICON_ERROR
|
wx
.
OK
)
# Apply the loaded labels
# to the loaded overlay.
applyLabels
(
filename
,
overlay
,
allLabels
)
def
__onSaveButton
(
self
,
ev
):
def
__onSaveButton
(
self
,
ev
):
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
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!
Save comment
Cancel
Please
register
or
sign in
to comment