Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NumPy 2 compatibility for tests and tools #1315

Merged
merged 22 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .github/workflows/headless-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip setuptools wheel
python -m pip install numpy "vtk<9.3" pillow pytest traitsui
python -m pip install numpy "vtk<9.3" pillow pytest pytest-timeout traitsui
- name: Install mayavi and tvtk
run: python -m pip install -v .
- name: Test tvtk package
run: pytest -v --pyargs tvtk
run: python -m pip install --no-build-isolation -v .
- name: Test Mayavi package
run: pytest -v --pyargs mayavi
run: pytest -v --timeout=10 --pyargs mayavi
- name: Test tvtk package
run: pytest -v --timeout=60 --pyargs tvtk
25 changes: 20 additions & 5 deletions .github/workflows/run-mayavi-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,22 @@ jobs:
qt-api: 'pyside6'
os: macos-13
vtk: 'vtk<9.3'
# Some old NumPys
- python-version: '3.12'
qt-api: 'pyside6'
os: ubuntu-latest
vtk: 'vtk>=9.3'
numpy: 'numpy==1.26.4'
- python-version: '3.12'
qt-api: 'pyside6'
os: windows-latest
vtk: 'vtk>=9.3'
numpy: 'numpy==2.0.2'
- python-version: '3.12'
qt-api: 'pyside6'
os: windows-latest
vtk: 'vtk>=9.3'
numpy: 'numpy==1.26.4'
fail-fast: false
defaults:
run:
Expand All @@ -69,14 +85,13 @@ jobs:
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
shell: bash
run: |
set -exo pipefail
python -m pip install --upgrade pip setuptools wheel
python -m pip install --upgrade "${{ matrix.qt-api }}" numpy "${{ matrix.vtk }}" pillow pytest traits traitsui
python -m pip install --upgrade "${{ matrix.qt-api }}" "${{ matrix.numpy || 'numpy' }}" "${{ matrix.vtk }}" pillow pytest pytest-timeout traits traitsui --only-binary="numpy,vtk"
- name: Install mayavi and tvtk
run: python -um pip install -ve .[app]
run: python -um pip install --no-build-isolation -ve .[app]
- name: Test Mayavi package
run: pytest -v mayavi
run: pytest -v --timeout=10 mayavi
- name: Test tvtk package
run: pytest -sv tvtk
run: pytest -sv --timeout=60 tvtk
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ docs/build/

docs/html.zip
tvtk/tvtk_classes.zip
tvtk/tvtk_classes/
mayavi/images/m2_about.jpg
2 changes: 2 additions & 0 deletions mayavi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@

__requires__ = [
'apptools',
'configobj',
'envisage',
'numpy',
'pyface>=6.1.1',
'pygments', # This is only needed for the Qt backend but we add it anyway.
'traits>=6.0.0',
'traitsui>=7.0.0',
'packaging',
'importlib_resources; python_version<"3.11"',
'vtk'
]

Expand Down
10 changes: 4 additions & 6 deletions mayavi/preferences/preference_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

# Standard library imports
from os.path import join
import pkg_resources
import importlib.resources

# Enthought library imports.
from traits.etsconfig.api import ETSConfig
Expand Down Expand Up @@ -106,12 +106,11 @@ def _load_preferences(self):
for pkg in ('mayavi.preferences',
'tvtk.plugins.scene'):
pref = 'preferences.ini'
pref_file = pkg_resources.resource_stream(pkg, pref)

pref_file = importlib.resources.files(pkg).joinpath(pref)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pkg_resources is deprecated, switch to importlib_resources

preferences = self.preferences
default = preferences.node('default/')
default.load(pref_file)
pref_file.close()
with open(pref_file, 'rb') as fid:
default.load(fid)
finally:
# Set back the application home.
ETSConfig.application_home = app_home
Expand All @@ -126,4 +125,3 @@ def _preferences_changed(self, preferences):
# A Global preference manager that all other modules can use.

preference_manager = PreferenceManager()

6 changes: 3 additions & 3 deletions mayavi/tests/test_csv_sniff.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import tempfile
from unittest import SkipTest

from numpy import array, ndarray
from numpy import array, ndarray, isnan

