Commit 1e05174a authored by Paul McCarthy's avatar Paul McCarthy 🚵
Browse files

Merge branch 'rel/0.31.2' into 'v0.31'

Rel/0.31.2

See merge request fsl/fsleyes/fsleyes!164
parents 24387a18 ef0b8d5c
......@@ -68,24 +68,24 @@ if [ "$TEST_STYLE"x != "x" ]; then exit 0; fi
# Run the tests
export MPLBACKEND=wxagg
export FSLEYES_TEST_GL=2.1
((xvfb-run -a -s "-screen 0 640x480x24" pytest --cov-report= --cov-append -m "not (clitest or overlayclitest)" && echo "0" > status) || echo "1" > status) || true
((xvfb-run -a -s "-screen 0 1920x1200x24" pytest --cov-report= --cov-append -m "not (clitest or overlayclitest)" && echo "0" > status) || echo "1" > status) || true
status=`cat status`
failed=$status
sleep 5
((xvfb-run -a -s "-screen 0 640x480x24" pytest --cov-report= --cov-append -m "clitest" && echo "0" > status) || echo "1" > status) || true
((xvfb-run -a -s "-screen 0 1920x1200x24" pytest --cov-report= --cov-append -m "clitest" && echo "0" > status) || echo "1" > status) || true
status=`cat status`
failed=`echo "$status + $failed" | bc`
sleep 5
((xvfb-run -a -s "-screen 0 640x480x24" pytest --cov-report= --cov-append -m "overlayclitest" && echo "0" > status) || echo "1" > status) || true
((xvfb-run -a -s "-screen 0 1920x1200x24" pytest --cov-report= --cov-append -m "overlayclitest" && echo "0" > status) || echo "1" > status) || true
status=`cat status`
failed=`echo "$status + $failed" | bc`
sleep 5
# test overlay types for GL14 as well
export FSLEYES_TEST_GL=1.4
((xvfb-run -a -s "-screen 0 640x480x24" pytest --cov-report= --cov-append -m "overlayclitest" && echo "0" > status) || echo "1" > status) || true
((xvfb-run -a -s "-screen 0 1920x1200x24" pytest --cov-report= --cov-append -m "overlayclitest" && echo "0" > status) || echo "1" > status) || true
status=`cat status`
failed=`echo "$status + $failed" | bc`
......
......@@ -163,27 +163,27 @@ variables:
- bash ./.ci/test_template.sh
test:3.5:
test:3.6:
stage: test
image: pauldmccarthy/fsleyes-py35-wxpy4-gtk2
image: pauldmccarthy/fsleyes-py36-wxpy4-gtk3
<<: *test_template
test:3.6:
test:3.7:
stage: test
image: pauldmccarthy/fsleyes-py36-wxpy4-gtk2
image: pauldmccarthy/fsleyes-py37-wxpy4-gtk3
<<: *test_template
test:3.7:
test:3.8:
stage: test
image: pauldmccarthy/fsleyes-py37-wxpy4-gtk2
image: pauldmccarthy/fsleyes-py38-wxpy4-gtk3
<<: *test_template
test:build-pypi-dist:
stage: test
image: pauldmccarthy/fsleyes-py35-wxpy4-gtk2
image: pauldmccarthy/fsleyes-py36-wxpy4-gtk3
<<: *except_releases
tags:
......@@ -201,7 +201,7 @@ test:build-pypi-dist:
style:
stage: style
image: pauldmccarthy/fsleyes-py35-wxpy4-gtk2
image: pauldmccarthy/fsleyes-py36-wxpy4-gtk3
<<: *test_template
variables:
TEST_STYLE: "true"
......@@ -219,7 +219,7 @@ style:
- docker
stage: doc
image: python:3.5
image: python:3.6
build-userdoc:
......@@ -256,7 +256,7 @@ build-pypi-dist:
<<: *check_version
stage: build
image: pauldmccarthy/fsleyes-py35-wxpy4-gtk2
image: pauldmccarthy/fsleyes-py36-wxpy4-gtk3
tags:
- docker
......@@ -281,7 +281,7 @@ deploy-doc:
<<: *setup_ssh
stage: deploy
when: manual
image: python:3.5
image: python:3.6
tags:
- docker
......@@ -299,7 +299,7 @@ deploy-pypi:
<<: *setup_ssh
stage: deploy
when: manual
image: python:3.5
image: python:3.6
tags:
- docker
......
......@@ -9,6 +9,28 @@ This document contains the ``fsleyes`` release history in reverse
chronological order.
0.31.2 (Tuesday October 22nd 2019)
----------------------------------
Changed
^^^^^^^
* FSLeyes is now more lenient towards NIfTI images with extreme qform affines.
* Various changes to improve GTK3 compatibility.
* Various changes to reduce warnings and unnecessary output messages.
Fixed
^^^^^
* Image texture data is now prepared off the main thread; this was the
behaviour before version 0.30.0, but was accidentally disabled for that
release.
0.31.1 (Tuesday October 8th 2019)
---------------------------------
......
......@@ -337,6 +337,18 @@ def _hacksAndWorkarounds():
if fslplatform.frozen:
os.environ.pop('MPLCONFIGDIR', None)
# nibabel rejects NIfTI images where the
# quaternion vector has a length greater
# than 1. This is fine, as it is mandated
# by the NIfTI spec. But FSL is much more
# lenient than nibabel, and nibabel can
# also reject some qforms due to float32
# imprecision. So here we're increasing
# the tolerance of nibabel to strange
# qforms.
import nibabel as nib
nib.Nifti1Header.quaternion_threshold = -1e5
# OSX sometimes sets the local environment
# variables to non-standard values, which
# breaks the python locale module.
......
......@@ -29,7 +29,7 @@ import numpy as np
import wx
import fsl.data.image as fslimage
import fsl.utils.transform as transform
import fsl.transform.flirt as flirt
import fsleyes.strings as strings
from . import base
......@@ -87,7 +87,7 @@ def calculateTransform(overlay, overlayList, displayCtx, matFile, refFile):
"""Calculates a source voxel -> reference world transformation matrix
from the given FLIRT transform and refernece image files.
See the :func:`fsl.utils.transform.flirtMatrixToSform` function.
See the :func:`fsl.transform.flirt.flirtMatrixToSform` function.
:arg overlay: The :class:`.Image` overlay - the source image of the
FLIRT transformation.
......@@ -110,7 +110,7 @@ def calculateTransform(overlay, overlayList, displayCtx, matFile, refFile):
flirtMat = np.loadtxt(matFile)
return transform.flirtMatrixToSform(flirtMat, overlay, refImg)
return flirt.flirtMatrixToSform(flirtMat, overlay, refImg)
def promptForFlirtFiles(parent, overlay, overlayList, displayCtx, save=False):
......@@ -260,8 +260,8 @@ def guessFlirtFiles(path):
mat = op.join(regDir, mat)
ref = op.join(regDir, ref)
try: ref = fslimage.addExt(ref)
except: continue
try: ref = fslimage.addExt(ref)
except Exception: continue
if op.exists(mat):
matFile = mat
......
......@@ -145,6 +145,14 @@ class Action(props.HasProperties):
return self.__displayCtx
@property
def instance(self):
"""Return the instance which owns this ``Action``, if relevant.
Returns ``None`` otherwise.
"""
return self.__instance
def __call__(self, *args, **kwargs):
"""Calls this action. An :exc:`ActionDisabledError` will be raised
if :attr:`enabled` is ``False``.
......
......@@ -19,7 +19,7 @@ import PIL
import wx
import fsl.utils.idle as idle
import fsl.utils.transform as transform
import fsl.transform.affine as affine
import fsl.utils.settings as fslsettings
import fsleyes_widgets.utils.progress as progress
from fsleyes_widgets import isalive
......@@ -261,7 +261,7 @@ def makeGif(overlayList,
# normalise the rotmat for this
# frame to the rms difference
# from the starting rotmat
frame = transform.rmsdev(ctx.startFrame, frame)
frame = affine.rmsdev(ctx.startFrame, frame)
# Keep capturing frames until we
# have performed a full 360 degree
......
......@@ -15,7 +15,7 @@ import logging
import numpy as np
import fsl.data.image as fslimage
import fsl.utils.transform as transform
import fsl.transform.affine as affine
import fsleyes_widgets.utils.status as status
import fsleyes.strings as strings
from . import base
......@@ -95,4 +95,4 @@ def calculateTransform(overlay,
if refImg is None:
refImg = fslimage.Image(refFile, loadData=False)
return transform.sformToFlirtMatrix(overlay, refImg, srcXform)
return affine.sformToFlirtMatrix(overlay, refImg, srcXform)
......@@ -46,7 +46,8 @@ class SaveOverlayAction(base.Action):
:arg frame: The :class:`.FSLeyesFrame`.
"""
base.Action.__init__(self, overlayList, displayCtx, self.__saveOverlay)
self.__name = '{}_{}'.format(type(self).__name__, id(self))
self.__name = '{}_{}'.format(type(self).__name__, id(self))
self.__registered = None
displayCtx .addListener('selectedOverlay',
self.__name,
......@@ -64,6 +65,11 @@ class SaveOverlayAction(base.Action):
"""
self.displayCtx .removeListener('selectedOverlay', self.__name)
self.overlayList.removeListener('overlays', self.__name)
if self.__registered is not None:
self.__registered.deregister(self.__name, 'saveState')
self.__registered = None
base.Action.destroy(self)
......@@ -82,19 +88,17 @@ class SaveOverlayAction(base.Action):
isinstance(overlay, fslimage.Image) and
(not overlay.saveState))
for ovl in self.overlayList:
if not isinstance(ovl, fslimage.Image):
continue
ovl.deregister(self.__name, 'saveState')
# Register a listener on the saved property
# of the currently selected image, so we can
# enable the save action when the image
# becomes 'unsaved', and vice versa.
if ovl is overlay:
ovl.register(self.__name,
if self.__registered is not None:
self.__registered.deregister(self.__name, 'saveState')
self.__registered = None
# Register a listener on the saved property
# of the currently selected image, so we can
# enable the save action when the image
# becomes 'unsaved', and vice versa.
if self.enabled:
self.__registered = overlay
overlay.register(self.__name,
self.__overlaySaveStateChanged,
'saveState')
......@@ -108,28 +112,17 @@ class SaveOverlayAction(base.Action):
see the :meth:`__selectedOverlayChanged` method.
"""
overlay = self.displayCtx.getSelectedOverlay()
if overlay is None:
self.enabled = False
elif not isinstance(overlay, fslimage.Image):
self.enabled = False
else:
self.enabled = not overlay.saveState
overlay = self.__registered
self.enabled = (overlay is not None) and (not overlay.saveState)
def __saveOverlay(self):
"""Called when this :class:`.Action` is executed. Calls
:func:`saveOverlay` with the currentyl selected overlay.
:func:`saveOverlay` with the currently selected overlay.
"""
overlay = self.displayCtx.getSelectedOverlay()
if (overlay is not None) and \
isinstance(overlay, fslimage.Image) and \
(not overlay.saveState):
overlay = self.__registered
if overlay is not None:
display = self.displayCtx.getDisplay(overlay)
saveOverlay(overlay, display)
......
......@@ -82,7 +82,7 @@ class ScreenshotAction(base.Action):
# it is available
if 'png' in fmts:
fmts = [('png', fmts['png'])] + \
[(k, v) for k, v in fmts.items() if k is not 'png']
[(k, v) for k, v in fmts.items() if k != 'png']
else:
fmts = list(fmts.items())
......
......@@ -73,6 +73,7 @@ class AtlasInfoPanel(fslpanel.FSLeyesPanel):
self.__infoPanel = wxhtml.HtmlWindow(self.__contentPanel)
self.__atlasList = elistbox.EditableListBox(
self.__contentPanel,
vgap=5,
style=(elistbox.ELB_NO_ADD |
elistbox.ELB_NO_REMOVE |
elistbox.ELB_NO_MOVE))
......@@ -166,7 +167,7 @@ class AtlasInfoPanel(fslpanel.FSLeyesPanel):
def onLoad(atlas):
if not self or self.destroyed():
if not self or self.destroyed:
return
self.__enabledAtlases[atlasID] = atlas
......@@ -499,6 +500,8 @@ class AtlasListWidget(wx.CheckBox):
self.Bind(wx.EVT_CHECKBOX, self.__onEnable)
self.SetMinSize(self.GetBestSize())
def __onEnable(self, ev):
"""Called when this ``AtlasListWidget`` is clicked. Toggles
......
......@@ -55,6 +55,7 @@ class AtlasManagementPanel(fslpanel.FSLeyesPanel):
labels=names,
clientData=descs,
tooltips=paths,
vgap=5,
style=(elistbox.ELB_NO_MOVE |
elistbox.ELB_TOOLTIP_DOWN))
......
......@@ -101,6 +101,7 @@ class AtlasOverlayPanel(fslpanel.FSLeyesPanel):
style=wx.SP_LIVE_UPDATE)
self.__atlasList = elistbox.EditableListBox(
self.__contentPanel,
vgap=5,
style=(elistbox.ELB_NO_ADD |
elistbox.ELB_NO_REMOVE |
elistbox.ELB_NO_MOVE))
......@@ -385,6 +386,7 @@ class AtlasOverlayPanel(fslpanel.FSLeyesPanel):
# widgets asynchronously on the wx idle loop.
regionList = elistbox.EditableListBox(
self.__regionPanel,
vgap=5,
style=(elistbox.ELB_NO_ADD |
elistbox.ELB_NO_REMOVE |
elistbox.ELB_NO_MOVE))
......@@ -546,7 +548,7 @@ class OverlayListWidget(wx.Panel):
if not self or \
not self.__atlasOvlPanel or \
self.__atlasOvlPanel .destroyed():
self.__atlasOvlPanel.destroyed:
return
self.__atlasPanel.enableAtlasPanel()
......
......@@ -322,7 +322,7 @@ class AtlasPanel(ctrlpanel.ControlPanel):
# The atlas panel may be destroyed
# before the atlas is loaded.
if not self or self.destroyed():
if not self or self.destroyed:
return
self.__loadedAtlases[atlasID, summary, res] = atlas
......
......@@ -39,7 +39,7 @@ def colourBarMinorAxisSize(fontSize):
# Fix the minor axis of the colour bar,
# according to the font size, and a
# constant size for the colour bar
return 2 * fontSize + 40
return round(2 * fontSize + 40)
class ColourBar(props.HasProperties, notifier.Notifier):
......
......@@ -83,7 +83,7 @@ class SettingsPanel(ControlPanel):
ControlPanel.__init__(self, *args, **kwargs)
self.__widgets = widgetlist.WidgetList(self)
self.__widgets = widgetlist.WidgetList(self, minHeight=24)
self.__sizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(self.__sizer)
......
......@@ -79,6 +79,8 @@ class DisplaySpaceWarning(fslpanel.FSLeyesPanel):
self.__sizer .Add(self.__realSizer, flag=wx.EXPAND)
self.SetSizer(self.__sizer)
self.Fit()
self.__size = self.GetBestSize()
self.__changeDS .Bind(wx.EVT_BUTTON, self.__onChangeDS)
......@@ -117,6 +119,9 @@ class DisplaySpaceWarning(fslpanel.FSLeyesPanel):
message is shown.
"""
if self.destroyed:
return
parent = self.GetParent()
condition = self.__warnCondition
displayCtx = self.displayCtx
......@@ -144,10 +149,16 @@ class DisplaySpaceWarning(fslpanel.FSLeyesPanel):
else:
show = False
if show: size = self.__size
else: size = (0, 0)
if show:
log.debug('Showing display space warning ({} / {})'.format(
displaySpace, condition))
self.SetMinSize( size)
self.CacheBestSize(size)
self.__sizer.Show(self.__realSizer, show)
self.Layout()
self.Fit()
......
......@@ -18,7 +18,7 @@ import numpy as np
import fsl.data.image as fslimage
import fsl.utils.idle as idle
import fsl.utils.transform as transform
import fsl.transform.affine as affine
import fsleyes_props as props
import fsleyes_widgets.floatslider as fslider
......@@ -457,12 +457,12 @@ class EditTransformPanel(ctrlpanel.ControlPanel):
# of the image in world coordinates
# to define the origin of rotation.
shape = self.__overlay.shape
lo, hi = transform.axisBounds(shape, self.__overlay.voxToWorldMat)
lo, hi = affine.axisBounds(shape, self.__overlay.voxToWorldMat)
origin = [l + (h - l) / 2.0 for h, l in zip(hi, lo)]
else:
origin = self.displayCtx.worldLocation
return transform.compose(scales, offsets, rotations, origin)
return affine.compose(scales, offsets, rotations, origin)
def __xformChanged(self, ev=None):
......@@ -481,7 +481,7 @@ class EditTransformPanel(ctrlpanel.ControlPanel):
else: v2wXform = self.__extraXform
xform = self.__getCurrentXform()
xform = transform.concat(xform, v2wXform)
xform = affine.concat(xform, v2wXform)
self.__formatXform(xform, self.__newXform)
......@@ -491,7 +491,7 @@ class EditTransformPanel(ctrlpanel.ControlPanel):
# the voxToWorldMat entirely. So we include
# a worldToVoxMat transform to trick the
# NiftiOpts code.
opts.displayXform = transform.concat(xform, overlay.worldToVoxMat)
opts.displayXform = affine.concat(xform, overlay.worldToVoxMat)
def __onApply(self, ev):
......@@ -511,7 +511,7 @@ class EditTransformPanel(ctrlpanel.ControlPanel):
newXform = self.__getCurrentXform()
opts = self.displayCtx.getOpts(overlay)
xform = transform.concat(newXform, v2wXform)
xform = affine.concat(newXform, v2wXform)
with props.suppress(opts, 'displayXform'):
opts.displayXform = np.eye(4)
......@@ -622,7 +622,7 @@ class EditTransformPanel(ctrlpanel.ControlPanel):
else: v2wXform = self.__extraXform
newXform = self.__getCurrentXform()
v2wXform = transform.concat(newXform, v2wXform)
v2wXform = affine.concat(newXform, v2wXform)
if affType == 'flirt':
xform = saveflirtxfm.calculateTransform(
......
......@@ -355,9 +355,9 @@ class FileTypePanel(elb.EditableListBox):
:arg ftpanel: The :class:`.FileTreePanel`
"""
elb.EditableListBox.__init__(
self, parent, style=(elb.ELB_NO_ADD |
elb.ELB_NO_REMOVE |
elb.ELB_NO_MOVE))
self, parent, vgap=5, style=(elb.ELB_NO_ADD |
elb.ELB_NO_REMOVE |
elb.ELB_NO_MOVE))
self.__ftpanel = ftpanel
......@@ -378,6 +378,7 @@ class FileTypePanel(elb.EditableListBox):
for ftype in filetypes:
toggle = wx.CheckBox(self)
toggle.SetMinSize(toggle.GetBestSize())
self.Append(ftype, extraWidget=toggle)
toggle.Bind(wx.EVT_CHECKBOX, self.__onToggle)
......
......@@ -17,7 +17,7 @@ import wx.html as wxhtml
import numpy as np
import fsl.utils.transform as transform
import fsl.transform.affine as affine
import fsl.utils.settings as fslsettings
import fsl.data.image as fslimage
import fsl.data.mesh as fslmesh
......@@ -408,7 +408,7 @@ class LocationInfoPanel(fslpanel.FSLeyesPanel):
if w > width: width = w
if h > height: height = h
return width + 5, height + 5
return width + 5, height
def __selectedOverlayChanged(self, *a):
......@@ -657,7 +657,7 @@ class LocationInfoPanel(fslpanel.FSLeyesPanel):
shape = refImage.shape[:3]
vlo = [0, 0, 0]
vhi = np.array(shape) - 1
wlo, whi = transform.axisBounds(shape, v2w)
wlo, whi = affine.axisBounds(shape, v2w)
wstep = refImage.pixdim[:3]
else:
vlo = [0, 0, 0]
......@@ -701,7 +701,7 @@ class LocationInfoPanel(fslpanel.FSLeyesPanel):
between the three location properties.
"""
if not self or self.destroyed():
if not self or self.destroyed:
return
if len(self.overlayList) == 0: return
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment