diff options
author | Alexandre Marie <alexandre.marie@synchrotron-soleil.fr> | 2020-07-21 14:45:14 +0200 |
---|---|---|
committer | Alexandre Marie <alexandre.marie@synchrotron-soleil.fr> | 2020-07-21 14:45:14 +0200 |
commit | 328032e2317e3ac4859196bbf12bdb71795302fe (patch) | |
tree | 8cd13462beab109e3cb53410c42335b6d1e00ee6 /silx/gui/plot3d | |
parent | 33ed2a64c92b0311ae35456c016eb284e426afc2 (diff) |
New upstream version 0.13.0+dfsg
Diffstat (limited to 'silx/gui/plot3d')
-rw-r--r-- | silx/gui/plot3d/SFViewParamTree.py | 8 | ||||
-rw-r--r-- | silx/gui/plot3d/_model/items.py | 75 | ||||
-rw-r--r-- | silx/gui/plot3d/items/image.py | 4 | ||||
-rw-r--r-- | silx/gui/plot3d/items/mesh.py | 5 | ||||
-rw-r--r-- | silx/gui/plot3d/items/mixins.py | 38 | ||||
-rw-r--r-- | silx/gui/plot3d/items/scatter.py | 7 | ||||
-rw-r--r-- | silx/gui/plot3d/items/volume.py | 51 | ||||
-rw-r--r-- | silx/gui/plot3d/scene/function.py | 93 | ||||
-rw-r--r-- | silx/gui/plot3d/tools/GroupPropertiesWidget.py | 4 | ||||
-rw-r--r-- | silx/gui/plot3d/utils/mng.py | 4 |
10 files changed, 181 insertions, 108 deletions
diff --git a/silx/gui/plot3d/SFViewParamTree.py b/silx/gui/plot3d/SFViewParamTree.py index a2b771c..4e179fc 100644 --- a/silx/gui/plot3d/SFViewParamTree.py +++ b/silx/gui/plot3d/SFViewParamTree.py @@ -1,7 +1,7 @@ # coding: utf-8 # /*########################################################################## # -# Copyright (c) 2015-2018 European Synchrotron Radiation Facility +# Copyright (c) 2015-2020 European Synchrotron Radiation Facility # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -380,7 +380,7 @@ class _LightDirectionAngleBaseItem(SubjectItem): editor.setOrientation(qt.Qt.Horizontal) editor.setMinimum(-90) editor.setMaximum(90) - editor.setValue(self._pullData()) + editor.setValue(int(self._pullData())) # Wrapping call in lambda is a workaround for PySide with Python 3 editor.valueChanged.connect( @@ -389,7 +389,7 @@ class _LightDirectionAngleBaseItem(SubjectItem): return editor def setEditorData(self, editor): - editor.setValue(self._pullData()) + editor.setValue(int(self._pullData())) return True def _setModelData(self, editor): @@ -826,7 +826,7 @@ class _IsoLevelSlider(qt.QSlider): if width > 0: sliderWidth = self.maximum() - self.minimum() sliderPosition = sliderWidth * (self.__norm(level) - min_) / width - self.setValue(sliderPosition) + self.setValue(int(sliderPosition)) def __dataChanged(self): """Handles data update to refresh slider range if needed""" diff --git a/silx/gui/plot3d/_model/items.py b/silx/gui/plot3d/_model/items.py index 7f3921a..be51663 100644 --- a/silx/gui/plot3d/_model/items.py +++ b/silx/gui/plot3d/_model/items.py @@ -1,7 +1,7 @@ # coding: utf-8 # /*########################################################################## # -# Copyright (c) 2017-2019 European Synchrotron Radiation Facility +# Copyright (c) 2017-2020 European Synchrotron Radiation Facility # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -693,7 +693,7 @@ class _ColormapBaseProxyRow(ProxyRow): """ item = self.item() if item is not None and self._colormap is not None: - return self._colormap.getColormapRange(item._getDataRange()) + return self._colormap.getColormapRange(item) else: return 1, 100 # Fallback @@ -829,6 +829,55 @@ class _ColormapBoundRow(_ColormapBaseProxyRow): return super(_ColormapBoundRow, self).setData(column, value, role) +class _ColormapGammaRow(_ColormapBaseProxyRow): + """ProxyRow for colormap gamma normalization parameter + + :param ColormapMixIn item: The item to handle + :param str name: Name of the raw + """ + + def __init__(self, item): + _ColormapBaseProxyRow.__init__( + self, + item, + name="Gamma", + fget=self._getGammaNormalizationParameter, + fset=self._setGammaNormalizationParameter) + + self.setToolTip('Colormap gamma correction parameter:\n' + 'Only meaningful for gamma normalization.') + + def _getGammaNormalizationParameter(self): + """Proxy for :meth:`Colormap.getGammaNormalizationParameter`""" + if self._colormap is not None: + return self._colormap.getGammaNormalizationParameter() + else: + return 0.0 + + def _setGammaNormalizationParameter(self, gamma): + """Proxy for :meth:`Colormap.setGammaNormalizationParameter`""" + if self._colormap is not None: + return self._colormap.setGammaNormalizationParameter(gamma) + + def _getNormalization(self): + """Proxy for :meth:`Colormap.getNormalization`""" + if self._colormap is not None: + return self._colormap.getNormalization() + else: + return '' + + def flags(self, column): + if column in (0, 1): + if self._getNormalization() == 'gamma': + flags = qt.Qt.ItemIsEditable | qt.Qt.ItemIsEnabled + else: + flags = qt.Qt.NoItemFlags # Disabled if not gamma correction + return flags + + else: # Never event + return super(_ColormapGammaRow, self).flags(column) + + class ColormapRow(_ColormapBaseProxyRow): """Represents :class:`ColormapMixIn` property. @@ -862,6 +911,16 @@ class ColormapRow(_ColormapBaseProxyRow): notify=self._sigColormapChanged, editorHint=norms)) + self.addRow(_ColormapGammaRow(item)) + + modes = [mode.title() for mode in self._colormap.AUTOSCALE_MODES] + self.addRow(ProxyRow( + name='Autoscale Mode', + fget=self._getAutoscaleMode, + fset=self._setAutoscaleMode, + notify=self._sigColormapChanged, + editorHint=modes)) + self.addRow(_ColormapBoundRow(item, name='Min.', index=0)) self.addRow(_ColormapBoundRow(item, name='Max.', index=1)) @@ -908,6 +967,18 @@ class ColormapRow(_ColormapBaseProxyRow): if self._colormap is not None: return self._colormap.setNormalization(normalization.lower()) + def _getAutoscaleMode(self): + """Proxy for :meth:`Colormap.getAutoscaleMode`""" + if self._colormap is not None: + return self._colormap.getAutoscaleMode().title() + else: + return '' + + def _setAutoscaleMode(self, mode): + """Proxy for :meth:`Colormap.setAutoscaleMode`""" + if self._colormap is not None: + return self._colormap.setAutoscaleMode(mode.lower()) + def _updateColormapImage(self, *args, **kwargs): """Notify colormap update to update the image in the tree""" if self._colormapImage is not None: diff --git a/silx/gui/plot3d/items/image.py b/silx/gui/plot3d/items/image.py index 210f2f3..cfd1188 100644 --- a/silx/gui/plot3d/items/image.py +++ b/silx/gui/plot3d/items/image.py @@ -1,7 +1,7 @@ # coding: utf-8 # /*########################################################################## # -# Copyright (c) 2017-2018 European Synchrotron Radiation Facility +# Copyright (c) 2017-2020 European Synchrotron Radiation Facility # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -118,7 +118,7 @@ class ImageData(_Image, ColormapMixIn): False to use as is (do not modify!). """ self._image.setData(data, copy=copy) - ColormapMixIn._setRangeFromData(self, self.getData(copy=False)) + self._setColormappedData(self.getData(copy=False), copy=False) self._updated(ItemChangedType.DATA) def getData(self, copy=True): diff --git a/silx/gui/plot3d/items/mesh.py b/silx/gui/plot3d/items/mesh.py index 3577dbf..4e19939 100644 --- a/silx/gui/plot3d/items/mesh.py +++ b/silx/gui/plot3d/items/mesh.py @@ -1,7 +1,7 @@ # coding: utf-8 # /*########################################################################## # -# Copyright (c) 2017-2019 European Synchrotron Radiation Facility +# Copyright (c) 2017-2020 European Synchrotron Radiation Facility # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -312,8 +312,7 @@ class ColormapMesh(_MeshBase, ColormapMixIn): copy=copy) self._setMesh(mesh) - # Store data range info - ColormapMixIn._setRangeFromData(self, self.getValueData(copy=False)) + self._setColormappedData(self.getValueData(copy=False), copy=False) def getData(self, copy=True): """Get the mesh geometry. diff --git a/silx/gui/plot3d/items/mixins.py b/silx/gui/plot3d/items/mixins.py index b355627..14cafc8 100644 --- a/silx/gui/plot3d/items/mixins.py +++ b/silx/gui/plot3d/items/mixins.py @@ -1,7 +1,7 @@ # coding: utf-8 # /*########################################################################## # -# Copyright (c) 2017-2019 European Synchrotron Radiation Facility +# Copyright (c) 2017-2020 European Synchrotron Radiation Facility # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -111,7 +111,6 @@ class ColormapMixIn(_ColormapMixIn): def __init__(self, sceneColormap=None): super(ColormapMixIn, self).__init__() - self._dataRange = None self.__sceneColormap = sceneColormap self._syncSceneColormap() @@ -120,37 +119,6 @@ class ColormapMixIn(_ColormapMixIn): self._syncSceneColormap() super(ColormapMixIn, self)._colormapChanged() - def _setRangeFromData(self, data=None): - """Compute the data range the colormap should use from provided data. - - :param data: Data set from which to compute the range or None - """ - if data is None or data.size == 0: - dataRange = None - else: - dataRange = min_max(data, min_positive=True, finite=True) - if dataRange.minimum is None: # Only non-finite data - dataRange = None - - if dataRange is not None: - min_positive = dataRange.min_positive - if min_positive is None: - min_positive = float('nan') - dataRange = dataRange.minimum, min_positive, dataRange.maximum - - self._dataRange = dataRange - - colormap = self.getColormap() - if None in (colormap.getVMin(), colormap.getVMax()): - self._colormapChanged() - - def _getDataRange(self): - """Returns the data range as used in the scene for colormap - - :rtype: Union[List[float],None] - """ - return self._dataRange - def _setSceneColormap(self, sceneColormap): """Set the scene colormap to sync with Colormap object. @@ -171,8 +139,8 @@ class ColormapMixIn(_ColormapMixIn): self.__sceneColormap.colormap = colormap.getNColors() self.__sceneColormap.norm = colormap.getNormalization() - range_ = colormap.getColormapRange(data=self._dataRange) - self.__sceneColormap.range_ = range_ + self.__sceneColormap.gamma = colormap.getGammaNormalizationParameter() + self.__sceneColormap.range_ = colormap.getColormapRange(self) class ComplexMixIn(_ComplexMixIn): diff --git a/silx/gui/plot3d/items/scatter.py b/silx/gui/plot3d/items/scatter.py index 5fce629..24abaa5 100644 --- a/silx/gui/plot3d/items/scatter.py +++ b/silx/gui/plot3d/items/scatter.py @@ -1,7 +1,7 @@ # coding: utf-8 # /*########################################################################## # -# Copyright (c) 2017-2019 European Synchrotron Radiation Facility +# Copyright (c) 2017-2020 European Synchrotron Radiation Facility # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -100,7 +100,7 @@ class Scatter3D(DataItem3D, ColormapMixIn, SymbolMixIn): self._scatter.setAttribute('z', z, copy=copy) self._scatter.setAttribute('value', value, copy=copy) - ColormapMixIn._setRangeFromData(self, self.getValueData(copy=False)) + self._setColormappedData(self.getValueData(copy=False), copy=False) self._updated(ItemChangedType.DATA) def getData(self, copy=True): @@ -366,8 +366,7 @@ class Scatter2D(DataItem3D, ColormapMixIn, SymbolMixIn, self._cachedLinesIndices = None self._cachedTrianglesIndices = None - # Store data range info - ColormapMixIn._setRangeFromData(self, self.getValueData(copy=False)) + self._setColormappedData(self.getValueData(copy=False), copy=False) self._updateScene() diff --git a/silx/gui/plot3d/items/volume.py b/silx/gui/plot3d/items/volume.py index e0a2a1f..6c6562f 100644 --- a/silx/gui/plot3d/items/volume.py +++ b/silx/gui/plot3d/items/volume.py @@ -1,7 +1,7 @@ # coding: utf-8 # /*########################################################################## # -# Copyright (c) 2017-2019 European Synchrotron Radiation Facility +# Copyright (c) 2017-2020 European Synchrotron Radiation Facility # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -93,8 +93,12 @@ class CutPlane(Item3D, ColormapMixIn, InterpolationMixIn, PlaneMixIn): # Store data range info as 3-tuple of values self._dataRange = range_ - self._setRangeFromData( - None if self._dataRange is None else numpy.array(self._dataRange)) + if range_ is None: + range_ = None, None, None + self._setColormappedData(self._data, copy=False, + min_=range_[0], + minPositive=range_[1], + max_=range_[2]) self._updated(ItemChangedType.DATA) @@ -724,15 +728,20 @@ class ComplexIsosurface(Isosurface, ComplexMixIn, ColormapMixIn): def _syncDataWithParent(self): """Synchronize this instance data with that of its parent""" - if self.getComplexMode() != self.ComplexMode.NONE: - self._setRangeFromData(self.getColormappedData(copy=False)) - parent = self.parent() if parent is None: self._data = None else: self._data = parent.getData( mode=parent.getComplexMode(), copy=False) + + if parent is None or self.getComplexMode() == self.ComplexMode.NONE: + self._setColormappedData(None, copy=False) + else: + self._setColormappedData( + parent.getData(mode=self.getComplexMode(), copy=False), + copy=False) + self._updateScenePrimitive() def _parentChanged(self, event): @@ -741,38 +750,16 @@ class ComplexIsosurface(Isosurface, ComplexMixIn, ColormapMixIn): self._syncDataWithParent() super(ComplexIsosurface, self)._parentChanged(event) - def getColormappedData(self, copy=True): - """Return 3D dataset used to apply the colormap on the isosurface. - - This depends on :meth:`getComplexMode`. - - :param bool copy: - True (default) to get a copy, - False to get the internal data (DO NOT modify!) - :return: The data set (or None if not set) - :rtype: Union[numpy.ndarray,None] - """ - if self.getComplexMode() == self.ComplexMode.NONE: - return None - else: - parent = self.parent() - if parent is None: - return None - else: - return parent.getData(mode=self.getComplexMode(), copy=copy) - def _updated(self, event=None): """Handle update of the isosurface (and take care of mode change) :param ItemChangedType event: The kind of update """ - if (event == ItemChangedType.COMPLEX_MODE and - self.getComplexMode() != self.ComplexMode.NONE): - self._setRangeFromData(self.getColormappedData(copy=False)) + if event == ItemChangedType.COMPLEX_MODE: + self._syncDataWithParent() - if event in (ItemChangedType.COMPLEX_MODE, - ItemChangedType.COLORMAP, - Item3DChangedType.INTERPOLATION): + elif event in (ItemChangedType.COLORMAP, + Item3DChangedType.INTERPOLATION): self._updateScenePrimitive() super(ComplexIsosurface, self)._updated(event) diff --git a/silx/gui/plot3d/scene/function.py b/silx/gui/plot3d/scene/function.py index 7651f75..69a24dd 100644 --- a/silx/gui/plot3d/scene/function.py +++ b/silx/gui/plot3d/scene/function.py @@ -1,7 +1,7 @@ # coding: utf-8 # /*########################################################################## # -# Copyright (c) 2015-2019 European Synchrotron Radiation Facility +# Copyright (c) 2015-2020 European Synchrotron Radiation Facility # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -384,31 +384,44 @@ class DirectionalLight(event.Notifier, ProgramFunction): class Colormap(event.Notifier, ProgramFunction): _declTemplate = string.Template(""" - uniform struct { - sampler2D texture; - bool isLog; - float min; - float oneOverRange; - } cmap; + uniform sampler2D cmap_texture; + uniform int cmap_normalization; + uniform float cmap_parameter; + uniform float cmap_min; + uniform float cmap_oneOverRange; const float oneOverLog10 = 0.43429448190325176; vec4 colormap(float value) { - if (cmap.isLog) { /* Log10 mapping */ + if (cmap_normalization == 1) { /* Log10 mapping */ if (value > 0.0) { - value = clamp(cmap.oneOverRange * - (oneOverLog10 * log(value) - cmap.min), + value = clamp(cmap_oneOverRange * + (oneOverLog10 * log(value) - cmap_min), 0.0, 1.0); } else { value = 0.0; } + } else if (cmap_normalization == 2) { /* Sqrt mapping */ + if (value > 0.0) { + value = clamp(cmap_oneOverRange * (sqrt(value) - cmap_min), + 0.0, 1.0); + } else { + value = 0.0; + } + } else if (cmap_normalization == 3) { /*Gamma correction mapping*/ + value = pow( + clamp(cmap_oneOverRange * (value - cmap_min), 0.0, 1.0), + cmap_parameter); + } else if (cmap_normalization == 4) { /* arcsinh mapping */ + /* asinh = log(x + sqrt(x*x + 1) for compatibility with GLSL 1.20 */ + value = clamp(cmap_oneOverRange * (log(value + sqrt(value*value + 1.0)) - cmap_min), 0.0, 1.0); } else { /* Linear mapping */ - value = clamp(cmap.oneOverRange * (value - cmap.min), 0.0, 1.0); + value = clamp(cmap_oneOverRange * (value - cmap_min), 0.0, 1.0); } $discard - vec4 color = texture2D(cmap.texture, vec2(value, 0.5)); + vec4 color = texture2D(cmap_texture, vec2(value, 0.5)); return color; } """) @@ -421,18 +434,19 @@ class Colormap(event.Notifier, ProgramFunction): call = "colormap" - NORMS = 'linear', 'log' + NORMS = 'linear', 'log', 'sqrt', 'gamma', 'arcsinh' """Tuple of supported normalizations.""" _COLORMAP_TEXTURE_UNIT = 1 """Texture unit to use for storing the colormap""" - def __init__(self, colormap=None, norm='linear', range_=(1., 10.)): + def __init__(self, colormap=None, norm='linear', gamma=0., range_=(1., 10.)): """Shader function to apply a colormap to a value. :param colormap: RGB(A) color look-up table (default: gray) :param colormap: numpy.ndarray of numpy.uint8 of dimension Nx3 or Nx4 - :param str norm: Normalization to apply: 'linear' (default) or 'log'. + :param str norm: Normalization to apply: see :attr:`NORMS`. + :param float gamma: Gamma normalization parameter :param range_: Range of value to map to the colormap. :type range_: 2-tuple of float (begin, end). """ @@ -441,6 +455,7 @@ class Colormap(event.Notifier, ProgramFunction): # Init privates to default self._colormap = None self._norm = 'linear' + self._gamma = -1. self._range = 1., 10. self._displayValuesBelowMin = True @@ -456,6 +471,7 @@ class Colormap(event.Notifier, ProgramFunction): # Set to param values through properties to go through asserts self.colormap = colormap self.norm = norm + self.gamma = gamma self.range_ = range_ @property @@ -482,8 +498,8 @@ class Colormap(event.Notifier, ProgramFunction): def norm(self): """Normalization to use for colormap mapping. - Either 'linear' (the default) or 'log' for log10 mapping. - With 'log' normalization, values <= 0. are set to 1. (i.e. log == 0) + One of 'linear' (the default), 'log' for log10 mapping or 'sqrt'. + Invalid values (e.g., negative values with 'log' or 'sqrt') are mapped to 0. """ return self._norm @@ -492,11 +508,23 @@ class Colormap(event.Notifier, ProgramFunction): if norm != self._norm: assert norm in self.NORMS self._norm = norm - if norm == 'log': + if norm in ('log', 'sqrt'): self.range_ = self.range_ # To test for positive range_ self.notify() @property + def gamma(self): + """Gamma correction normalization parameter (float >= 0.)""" + return self._gamma + + @gamma.setter + def gamma(self, gamma): + if gamma != self._gamma: + assert gamma >= 0. + self._gamma = gamma + self.notify() + + @property def range_(self): """Range of values to map to the colormap. @@ -517,6 +545,10 @@ class Colormap(event.Notifier, ProgramFunction): "Log normalization and negative range: updating range.") minPos = numpy.finfo(numpy.float32).tiny range_ = max(range_[0], minPos), max(range_[1], minPos) + elif self.norm == 'sqrt' and (range_[0] < 0. or range_[1] < 0.): + _logger.warning( + "Sqrt normalization and negative range: updating range.") + range_ = max(range_[0], 0.), max(range_[1], 0.) if range_ != self._range: self._range = range_ @@ -549,16 +581,31 @@ class Colormap(event.Notifier, ProgramFunction): self._texture.bind() - gl.glUniform1i(program.uniforms['cmap.texture'], + gl.glUniform1i(program.uniforms['cmap_texture'], self._texture.texUnit) - gl.glUniform1i(program.uniforms['cmap.isLog'], self._norm == 'log') min_, max_ = self.range_ + param = 0. if self._norm == 'log': min_, max_ = numpy.log10(min_), numpy.log10(max_) - - gl.glUniform1f(program.uniforms['cmap.min'], min_) - gl.glUniform1f(program.uniforms['cmap.oneOverRange'], + normID = 1 + elif self._norm == 'sqrt': + min_, max_ = numpy.sqrt(min_), numpy.sqrt(max_) + normID = 2 + elif self._norm == 'gamma': + # Keep min_, max_ as is + param = self._gamma + normID = 3 + elif self._norm == 'arcsinh': + min_, max_ = numpy.arcsinh(min_), numpy.arcsinh(max_) + normID = 4 + else: # Linear + normID = 0 + + gl.glUniform1i(program.uniforms['cmap_normalization'], normID) + gl.glUniform1f(program.uniforms['cmap_parameter'], param) + gl.glUniform1f(program.uniforms['cmap_min'], min_) + gl.glUniform1f(program.uniforms['cmap_oneOverRange'], (1. / (max_ - min_)) if max_ != min_ else 0.) def prepareGL2(self, context): diff --git a/silx/gui/plot3d/tools/GroupPropertiesWidget.py b/silx/gui/plot3d/tools/GroupPropertiesWidget.py index 5b0bcdb..ec995a3 100644 --- a/silx/gui/plot3d/tools/GroupPropertiesWidget.py +++ b/silx/gui/plot3d/tools/GroupPropertiesWidget.py @@ -1,7 +1,7 @@ # coding: utf-8 # /*########################################################################## # -# Copyright (c) 2018 European Synchrotron Radiation Facility +# Copyright (c) 2018-2020 European Synchrotron Radiation Facility # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -161,6 +161,8 @@ class GroupPropertiesWidget(qt.QWidget): else: itemCmap.setColormapLUT(colormap.getColormapLUT()) itemCmap.setNormalization(colormap.getNormalization()) + itemCmap.setGammaNormalizationParameter( + colormap.getGammaNormalizationParameter()) itemCmap.setVRange(colormap.getVMin(), colormap.getVMax()) else: # Reset colormap diff --git a/silx/gui/plot3d/utils/mng.py b/silx/gui/plot3d/utils/mng.py index fe79a52..8049a2f 100644 --- a/silx/gui/plot3d/utils/mng.py +++ b/silx/gui/plot3d/utils/mng.py @@ -1,7 +1,7 @@ # coding: utf-8 # /*########################################################################## # -# Copyright (c) 2016-2017 European Synchrotron Radiation Facility +# Copyright (c) 2016-2020 European Synchrotron Radiation Facility # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -108,7 +108,7 @@ def convert(images, nb_images=0, fps=25): # Add filter 'None' before each scanline prepared_data = b'\x00' + b'\x00'.join( - line.tostring() for line in image) # TODO optimize that + line.tobytes() for line in image) # TODO optimize that compressed_data = zlib.compress(prepared_data, 8) # IDAT chunk: Payload |