Skip to content

Commit c0353dd

Browse files
committed
First complete version
1 parent 8730f3b commit c0353dd

File tree

11 files changed

+1057
-905
lines changed

11 files changed

+1057
-905
lines changed
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
name: Lint and Tests
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
- release/*
8+
pull_request:
9+
branches:
10+
- master
11+
- release/*
12+
13+
jobs:
14+
15+
###########
16+
# LINTING #
17+
###########
18+
linting:
19+
runs-on: ubuntu-latest
20+
steps:
21+
- uses: actions/checkout@v2
22+
- name: Set up Python
23+
uses: actions/setup-python@v2
24+
with:
25+
python-version: 3.9
26+
# Cache pip
27+
- uses: actions/cache@v2
28+
with:
29+
path: ~/.cache/pip
30+
key: ${{ runner.os }}-pip
31+
restore-keys: ${{ runner.os }}-pip
32+
- name: Install dependencies
33+
run: |
34+
python -m pip install --upgrade pip
35+
python -m pip install mypy black isort
36+
- name: Python Code Quality and Lint
37+
uses: abey79/python-lint@master
38+
with:
39+
python-root-list: "vnoise tests"
40+
use-pylint: false
41+
use-pycodestyle: false
42+
use-flake8: false
43+
use-black: true
44+
extra-black-options: --diff
45+
use-mypy: true
46+
use-isort: true
47+
48+
#########
49+
# TESTS #
50+
#########
51+
tests:
52+
needs: linting
53+
strategy:
54+
fail-fast: true
55+
matrix:
56+
python-version: [3.7, 3.8, 3.9]
57+
os: [ubuntu-latest, macos-latest, windows-latest]
58+
defaults:
59+
run:
60+
shell: bash
61+
runs-on: ${{ matrix.os }}
62+
63+
steps:
64+
- uses: actions/checkout@v2
65+
- name: Set up Python ${{ matrix.python-version }}
66+
uses: actions/setup-python@v2
67+
with:
68+
python-version: ${{ matrix.python-version }}
69+
- name: Install dependencies
70+
run: |
71+
pip install .
72+
pip install -r dev-requirements.txt
73+
- name: Pytest
74+
run: |
75+
pytest

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Copyright (c) 2021 Antoine Beyeler, David Olsen
1+
Copyright (c) 2021 Antoine Beyeler, Tatarize
22
Copyright (c) 2008 Casey Duncan
33

44
Permission is hereby granted, free of charge, to any person obtaining a copy

README.md

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,84 @@
1-
# vnoise
1+
# *vnoise*
22

3-
Vectorized, pure-Python Perlin noise library.
3+
*vnoise* is a pure-Python, Numpy-based, vectorized port of the [*noise*](https://github.com/caseman/noise) library. It
4+
currently implements the Perlin noise functions in 1D, 2D and 3D version (*noise* also implements simplex noise).
5+
6+
## Why?
7+
8+
*vnoise* was started because the original *noise* library is no longer supported and binaries for recent versions of
9+
Python are unavailable, making it hard to install for non-technical users. *vnoise* does not suffer from the same issue
10+
since it is written in pure Python.
11+
12+
## Is *vnoise* slow?
13+
14+
For scalar input (e.g. when computing noise values one at a time), yes (~300-2000x slower depending on the conditions).
15+
16+
*vnoise* deals with this by offering a vectorized API to compute large numbers of noise values in a single call. Since is
17+
uses Numpy internally, its performance can match or exceed the original library (0.3-4x faster depending on the conditions).
18+
19+
## Installing
20+
21+
```
22+
$ pip install vnoise
23+
```
24+
25+
## Using *vnoise*
26+
27+
Basic example:
28+
29+
```python
30+
>>> import vnoise
31+
>>> noise = vnoise.Noise()
32+
>>> noise.noise1(0.5)
33+
0.0
34+
>>> noise.noise1(0.1)
35+
0.09144000000000001
36+
>>> noise.noise2(0.1, 0.3)
37+
0.09046282464000001
38+
>>> noise.noise3(0.1, 0.3, 0.7)
39+
0.27788822071249925
40+
```
41+
42+
The `noiseX()` functions also accept sequences as arguments:
43+
44+
```python
45+
>>> import numpy as np
46+
>>> noise.noise2([0.1, 0.2, 0.3], np.linspace(0.5, 0.8, 10), grid_mode=True)
47+
array([[0.0893 , 0.08919374, 0.08912713, 0.08910291, 0.08912241,
48+
0.08918551, 0.08929079, 0.08943552, 0.08961588, 0.08982716],
49+
[0.1276 , 0.126881 , 0.12643032, 0.12626645, 0.12639833,
50+
0.12682535, 0.12753768, 0.12851697, 0.12973738, 0.13116695],
51+
[0.09615 , 0.09412557, 0.09285663, 0.09239525, 0.09276657,
52+
0.09396889, 0.09597453, 0.09873182, 0.10216802, 0.10619312]])
53+
```
54+
55+
With `grid_mode=True` (default value), a noise value is computed for every combination of the input value. In this case, the length of
56+
the first input is 3, and the length of the second input is 10. The result is an array with shape `(3, 10)`.
57+
58+
If `grid_mode=False`, all the input must have the same length, and the result has the same shape:
59+
60+
```python
61+
>>> noise.noise2(np.linspace(0.1, 0.3, 30), np.linspace(10, 10.5, 30), grid_mode=False)
62+
array([0.099144 , 0.12303124, 0.14685425, 0.17057388, 0.194133 ,
63+
0.21745892, 0.24046583, 0.26305716, 0.28512793, 0.30656706,
64+
0.32725957, 0.34708877, 0.36593838, 0.38369451, 0.40024759,
65+
0.41549419, 0.42933872, 0.44169499, 0.45248762, 0.46165335,
66+
0.46914216, 0.4749182 , 0.47896062, 0.48126415, 0.48183955,
67+
0.4807138 , 0.47793023, 0.47354831, 0.46764335, 0.460306 ])
68+
```
69+
70+
A seed value can be specified when creating the `Noise` class or afterward using the `seed()` function:
71+
72+
```python
73+
>>> noise = vnoise.Noise(4)
74+
>>> noise.seed(5)
75+
```
76+
77+
## License
78+
79+
This code is available under the MIT license, see [LICENSE](LICENSE).
80+
81+
82+
## Acknowledgments
83+
84+
This code is based on Casey Duncan's [noise](https://github.com/caseman/noise) library. The port was done with the help of [@tatarize](https://github.com/tatarize)

mypy.ini

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[mypy]
2+
ignore_missing_imports = True
3+
allow_redefinition = True
4+
disallow_incomplete_defs = True
5+
files = vnoise,tests
6+

setup.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
setup(
1010
name="vnoise",
11-
version="0.1.0",
11+
version="0.1.0a0",
1212
description="Vectorized, pure-Python Perlin noise library",
1313
long_description=readme,
1414
long_description_content_type="text/markdown",
@@ -17,7 +17,9 @@
1717
url="https://github.com/vpype/vnoise",
1818
license=license_file,
1919
packages=["vnoise"],
20+
python_requires=">=3.6",
2021
install_requires=[
21-
"numpy>=1.20.0",
22+
"numpy>=1.19",
23+
"setuptools",
2224
],
2325
)

tests/conftest.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import pytest
2+
from vnoise import Noise
3+
4+
5+
@pytest.fixture(scope="module")
6+
def noise():
7+
return Noise()

tests/test_noise.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
from numbers import Number
2+
3+
import numpy as np
4+
import pytest
5+
from vnoise import Noise
6+
7+
8+
def test_noise1_dimensions(noise):
9+
assert isinstance(noise.noise1(1.5), float)
10+
assert noise.noise1([1]).shape == (1,)
11+
assert noise.noise1([1, 3, 4]).shape == (3,)
12+
13+
14+
def test_noise2_dimensions(noise):
15+
# scalar
16+
assert isinstance(noise.noise2(1.5, 1.0, grid_mode=True), float)
17+
assert isinstance(noise.noise2(1.5, 1.0, grid_mode=False), float)
18+
19+
# grid mode on
20+
assert noise.noise2([0, 1], [2, 3, 4]).shape == (2, 3)
21+
assert noise.noise2([1], [2, 3, 4]).shape == (1, 3)
22+
assert noise.noise2([0, 1], 4).shape == (2,)
23+
assert noise.noise2(4, [0, 1]).shape == (2,)
24+
25+
# grid mode off
26+
assert noise.noise2([0, 1, 2], [3, 4, 5], grid_mode=False).shape == (3,)
27+
assert noise.noise2([0], [3], grid_mode=False).shape == (1,)
28+
29+
with pytest.raises(ValueError):
30+
noise.noise2([0, 1, 2], [3, 4, 5, 6], grid_mode=False)
31+
32+
33+
def test_noise3_dimensions(noise):
34+
# scalar
35+
assert isinstance(noise.noise3(1.5, 1.0, 1.5, grid_mode=True), float)
36+
assert isinstance(noise.noise3(1.5, 1.0, 1.5, grid_mode=False), float)
37+
38+
# grid mode on
39+
assert noise.noise3([0, 1], [2, 3, 4], 4).shape == (2, 3)
40+
assert noise.noise3([0, 1], [2, 3, 4], [5, 6, 7, 8]).shape == (2, 3, 4)
41+
assert noise.noise3([1], [2, 3, 4], [5, 6, 7, 8]).shape == (1, 3, 4)
42+
assert noise.noise3([0, 1], 4, [5, 6, 7, 8]).shape == (2, 4)
43+
assert noise.noise3(4, 4, [5, 6, 7, 8]).shape == (4,)
44+
assert noise.noise3(4, [0, 1], [5, 6, 7, 8]).shape == (2, 4)
45+
46+
# grid mode off
47+
assert noise.noise3([0, 1, 2], [3, 4, 5], [5, 6, 7], grid_mode=False).shape == (3,)
48+
assert noise.noise3([0], [3], [5], grid_mode=False).shape == (1,)
49+
50+
with pytest.raises(ValueError):
51+
noise.noise3([0, 1, 2], [3, 4, 5, 6], [5, 6, 7], grid_mode=False)
52+
53+
54+
@pytest.mark.parametrize("seed", [None, 0, 1])
55+
@pytest.mark.parametrize("octaves", list(range(1, 9)))
56+
@pytest.mark.parametrize("x", [10, [1], 0.5, range(5), np.linspace(0, 10, 100)])
57+
def test_noise1_noise2(seed, x, octaves):
58+
noise = Noise(seed)
59+
y = 0 if isinstance(x, Number) else np.zeros_like(x)
60+
61+
n2 = noise.noise2(x, y, octaves=octaves, grid_mode=False)
62+
n1 = noise.noise1(x, octaves=octaves)
63+
assert np.all(n1 == n2)
64+
65+
66+
@pytest.mark.parametrize("seed", [None, 0, 1])
67+
@pytest.mark.parametrize("octaves", list(range(1, 9)))
68+
@pytest.mark.parametrize(
69+
["x", "y", "grid_mode"],
70+
[
71+
(0, 10, False),
72+
(0.0, 15.0, False),
73+
(range(5), 0, True),
74+
(np.linspace(0, 10, 100), np.linspace(10, 20, 100), False),
75+
],
76+
)
77+
def test_noise2_noise3(seed, x, y, grid_mode, octaves):
78+
noise = Noise(seed)
79+
z = 0 if grid_mode else np.zeros_like(x)
80+
assert np.all(
81+
noise.noise2(x, y, octaves=octaves, grid_mode=grid_mode)
82+
== noise.noise3(x, y, z, octaves=octaves, grid_mode=grid_mode)
83+
)

tests/test_ported.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
"""This test suite is a port of Casey Duncan original test suite
2+
"""
3+
4+
5+
def test_perlin_1d_range(noise):
6+
for i in range(-10000, 10000):
7+
x = i * 0.49
8+
n = noise.noise1(x)
9+
assert -1.0 <= n <= 1.0
10+
11+
12+
def test_perlin_1d_octaves_range(noise):
13+
for i in range(-1000, 1000):
14+
for o in range(10):
15+
x = i * 0.49
16+
n = noise.noise1(x, octaves=o + 1)
17+
assert -1.0 <= n <= 1.0
18+
19+
20+
def test_perlin_1d_base(noise):
21+
assert noise.noise1(0.5) == noise.noise1(0.5, base=0)
22+
assert noise.noise1(0.5) != noise.noise1(0.5, base=5)
23+
assert noise.noise1(0.5, base=5) != noise.noise1(0.5, base=1)
24+
25+
26+
def test_perlin_2d_range(noise):
27+
for i in range(-10000, 10000):
28+
x = i * 0.49
29+
y = -i * 0.67
30+
n = noise.noise2(x, y)
31+
assert -1.0 <= n <= 1.0
32+
33+
34+
def test_perlin_2d_octaves_range(noise):
35+
for i in range(-1000, 1000):
36+
for o in range(10):
37+
x = -i * 0.49
38+
y = i * 0.67
39+
n = noise.noise2(x, y, octaves=o + 1)
40+
assert -1.0 <= n <= 1.0
41+
42+
43+
def test_perlin_2d_base(noise):
44+
x, y = 0.73, 0.27
45+
assert noise.noise2(x, y) == noise.noise2(x, y, base=0)
46+
assert noise.noise2(x, y) != noise.noise2(x, y, base=5)
47+
assert noise.noise2(x, y, base=5) != noise.noise2(x, y, base=1)
48+
49+
50+
def test_perlin_3d_range(noise):
51+
for i in range(-10000, 10000):
52+
x = -i * 0.49
53+
y = i * 0.67
54+
z = -i * 0.727
55+
n = noise.noise3(x, y, z)
56+
assert -1.0 <= n <= 1.0
57+
58+
59+
def test_perlin_3d_octaves_range(noise):
60+
for i in range(-1000, 1000):
61+
x = i * 0.22
62+
y = -i * 0.77
63+
z = -i * 0.17
64+
for o in range(10):
65+
n = noise.noise3(x, y, z, octaves=o + 1)
66+
assert -1.0 <= n <= 1.0
67+
68+
69+
def test_perlin_3d_base(noise):
70+
x, y, z = 0.1, 0.7, 0.33
71+
assert noise.noise3(x, y, z) == noise.noise3(x, y, z, base=0)
72+
assert noise.noise3(x, y, z) != noise.noise3(x, y, z, base=5)
73+
assert noise.noise3(x, y, z, base=5) != noise.noise3(x, y, z, base=1)

vnoise/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from .vnoise import Noise
2+
3+
4+
def _get_version() -> str:
5+
import pkg_resources
6+
7+
return pkg_resources.get_distribution("vnoise").version
8+
9+
10+
__version__ = _get_version()

0 commit comments

Comments
 (0)