Skip to content
Snippets Groups Projects
Commit e8a0647f authored by Paul McCarthy's avatar Paul McCarthy
Browse files

Adjusted melodic perspective, added support for 'manual' label

files (containing only a noisy component list), adjusted melodic lookup
table.
parent 80d7cf45
No related branches found
No related tags found
No related merge requests found
......@@ -229,35 +229,8 @@ class MelodicClassification(props.HasProperties):
def load(self, filename):
"""Loads component labels from the specified file. The file is assuemd
to be of the format generated by FIX or Melview; such a file should
have a structure resembling the following::
filtered_func_data.ica
1, Signal, False
2, Unclassified Noise, True
3, Unknown, False
4, Signal, False
5, Unclassified Noise, True
6, Unclassified Noise, True
7, Unclassified Noise, True
8, Signal, False
[2, 5, 6, 7]
The first line of the file contains the name of the melodic directory.
Then, one line is present for each component, containing the following,
separated by commas:
- The component index (starting from 1).
- One or more labels for the component (multiple labels must be
comma-separated).
- ``'True'`` if the component has been classified as *bad*,
``'False'`` otherwise.
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
signal or unknown.
"""Loads component labels from the specified file. See the
:func:`loadMelodicLabelFile` function.
.. note:: This method adds to, but does not replace, any existing
component classifications stored by this
......@@ -266,57 +239,27 @@ class MelodicClassification(props.HasProperties):
classifications.
"""
with open(filename, 'rt') as f:
lines = f.readlines()
if len(lines) < 3:
raise InvalidFixFileError('Invalid FIX classification '
'file - not enough lines')
lines = [l.strip() for l in lines]
# Ignore the first and last
# lines - we're only interested
# in the component labels
compLines = lines[1:-1]
if len(compLines) != self.__ncomps:
raise InvalidFixFileError('Invalid FIX classification '
'file - number of components '
'do not match')
# Parse the labels for every component
# We dot not add the labels as we go
# as, if something is wrong with the
# file contents, we don't want this
# MelodicClassification instance to
# be modified. So we'll assign the
# labels afterwards
allLabels = []
for i, compLine in enumerate(compLines):
tokens = compLine.split(',')
tokens = [t.strip() for t in tokens]
if len(tokens) < 3:
raise InvalidFixFileError('Invalid FIX classification '
'file - component line {} does '
'not have enough '
'tokens'.format(i + 1))
compIdx = int(tokens[0])
compLabels = tokens[1:-1]
if compIdx != i + 1:
raise InvalidFixFileError('Invalid FIX classification '
'file - component line {} has '
'wrong component number '
'({})'.format(i, compIdx))
allLabels.append(compLabels)
# Now that all the labels are
# read in, we can store them
# Read the labels in
_, allLabels = loadMelodicLabelFile(filename)
# More labels in the file than there are in
# melodic_IC - that doesn't make any sense.
if len(allLabels) > self.__ncomps:
raise InvalidFixFileError('The number of components in {} does '
'not match the number of components in '
'{}!'.format(filename,
self.__melimage.dataSource))
# Less labels in the file than there are in
# the melodic_IC image - this is ok, as the
# file may have only contained a list of
# noisy components. We'll label the remaining
# components as 'Unknown'.
elif len(allLabels) < self.__ncomps:
for i in range(len(allLabels), self.__ncomps):
allLabels.append(['Unknown'])
# Add the labels to this melclass object
notifState = self.getNotificationState('labels')
self.disableNotification('labels')
......@@ -330,44 +273,19 @@ class MelodicClassification(props.HasProperties):
def save(self, filename):
"""Saves the component classifications stored by this
``MeloidicClassification`` to the specified file. The classifications
are saved in the format described in the :meth:`load` method.
.. TODO:: Accept a dictionary of ``{label : display label}`` mappings,
so we can output cased labels (e.g. ``'Signal'`` instead of
``'signal'``).
``MeloidicClassification`` to the specified file. See the
:func:`saveMelodicLabelFile` function.
"""
lines = []
badComps = []
image = self.__melimage
# The first line - the melodic directory name
lines.append(op.basename(image.getMelodicDir()))
# A line for each component
for comp in range(self.__ncomps):
noise = not (self.hasLabel(comp, 'signal') or
self.hasLabel(comp, 'unknown'))
# Make sure there are no
# commas in any label names
labels = [self.getDisplayLabel(l) for l in self.getLabels(comp)]
labels = [l.replace(',', '_') for l in labels]
tokens = [str(comp + 1)] + labels + [str(noise)]
lines.append(', '.join(tokens))
if noise:
badComps.append(comp)
allLabels = []
# A line listing the bad components
lines.append('[' + ', '.join([str(c + 1) for c in badComps]) + ']')
for c in range(self.__ncomps):
labels = [self.getDisplayLabel(l) for l in self.labels[c]]
allLabels.append(labels)
with open(filename, 'wt') as f:
f.write('\n'.join(lines) + '\n')
saveMelodicLabelFile(self.__melImage.getMelodicDir(),
allLabels,
filename)
def getLabels(self, component):
......@@ -483,7 +401,162 @@ class MelodicClassification(props.HasProperties):
self.notify('labels')
class InvalidFixFileError(Exception):
def loadMelodicLabelFile(filename):
"""Loads component labels from the specified file. The file is assuemd
to be of the format generated by FIX or Melview; such a file should
have a structure resembling the following::
filtered_func_data.ica
1, Signal, False
2, Unclassified Noise, True
3, Unknown, False
4, Signal, False
5, Unclassified Noise, True
6, Unclassified Noise, True
7, Unclassified Noise, True
8, Signal, False
[2, 5, 6, 7]
The first line of the file contains the name of the melodic directory.
Then, one line is present for each component, containing the following,
separated by commas:
- The component index (starting from 1).
- One or more labels for the component (multiple labels must be
comma-separated).
- ``'True'`` if the component has been classified as *bad*,
``'False'`` otherwise.
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
signal or unknown.
"""
with open(filename, 'rt') as f:
lines = f.readlines()
if len(lines) < 1:
raise InvalidFixFileError('Invalid FIX classification '
'file - not enough lines')
lines = [l.strip() for l in lines]
lines = [l for l in lines if l != '']
# If the file contains a single
# line, we assume that it is just
# a list of noise components.
if len(lines) == 1:
melDir = None
noisyComps = map(int, lines[0][1:-1].split(', '))
allLabels = []
for i in range(max(noisyComps)):
if (i + 1) in noisyComps: allLabels.append(['Unclassified noise'])
else: allLabels.append(['Signal'])
# Otherwise, we assume that
# it is a full label file.
else:
melDir = lines[0]
noisyComps = map(int, lines[-1][1:-1].split(', '))
# Parse the labels for every component
# We dot not add the labels as we go
# as, if something is wrong with the
# file contents, we don't want this
# MelodicClassification instance to
# be modified. So we'll assign the
# labels afterwards
allLabels = []
for i, compLine in enumerate(lines[1:-1]):
tokens = compLine.split(',')
tokens = [t.strip() for t in tokens]
if len(tokens) < 3:
raise InvalidFixFileError('Invalid FIX classification '
'file - component line {} does '
'not have enough '
'tokens'.format(i + 1))
compIdx = int(tokens[0])
compLabels = tokens[1:-1]
if compIdx != i + 1:
raise InvalidFixFileError('Invalid FIX classification '
'file - component line {} has '
'wrong component number '
'({})'.format(i, compIdx))
allLabels.append(compLabels)
# Validate the labels against
# the noisy list - all components
# in the noisy list should have
# the label 'unclassified noise'.
for i, labels in enumerate(allLabels):
for label in labels:
if label.lower() == 'unclassified noise' and \
(i + 1) not in noisyComps:
raise InvalidFixFileError('Noisy component {} has an invalid '
'label: {}'.format(i + 1, label))
for comp in noisyComps:
labels = allLabels[comp - 1]
labels = [l.lower() for l in labels]
if 'unclassified noise' not in labels:
raise InvalidFixFileError('Noisy component {} is missing '
'a noise label'.format(i))
return melDir, allLabels
def saveMelodicLabelFile(melDir, allLabels, filename):
"""Saves the component classifications stored by this
``MeloidicClassification`` to the specified file. The classifications
are saved in the format described in the :meth:`load` method.
"""
lines = []
noisyComps = []
# The first line - the melodic directory name
lines.append(op.abspath(melDir))
# A line for each component
for i, labels in enumerate(allLabels):
comp = i + 1
lowered = [l.lower() for l in labels]
noise = 'signal' not in lowered and 'unknown' not in lowered
# Make sure there are no
# commas in any label names
labels = [l.replace(',', '_') for l in labels]
tokens = [str(comp + 1)] + labels + [str(noise)]
lines.append(', '.join(tokens))
if noise:
noisyComps.append(comp)
# A line listing the bad components
lines.append('[' + ', '.join([str(c + 1) for c in noisyComps]) + ']')
with open(filename, 'wt') as f:
f.write('\n'.join(lines) + '\n')
class InvalidFixFileError(Exception):
"""Exception raised by the :meth:`MelodicClassification.load` method and
the :func:`loadMelodicLabelFile` function when an attempt is made to load
an invalid FIX label file.
"""
pass
1 0.419608 0.619608 0.992157 Signal
2 0.490196 0.827451 0.000000 Unknown
3 0.980392 0.478431 0.588235 Unclassified noise
4 0.843137 0.776471 0.000000 Movement
5 0.850980 0.435294 0.000000 Cardiac
6 0.494118 0.964706 0.984314 White matter
7 0.984313 0.564706 0.850980 Non-brain
8 0.858824 0.854902 0.847059 MRI
9 0.666667 0.972549 0.745098 Susceptibility-motion
10 0.780392 0.470588 0.870588 Sagittal sinus
11 0.427450 0.725490 0.603922 Respiratory
1 0.39216 0.82745 0.00000 Signal
2 0.91765 0.86275 0.00000 Unknown
3 1.00000 0.37647 0.26275 Unclassified noise
4 0.08235 0.82353 0.83922 Movement
5 0.70588 0.27059 1.00000 Cardiac
6 0.49412 0.96471 0.98431 White matter
7 0.98431 0.56471 0.85098 Non-brain
8 0.85882 0.85490 0.84706 MRI
9 0.66667 0.97255 0.74510 Susceptibility-motion
10 0.78039 0.47059 0.87059 Sagittal sinus
11 0.42745 0.72549 0.60392 Respiratory
......@@ -404,13 +404,13 @@ BUILT_IN_PERSPECTIVES = collections.OrderedDict((
('melodic',
textwrap.dedent("""
LightBoxPanel,TimeSeriesPanel,PowerSpectrumPanel,
layout2|name=LightBoxPanel 1;caption=Lightbox View 1;state=67377088;dir=5;layer=0;row=0;pos=0;prop=100000;bestw=853;besth=-1;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=TimeSeriesPanel 2;caption=Time series 2;state=67377148;dir=3;layer=0;row=0;pos=0;prop=100000;bestw=-1;besth=472;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=PowerSpectrumPanel 3;caption=Power spectra 3;state=67377148;dir=3;layer=0;row=0;pos=1;prop=100000;bestw=-1;besth=472;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|dock_size(5,0,0)=22|dock_size(3,0,0)=493|
OverlayListPanel,LightBoxToolBar,OverlayDisplayToolBar,LocationPanel,MelodicClassificationPanel,LookupTablePanel,
layout2|name=Panel;caption=;state=768;dir=5;layer=0;row=0;pos=0;prop=100000;bestw=20;besth=20;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=OverlayListPanel;caption=Overlay list;state=67373052;dir=3;layer=0;row=0;pos=0;prop=100000;bestw=197;besth=80;minw=197;minh=80;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=197;floath=96;notebookid=-1;transparent=255|name=LightBoxToolBar;caption=Lightbox view toolbar;state=67382012;dir=1;layer=10;row=0;pos=0;prop=100000;bestw=757;besth=43;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=OverlayDisplayToolBar;caption=Display toolbar;state=67382012;dir=1;layer=11;row=0;pos=0;prop=100000;bestw=860;besth=49;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=LocationPanel;caption=Location;state=67373052;dir=3;layer=0;row=0;pos=1;prop=100000;bestw=440;besth=109;minw=440;minh=109;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=440;floath=125;notebookid=-1;transparent=255|name=MelodicClassificationPanel;caption=Melodic IC classification;state=67373052;dir=2;layer=0;row=0;pos=1;prop=100000;bestw=400;besth=100;minw=400;minh=100;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=400;floath=116;notebookid=-1;transparent=255|name=LookupTablePanel;caption=Lookup tables;state=67373052;dir=2;layer=0;row=0;pos=0;prop=100000;bestw=358;besth=140;minw=358;minh=140;maxw=-1;maxh=-1;floatx=3614;floaty=658;floatw=358;floath=156;notebookid=-1;transparent=255|dock_size(5,0,0)=22|dock_size(3,0,0)=130|dock_size(1,10,0)=45|dock_size(1,11,0)=10|dock_size(2,0,0)=402|
layout2|name=LightBoxPanel 1;caption=Lightbox View 1;state=67377088;dir=5;layer=0;row=0;pos=0;prop=100000;bestw=853;besth=-1;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=TimeSeriesPanel 2;caption=Time series 2;state=67377148;dir=3;layer=0;row=0;pos=0;prop=100000;bestw=-1;besth=472;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=PowerSpectrumPanel 3;caption=Power spectra 3;state=67377148;dir=3;layer=0;row=0;pos=1;prop=100000;bestw=-1;besth=472;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|dock_size(5,0,0)=22|dock_size(3,0,0)=195|
OverlayListPanel,OverlayDisplayToolBar,LocationPanel,LightBoxToolBar,MelodicClassificationPanel,
layout2|name=Panel;caption=;state=768;dir=5;layer=0;row=0;pos=0;prop=100000;bestw=20;besth=20;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=OverlayListPanel;caption=Overlay list;state=67373052;dir=3;layer=0;row=0;pos=0;prop=100000;bestw=204;besth=80;minw=197;minh=80;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=204;floath=96;notebookid=-1;transparent=255|name=OverlayDisplayToolBar;caption=Display toolbar;state=67382012;dir=1;layer=11;row=0;pos=0;prop=100000;bestw=810;besth=49;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=LocationPanel;caption=Location;state=67373052;dir=3;layer=0;row=0;pos=1;prop=100000;bestw=440;besth=111;minw=440;minh=109;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=440;floath=127;notebookid=-1;transparent=255|name=LightBoxToolBar;caption=Lightbox view toolbar;state=67382012;dir=1;layer=10;row=0;pos=0;prop=100000;bestw=753;besth=43;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=MelodicClassificationPanel;caption=Melodic IC classification;state=67373052;dir=2;layer=0;row=0;pos=0;prop=100000;bestw=400;besth=100;minw=400;minh=100;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=400;floath=116;notebookid=-1;transparent=255|dock_size(5,0,0)=22|dock_size(3,0,0)=130|dock_size(1,10,0)=45|dock_size(1,11,0)=51|dock_size(2,0,0)=402|
,
layout2|name=FigureCanvasWxAgg;caption=;state=768;dir=5;layer=0;row=0;pos=0;prop=100000;bestw=640;besth=480;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|dock_size(5,0,0)=642|
,
layout2|name=FigureCanvasWxAgg;caption=;state=768;dir=5;layer=0;row=0;pos=0;prop=100000;bestw=640;besth=480;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|dock_size(5,0,0)=642|
layout2|name=FigureCanvasWxAgg;caption=;state=768;dir=5;layer=0;row=0;pos=0;prop=100000;bestw=640;besth=480;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|dock_size(5,0,0)=642|'
""")),
('feat',
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment