#
# autodisplay.py - Routines for configuring default overlay display
# settings.
#
# Author: Paul McCarthy <pauldmccarthy@gmail.com>
#
"""This module provides the :func:`audoDisplay` function, which is used
for automatically configuring overlay display settings.
The :autoDisplay` function is called when *FSLeyes* is started, and when
new overlays are loaded.
"""
import re
import sys
import logging
import os.path as op
import fsl.data.image as fslimage
log = logging.getLogger(__name__)
[docs]def autoDisplay(overlay, overlayList, displayCtx, **kwargs):
"""Automatically configure display settings for the given overlay.
:arg overlay: The overlay object (e.g. an :class:`.Image` instance).
:arg overlayList: The :class:`.OverlayList`.
:arg displayCtx: The :class:`.DisplayContext`.
:arg kwargs: Passed through to the overlay-type specific function.
"""
oType = type(overlay).__name__
func = getattr(sys.modules[__name__], '_{}Display'.format(oType), None)
if func is None:
log.warn('Unknown overlay type: {}'.format(oType))
return
log.debug('Applying default display arguments for {}'.format(overlay))
func(overlay, overlayList, displayCtx, **kwargs)
[docs]def _ImageDisplay(overlay, overlayList, displayCtx, **kwargs):
"""Configure default display settings for the given :class:`.Image`
overlay.
"""
if _isStatImage(overlay):
_statImageDisplay(overlay, overlayList, displayCtx, **kwargs)
elif _isPEImage(overlay):
_peImageDisplay( overlay, overlayList, displayCtx, **kwargs)
# Automatically configure nice display range?
[docs]def _isStatImage(overlay):
"""Returns ``True`` if the given :class:`.Image` overlay looks like a
statistic image, ``False`` otherwise.
"""
basename = op.basename(overlay.dataSource)
basename = fslimage.removeExt(basename)
tokens = ['zstat', 'tstat', 'fstat', 'zfstat']
pattern = r'({})\d+'.format('|'.join(tokens))
return re.search(pattern, basename) is not None
[docs]def _isPEImage(overlay):
"""Returns ``True`` if the given :class:`.Image` overlay looks like a
statistic image, ``False`` otherwise.
"""
basename = op.basename(overlay.dataSource)
basename = fslimage.removeExt(basename)
tokens = ['cope', 'pe']
pattern = r'^({})\d+'.format('|'.join(tokens))
return re.search(pattern, basename) is not None
[docs]def _statImageDisplay(overlay,
overlayList,
displayCtx,
zthres=3.0,
posCmap=None,
negCmap=None):
"""Configure default display settings for the given statistic
:class:`.Image` overlay.
"""
opts = displayCtx.getOpts(overlay)
basename = op.basename(overlay.dataSource)
basename = fslimage.removeExt(basename)
pTokens = ['p', 'corrp']
statTokens = ['zstat', 'tstat', 'zfstat']
fStatTokens = ['fstat']
# Rendered stat images (e.g.
# rendered_thres_zstat1) are
# generated specifically for
# use with the Render1 colour
# map.
if 'rendered' in basename:
opts.cmap = 'Render1'
# Give each normal stat image
# a different colour map
else:
cmap = _statImageDisplay.cmaps[_statImageDisplay.currentCmap]
if posCmap is None: posCmap = cmap
if negCmap is None: negCmap = cmap
_statImageDisplay.currentCmap += 1
_statImageDisplay.currentCmap %= len(_statImageDisplay.cmaps)
opts.cmap = posCmap
opts.negativeCmap = negCmap
# The order of these tests is
# important, due to name overlap
# P-value image ?
if any([token in basename for token in pTokens]):
opts.displayRange = [0.95, 1.0]
opts.clippingRange = [0.95, 1.0]
# T or Z stat image?
elif any([token in basename for token in statTokens]) and \
'rendered' not in basename:
maxVal = overlay.dataRange[1]
opts.useNegativeCmap = True
opts.clippingRange = [zthres, maxVal]
opts.displayRange = [zthres, min((7.5, maxVal))]
# F stat image?
elif any([token in basename for token in fStatTokens]):
opts.displayRange = [0, 10]
# Colour maps used for statistic images
_statImageDisplay.cmaps = ['red-yellow',
'blue-lightblue',
'green',
'cool',
'hot',
'blue',
'red',
'yellow',
'pink',
'copper']
# Index into the cmaps list, pointing to the
# next colour map to use for statistic images.
_statImageDisplay.currentCmap = 0
[docs]def _peImageDisplay(overlay, overlayList, displayCtx):
"""Automatically configure display settings for the given PE/COPE
:class:`.Image` overlay.
"""
opts = displayCtx.getOpts(overlay)
opts.cmap = 'Red-Yellow'
opts.negativeCmap = 'Blue-LightBlue'
opts.useNegativeCmap = True
opts.displayRange = [1.0, 100.0]
opts.clippingRange = [1.0, overlay.dataRange[1]]
[docs]def _FEATImageDisplay(overlay, overlayList, displayCtx):
"""Automatically configure display settings for the given
:class:`.FEATImage` overlay.
"""
pass
[docs]def _MelodicImageDisplay(overlay, overlayList, displayCtx):
"""Automatically configure display settings for the given
:class:`.MelodicImage` overlay.
"""
opts = displayCtx.getOpts(overlay)
maxVal = overlay.dataRange[1]
opts.cmap = 'Red-Yellow'
opts.negativeCmap = 'Blue-LightBlue'
opts.useNegativeCmap = True
opts.displayRange = [3.0, min((10, maxVal))]
opts.clippingRange = [3.0, maxVal]
# Add the mean as an underlay
idx = overlayList.index(overlay)
meanFile = overlay.getMeanFile()
existing = [op.abspath(o.dataSource) for o in overlayList]
# But only if it's not
# already in the list
if meanFile not in existing:
log.debug('Inserting mean melodic image into '
'overlay list: {}'.format(meanFile))
meanImg = fslimage.Image(meanFile)
overlayList.insert(idx, meanImg)