from mayavi.tools.data_wizards.csv_sniff import \
Sniff, loadtxt, loadtxt_unknown, array2dict
Expand All @@ -33,8 +33,8 @@ def assertAllClose(self, x, y):

def assertClose(self, a, b):
if isinstance(a, (int, float)):
if repr(a) == 'nan':
self.assertTrue(repr(b) == 'nan')
if isnan(a):
self.assertTrue(isnan(b), '%r != %r' % (a ,b))
else:
self.assertTrue(abs(a - b) < 1e-6 * max(1, abs(a)),
'%r != %r %r' % (a, b, abs(a - b)))
Expand Down
2 changes: 1 addition & 1 deletion mayavi/tests/test_mlab_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ def test_set(self):
self.check_traits()
self.check_dataset()

def test_strange_shape(self):
def test_basic_strange_shape(self):
" Test the MGlyphSource with strange shapes for the arguments "
x, y, z, v, s, src = self.get_data()
x = y = z = v = s = 0
Expand Down
2 changes: 1 addition & 1 deletion mayavi/tools/data_wizards/loadtxt.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def _getconv(dtype):
return lambda x: int(float(x))
elif issubclass(typ, np.floating):
return float
elif issubclass(typ, np.complex_):
elif issubclass(typ, np.complexfloating):
return complex
else:
return str
Expand Down
2 changes: 2 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@ addopts =
filterwarnings =
# Currently unsatisfiable
ignore:Workbench will be moved from pyface:PendingDeprecationWarning
# Should be fixed in traits
ignore: module 'sre_.+' is deprecated:DeprecationWarning
# We call deprecated methods and classes in our tests, and there are many variants for how the parentheticals are formatted
ignore:Call to deprecated .*. \((This|Use|Please|Deprecated|Deprecating|Removed|Part|no|renamed) .*\) -- Deprecated since version.*:DeprecationWarning
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ def example_files(self):
mlab_ref_dir = join(DEFAULT_INPUT_DIR, 'mayavi', 'auto')

source_path = join('examples', 'mayavi')
sources = '(\.py)|(\.rst)$'
excluded_dirs = '^\.'
sources = r'(\.py)|(\.rst)$'
excluded_dirs = r'^\.'
target_path = mlab_ref_dir
target_time = self.latest_modified(target_path,
ignore_dirs=excluded_dirs)[0]
Expand Down
4 changes: 2 additions & 2 deletions tvtk/array_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,9 +197,9 @@ def get_vtk_array_type(numeric_array_type):
numpy.dtype(ULONG_TYPE_CODE): vtkConstants.VTK_UNSIGNED_LONG,
numpy.dtype(LONG_TYPE_CODE): vtkConstants.VTK_LONG,
}
for t in _extra:
for t, val in _extra.items():
if t not in _arr_vtk:
_arr_vtk[t] = _extra[t]
_arr_vtk[t] = val

try:
return _arr_vtk[numeric_array_type]
Expand Down
7 changes: 5 additions & 2 deletions tvtk/code_gen.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
"""This module generates tvtk (Traited VTK) classes from the VTK-Python API.

This can be evoked for example by:
This can be evoked for example by running from the ``mayavi`` root:

..code-block:: console

$ python -ic "from tvtk.code_gen import main; main()" -szv
$ python -m tvtk.code_gen -szvno $PWD/tvtk

On failures you can then for example do ``import pdb; pdb.pm()`` to do
post-mortem debugging.

Exceptions to behaviors based on VTK versions and bugs etc. live in ``wrapper_gen.py``
and ``tvtk_parser.py``.
"""
# Author: Prabhu Ramachandran
# Copyright (c) 2004-2020, Enthought, Inc.
Expand Down
6 changes: 5 additions & 1 deletion tvtk/tests/test_array_ext.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@
# Copyright (c) 2005, Enthought, Inc.
# License: BSD Style.

import pytest
import unittest

import numpy

from tvtk.array_handler import ID_TYPE_CODE, set_id_type_array_py
from tvtk.array_ext import set_id_type_array
try:
from tvtk.array_ext import set_id_type_array
except ModuleNotFoundError: # not compiled
pytest.skip("array_ext not found", allow_module_level=True)


class TestArrayExt(unittest.TestCase):
Expand Down
28 changes: 15 additions & 13 deletions tvtk/tests/test_array_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ def mysum(arr):
class TestArrayHandler(unittest.TestCase):
def _check_arrays(self, arr, vtk_arr):
self.assertEqual(vtk_arr.GetNumberOfTuples(), len(arr))
msg = f"\n{vtk_arr}"
if len(arr.shape) == 2:
dim1 = arr.shape[1]
self.assertEqual(vtk_arr.GetNumberOfComponents(), dim1)
Expand All @@ -45,8 +46,7 @@ def _check_arrays(self, arr, vtk_arr):
self.assertEqual(chr(int(vtk_arr.GetTuple1(i))), arr[i])
else:
for i in range(len(arr)):
self.assertEqual(vtk_arr.GetTuple1(i), arr[i])

self.assertEqual(vtk_arr.GetTuple1(i), arr[i], msg=msg)

def test_array2vtk(self):
"""Test Numeric array to VTK array conversion and vice-versa."""
Expand All @@ -63,6 +63,7 @@ def test_array2vtk(self):
t_z.append(numpy.array([-2147483648, 0, 2147483647], numpy.int32))
t_z.append(numpy.array([
-9223372036854775808, 0, 9223372036854775807], numpy.int64))
assert t_z[-1][0] == -9223372036854775808
t_z.append(numpy.array([0, 255], numpy.uint8))
t_z.append(numpy.array([0, 65535], numpy.uint16))
t_z.append(numpy.array([0, 4294967295], numpy.uint32))
Expand Down Expand Up @@ -160,13 +161,16 @@ def test_array2vtk(self):
self.assertEqual(vtk_arr.GetValue(2), 0)
self.assertEqual(vtk_arr.GetValue(3), 1)

# Make sure the code at least runs for all the non-complex
# numerical dtypes in numpy.
float_types = [x for x in numpy.sctypes['float']
if x().dtype.name not in ('float16', 'float128')]
for dtype in (numpy.sctypes['int'] + numpy.sctypes['uint'] +
float_types):
array_handler.array2vtk(numpy.zeros((1,), dtype=dtype))
# Make sure the code at least runs for all
# numerical dtypes in numpy
# except for half, longdouble and complexfloating
int_types = ['byte', 'short', 'int', 'intc', 'int_', 'long', 'longlong']
uint_types = ['ubyte', 'ushort', 'uintc', 'uint', 'ulong',
'ulonglong']
float_types = ['single', 'double']
for dtype in int_types + uint_types + float_types:
array_handler.array2vtk(numpy.zeros((1,),
dtype=numpy.dtype(dtype)))

def test_arr2cell_array(self):
"""Test Numeric array to vtkCellArray conversion."""
Expand Down Expand Up @@ -201,17 +205,15 @@ def test_arr2cell_array(self):
cells = array_handler.array2vtkCellArray(a)
arr = array_handler.vtk2array(cells.GetData())
expect = numpy.array([3, 0, 1, 2]*3, int)
self.assertEqual(numpy.alltrue(numpy.equal(arr, expect)),
True)
self.assertTrue(numpy.all(numpy.equal(arr, expect)))
self.assertEqual(cells.GetNumberOfCells(), N)

# Test if a list of Numeric arrays of different cell lengths works.
l_a = [a[:,:1], a, a[:2,:2]]
cells = array_handler.array2vtkCellArray(l_a)
arr = array_handler.vtk2array(cells.GetData())
expect = numpy.array([1, 0]*3 + [3, 0, 1, 2]*3 + [2, 0,1]*2, int)
self.assertEqual(numpy.alltrue(numpy.equal(arr, expect)),
True)
self.assertTrue(numpy.all(numpy.equal(arr, expect)))
self.assertEqual(cells.GetNumberOfCells(), N*2 + 2)

# This should not take a long while. This merely tests if a
Expand Down
18 changes: 11 additions & 7 deletions tvtk/wrapper_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,10 @@ def _gen_state_methods(self, klass, out):

