Skip to content

Commit

Permalink
lazy import numpy
Browse files Browse the repository at this point in the history
  • Loading branch information
damusss committed Dec 1, 2024
1 parent 1c2e752 commit d3c30f5
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 33 deletions.
2 changes: 2 additions & 0 deletions docs/reST/ref/sndarray.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ Each sample is an 8-bit or 16-bit integer, depending on the data format. A
stereo sound file has two values per sample, while a mono sound file only has
one.

.. versionchanged:: 2.5.3 sndarray module is lazily loaded to avoid loading NumPy needlessly

.. function:: array

| :sl:`copy Sound samples into an array`
Expand Down
2 changes: 2 additions & 0 deletions docs/reST/ref/surfarray.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ pixels from the surface and any changes performed to the array will make changes
in the surface. As this last functions share memory with the surface, this one
will be locked during the lifetime of the array.

.. versionchanged:: 2.5.3 surfarray module is lazily loaded to avoid loading NumPy needlessly

.. function:: array2d

| :sl:`Copy pixels into a 2d array`
Expand Down
20 changes: 13 additions & 7 deletions src_py/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,15 +300,21 @@ def PixelArray(surface): # pylint: disable=unused-argument
except (ImportError, OSError):
scrap = MissingModule("scrap", urgent=0)

try:
import pygame.surfarray
except (ImportError, OSError):
import importlib.util as importlib_util
if importlib_util.find_spec("numpy") is None:
surfarray = MissingModule("surfarray", urgent=0)

try:
import pygame.sndarray
except (ImportError, OSError):
sndarray = MissingModule("sndarray", urgent=0)
else:
try:
import pygame.surfarray
except (ImportError, OSError):
surfarray = MissingModule("surfarray", urgent=0)

try:
import pygame.sndarray
except (ImportError, OSError):
sndarray = MissingModule("sndarray", urgent=0)
del importlib_util

try:
import pygame._debug
Expand Down
14 changes: 10 additions & 4 deletions src_py/sndarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@
"""

from pygame import mixer
import numpy

import warnings

numpy = None

__all__ = [
"array",
Expand All @@ -53,6 +53,11 @@
]


def _lazy_import():
global numpy
import numpy


def array(sound):
"""pygame.sndarray.array(Sound): return array
Expand All @@ -62,7 +67,8 @@ def array(sound):
array will always be in the format returned from
pygame.mixer.get_init().
"""

if numpy is None:
_lazy_import()
return numpy.array(sound, copy=True)


Expand All @@ -75,7 +81,8 @@ def samples(sound):
object. Modifying the array will change the Sound. The array will
always be in the format returned from pygame.mixer.get_init().
"""

if numpy is None:
_lazy_import()
return numpy.array(sound, copy=False)


Expand All @@ -88,7 +95,6 @@ def make_sound(array):
must be initialized and the array format must be similar to the mixer
audio format.
"""

return mixer.Sound(array=array)


Expand Down
78 changes: 56 additions & 22 deletions src_py/surfarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,25 +43,13 @@
map_array as pix_map_array,
make_surface as pix_make_surface,
)
import numpy
from numpy import (
array as numpy_array,
empty as numpy_empty,
uint32 as numpy_uint32,
ndarray as numpy_ndarray,
)

import warnings # will be removed in the future


# float96 not available on all numpy versions.
numpy_floats = [
getattr(numpy, type_name)
for type_name in "float32 float64 float96".split()
if hasattr(numpy, type_name)
]
# Added below due to deprecation of numpy.float. See pygame-ce issue #1440
numpy_floats.append(float)
numpy = None
numpy_floats = []


# Pixel sizes corresponding to NumPy supported integer sizes, and therefore
# permissible for 2D reference arrays.
Expand Down Expand Up @@ -93,6 +81,20 @@
]


def _lazy_import():
global numpy, numpy_floats
import numpy

# float96 not available on all numpy versions.
numpy_floats = [
getattr(numpy, type_name)
for type_name in "float32 float64 float96".split()
if hasattr(numpy, type_name)
]
# Added below due to deprecation of numpy.float. See pygame-ce issue #1440
numpy_floats.append(float)


def blit_array(surface, array):
"""pygame.surfarray.blit_array(Surface, array): return None
Expand All @@ -106,8 +108,10 @@ def blit_array(surface, array):
This function will temporarily lock the Surface as the new values are
copied.
"""
if isinstance(array, numpy_ndarray) and array.dtype in numpy_floats:
array = array.round(0).astype(numpy_uint32)
if numpy is None:
_lazy_import()
if isinstance(array, numpy.ndarray) and array.dtype in numpy_floats:
array = array.round(0).astype(numpy.uint32)
return array_to_surface(surface, array)


Expand All @@ -119,8 +123,10 @@ def make_surface(array):
Create a new Surface that best resembles the data and format on the
array. The array can be 2D or 3D with any sized integer values.
"""
if isinstance(array, numpy_ndarray) and array.dtype in numpy_floats:
array = array.round(0).astype(numpy_uint32)
if numpy is None:
_lazy_import()
if isinstance(array, numpy.ndarray) and array.dtype in numpy_floats:
array = array.round(0).astype(numpy.uint32)
return pix_make_surface(array)


Expand All @@ -137,6 +143,8 @@ def array2d(surface):
(see the Surface.lock - lock the Surface memory for pixel access
method).
"""
if numpy is None:
_lazy_import()
bpp = surface.get_bytesize()
try:
dtype = (numpy.uint8, numpy.uint16, numpy.int32, numpy.int32)[bpp - 1]
Expand Down Expand Up @@ -164,10 +172,12 @@ def pixels2d(surface):
the array (see the Surface.lock - lock the Surface memory for pixel
access method).
"""
if numpy is None:
_lazy_import()
if surface.get_bitsize() not in _pixel2d_bitdepths:
raise ValueError("unsupported bit depth for 2D reference array")
try:
return numpy_array(surface.get_view("2"), copy=False)
return numpy.array(surface.get_view("2"), copy=False)
except (ValueError, TypeError):
raise ValueError(
f"bit depth {surface.get_bitsize()} unsupported for 2D reference array"
Expand All @@ -187,6 +197,8 @@ def array3d(surface):
(see the Surface.lock - lock the Surface memory for pixel access
method).
"""
if numpy is None:
_lazy_import()
width, height = surface.get_size()
array = numpy.empty((width, height, 3), numpy.uint8)
surface_to_array(array, surface)
Expand All @@ -209,7 +221,9 @@ def pixels3d(surface):
the array (see the Surface.lock - lock the Surface memory for pixel
access method).
"""
return numpy_array(surface.get_view("3"), copy=False)
if numpy is None:
_lazy_import()
return numpy.array(surface.get_view("3"), copy=False)


def array_alpha(surface):
Expand All @@ -226,6 +240,8 @@ def array_alpha(surface):
(see the Surface.lock - lock the Surface memory for pixel access
method).
"""
if numpy is None:
_lazy_import()
size = surface.get_size()
array = numpy.empty(size, numpy.uint8)
surface_to_array(array, surface, "A")
Expand All @@ -247,6 +263,8 @@ def pixels_alpha(surface):
The Surface this array references will remain locked for the
lifetime of the array.
"""
if numpy is None:
_lazy_import()
return numpy.array(surface.get_view("A"), copy=False)


Expand All @@ -264,6 +282,8 @@ def pixels_red(surface):
The Surface this array references will remain locked for the
lifetime of the array.
"""
if numpy is None:
_lazy_import()
return numpy.array(surface.get_view("R"), copy=False)


Expand All @@ -279,6 +299,8 @@ def array_red(surface):
(see the Surface.lock - lock the Surface memory for pixel access
method).
"""
if numpy is None:
_lazy_import()
size = surface.get_size()
array = numpy.empty(size, numpy.uint8)
surface_to_array(array, surface, "R")
Expand All @@ -299,6 +321,8 @@ def pixels_green(surface):
The Surface this array references will remain locked for the
lifetime of the array.
"""
if numpy is None:
_lazy_import()
return numpy.array(surface.get_view("G"), copy=False)


Expand All @@ -314,6 +338,8 @@ def array_green(surface):
(see the Surface.lock - lock the Surface memory for pixel access
method).
"""
if numpy is None:
_lazy_import()
size = surface.get_size()
array = numpy.empty(size, numpy.uint8)
surface_to_array(array, surface, "G")
Expand All @@ -334,6 +360,8 @@ def pixels_blue(surface):
The Surface this array references will remain locked for the
lifetime of the array.
"""
if numpy is None:
_lazy_import()
return numpy.array(surface.get_view("B"), copy=False)


Expand All @@ -349,6 +377,8 @@ def array_blue(surface):
(see the Surface.lock - lock the Surface memory for pixel access
method).
"""
if numpy is None:
_lazy_import()
size = surface.get_size()
array = numpy.empty(size, numpy.uint8)
surface_to_array(array, surface, "B")
Expand All @@ -370,6 +400,8 @@ def array_colorkey(surface):
This function will temporarily lock the Surface as pixels are
copied.
"""
if numpy is None:
_lazy_import()
size = surface.get_size()
array = numpy.empty(size, numpy.uint8)
surface_to_array(array, surface, "C")
Expand All @@ -390,12 +422,14 @@ def map_array(surface, array):
colors). The array shape is limited to eleven dimensions maximum,
including the three element minor axis.
"""
if numpy is None:
_lazy_import()
if array.ndim == 0:
raise ValueError("array must have at least 1 dimension")
shape = array.shape
if shape[-1] != 3:
raise ValueError("array must be a 3d array of 3-value color data")
target = numpy_empty(shape[:-1], numpy.int32)
target = numpy.empty(shape[:-1], numpy.int32)
pix_map_array(target, array, surface)
return target

Expand Down

0 comments on commit d3c30f5

Please sign in to comment.