if not vtk_val:
default = self._reform_name(meths[m][0][0])
# Weirdness on NumPy 2.1 and vtk >= 9.3 that this does not show up as
# an option and creates problems
if klass.__name__ == "vtkPoints" and m == "DataType" and sys.platform == "win32":
d["int32"] = vtk.VTK_ID_TYPE
Comment on lines +682 to +685
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This took a really long time for me to find. No idea why but on NumPy 2.1 and VTK 9.3+ vtkPoints get created with datatype 12 i.e. VTK_ID_TYPE and it wasn't showing up in the RevPrefixMap for Points, causing a cryptic error:

Traceback
____________ TestMTriangularMeshSource.test_reset_changes_pipeline ____________
tvtk\tvtk_base.py:235: in validate
    n = len(value)
E   TypeError: object of type 'int' has no len()

During handling of the above exception, another exception occurred:
mayavi\tests\test_mlab_source.py:991: in test_reset_changes_pipeline
    obj.mlab_source.reset(x=x, y=y, z=z, triangles=triangles, scalars=z)
mayavi\tools\sources.py:832: in reset
    pd.trait_set(points=points)
C:\hostedtoolcache\windows\Python\3.9.13\x64\lib\site-packages\traits\has_traits.py:1509: in trait_set
    setattr(self, name, value)
tvtk_classes\point_set.py:122: in _set_points
    ???
tvtk_classes\point_set.py:120: in _get_points
    ???
tvtk_classes\tvtk_helper.py:65: in wrap_vtk
    ???
tvtk_classes\points.py:45: in __init__
    ???
tvtk\tvtk_base.py:432: in __init__
    self.update_traits()
tvtk\tvtk_base.py:585: in update_traits
    setattr(self, name, val)
tvtk\tvtk_base.py:247: in validate
    self.error(object, name, value)
C:\hostedtoolcache\windows\Python\3.9.13\x64\lib\site-packages\traits\base_trait_handler.py:74: in error
    raise TraitError(
E   traits.trait_errors.TraitError: The 'data_type' trait of a Points instance must be 'bit' or 'char' or 'double' or 'float' or 'int' or 'long' or 'short' or 'unsigned_char' or 'unsigned_int' or 'unsigned_long' or 'unsigned_short' (or any unique prefix) or 1 or 10 or 11 or 2 or 3 or 4 or 5 or 6 or 7 or 8 or 9, but a value of 12 <class 'int'> was specified.

This fixes it. Not 100% sure it's correct but tests at least pass 🤷

if extra_val is None:
t_def = """tvtk_base.RevPrefixMap(%(d)s, default_value='%(default)s')""" % locals()
elif hasattr(extra_val, '__iter__'):
Expand Down Expand Up @@ -1591,22 +1595,22 @@ def _write_trait_with_range(self, klass, out, vtk_attr_name):
# the code for this trait,
# i.e. getattr(self, name_of_method)(...)
special_traits = {
'[a-zA-Z0-9]+\.Output$': (
r'[a-zA-Z0-9]+\.Output$': (
False, False, '_write_any_output'),
'[a-zA-Z0-9]+\.Source$': (
r'[a-zA-Z0-9]+\.Source$': (
False, False, '_write_any_source'),
'[a-zA-Z0-9]+\.ScalarType$': (
r'[a-zA-Z0-9]+\.ScalarType$': (
False, False, '_write_any_scalar_type'),

# In VTK > 4.5, Set/GetInput have multiple signatures
'[a-zA-Z0-9]+\.Input$': (
r'[a-zA-Z0-9]+\.Input$': (
False, False, '_write_any_input'),

'[a-zA-Z0-9]+\.InputConnection$': (
r'[a-zA-Z0-9]+\.InputConnection$': (
False, False, '_write_any_input_connection'),
'[a-zA-Z0-9\.]+FileName$': (
r'[a-zA-Z0-9\.]+FileName$': (
True, False, '_write_any_something_file_name'),
'[a-zA-Z0-9\.]+FilePrefix$': (
r'[a-zA-Z0-9\.]+FilePrefix$': (
True, False, '_write_any_something_file_prefix'),
'vtkImageReader2.HeaderSize$': (
True, False, '_write_image_reader2_header_size'),
Expand Down