Skip to main content

Quaternion-based orientation control and traversal in observer-centered spherical coordinates.

Project description

SpinStep

SpinStep is a quaternion-driven traversal framework for trees and orientation-based data structures. Instead of traversing by position or order, SpinStep uses 3D rotation math to navigate trees based on orientation — making it ideal for robotics, spatial reasoning, 3D scene graphs, and anywhere quaternion math naturally applies.

A 3D Graph concept image

Overview

SpinStep provides two traversal modes:

  • Continuous traversal — apply a single rotation step at each node and visit children whose orientation falls within an angular threshold (QuaternionDepthIterator).
  • Discrete traversal — try every rotation from a predefined symmetry group or custom set and visit reachable children (DiscreteQuaternionIterator with DiscreteOrientationSet).

Both modes are built on a simple Node class that stores a name, a unit quaternion orientation [x, y, z, w], and a list of children.

Installation

Requirements: Python 3.9+

Install from source:

git clone https://github.com/VoxleOne/SpinStep.git
cd SpinStep
pip install .

Or install in editable mode for development:

pip install -e ".[dev]"

Quick Start

from spinstep import Node, QuaternionDepthIterator

# Build a small tree with quaternion orientations [x, y, z, w]
root = Node("root", [0, 0, 0, 1], [
    Node("child", [0.2588, 0, 0, 0.9659])  # ~30° rotation around Z
])

# Traverse using a 30° rotation step
iterator = QuaternionDepthIterator(root, [0.2588, 0, 0, 0.9659])

for node in iterator:
    print("Visited:", node.name)
# Output:
# Visited: root
# Visited: child

Core Concepts

Node

A tree node with a quaternion-based orientation. Each node stores a name, a unit quaternion [x, y, z, w] (automatically normalised), and a list of children.

from spinstep import Node

root = Node("root", [0, 0, 0, 1])
child = Node("child", [0, 0, 0.3827, 0.9239])  # ~45° around Z
root.children.append(child)

QuaternionDepthIterator

Depth-first iterator that applies a continuous rotation step at each node. Only children within an angular threshold of the rotated state are visited.

from spinstep import Node, QuaternionDepthIterator

root = Node("root", [0, 0, 0, 1], [
    Node("close", [0.2588, 0, 0, 0.9659]),   # ~30° — will be visited
    Node("far", [0.7071, 0, 0, 0.7071]),      # ~90° — too far
])

for node in QuaternionDepthIterator(root, [0.2588, 0, 0, 0.9659]):
    print(node.name)
# Output:
# root
# close

DiscreteOrientationSet

A set of discrete quaternion orientations with spatial querying. Comes with factory methods for common symmetry groups.

from spinstep import DiscreteOrientationSet

cube_set = DiscreteOrientationSet.from_cube()          # 24 orientations
icosa_set = DiscreteOrientationSet.from_icosahedron()   # 60 orientations
grid_set = DiscreteOrientationSet.from_sphere_grid(200) # 200 Fibonacci-sampled

DiscreteQuaternionIterator

Depth-first iterator that tries every rotation in a DiscreteOrientationSet at each node. Children reachable by any rotation within the angular threshold are visited.

import numpy as np
from spinstep import Node, DiscreteOrientationSet, DiscreteQuaternionIterator

root = Node("root", [0, 0, 0, 1], [
    Node("child1", [0, 0, 0.3827, 0.9239]),
    Node("child2", [0, 0.7071, 0, 0.7071]),
])

orientation_set = DiscreteOrientationSet.from_cube()
it = DiscreteQuaternionIterator(root, orientation_set, angle_threshold=np.pi / 4)

for node in it:
    print(node.name)
# Output:
# root
# child1
# child2

Examples

Continuous Traversal with Custom Threshold

import numpy as np
from spinstep import Node, QuaternionDepthIterator

root = Node("origin", [0, 0, 0, 1], [
    Node("alpha", [0.1305, 0, 0, 0.9914]),  # ~15°
])

# Use explicit angle threshold of 20° (in radians)
it = QuaternionDepthIterator(root, [0.1305, 0, 0, 0.9914], angle_threshold=np.deg2rad(20))
print([n.name for n in it])
# Output: ['origin', 'alpha']

Query Orientations by Angle

import numpy as np
from spinstep import DiscreteOrientationSet

dos = DiscreteOrientationSet.from_cube()
indices = dos.query_within_angle([0, 0, 0, 1], np.deg2rad(10))
print(f"Found {len(indices)} orientations within 10° of identity")

Optional Dependencies

SpinStep works out of the box with NumPy and SciPy. Two optional dependencies unlock additional features:

Package Install Feature
CuPy pip install cupy-cuda12x GPU-accelerated orientation storage and angular distance computation
healpy pip install healpy HEALPix-based unique relative spin detection via get_unique_relative_spins()

GPU example:

import numpy as np
from spinstep import DiscreteOrientationSet

orientations = np.random.randn(1000, 4)
# Store orientations on GPU (requires CuPy)
gpu_set = DiscreteOrientationSet(orientations, use_cuda=True)

Development

# Install with development dependencies
pip install -e ".[dev]"

# Run tests
python -m pytest tests/ -v

# Run linter
ruff check spinstep/

Documentation

Full documentation is available in the docs/ directory:

License

MIT — see LICENSE for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

spinstep-0.6.0a0.tar.gz (38.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

spinstep-0.6.0a0-py3-none-any.whl (50.5 kB view details)

Uploaded Python 3

File details

Details for the file spinstep-0.6.0a0.tar.gz.

File metadata

  • Download URL: spinstep-0.6.0a0.tar.gz
  • Upload date:
  • Size: 38.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for spinstep-0.6.0a0.tar.gz
Algorithm Hash digest
SHA256 00fbf06d14070ed3d1e0daea25383b83a11224f5d8c2a3b261315d3970fa0474
MD5 235dc40c7dcc51665333d6e2792a6e01
BLAKE2b-256 6abc5dc90a07ed0a2d3c4e732a622deafa86262bd10c02986bfc4989c3b0aeee

See more details on using hashes here.

Provenance

The following attestation bundles were made for spinstep-0.6.0a0.tar.gz:

Publisher: publish.yml on VoxleOne/SpinStep

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file spinstep-0.6.0a0-py3-none-any.whl.

File metadata

  • Download URL: spinstep-0.6.0a0-py3-none-any.whl
  • Upload date:
  • Size: 50.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for spinstep-0.6.0a0-py3-none-any.whl
Algorithm Hash digest
SHA256 8be4fd4567efdca86cfcd23ba4dd5b906aa9eaa3afcc1daa1148a2693ddfee63
MD5 68eca407b570addd41fcda2d0f003a4d
BLAKE2b-256 4a6d54407dc522c662b7f25e0dc7de8e02ca7ed9f7d13686d7f5efd8ef97512c

See more details on using hashes here.

Provenance

The following attestation bundles were made for spinstep-0.6.0a0-py3-none-any.whl:

Publisher: publish.yml on VoxleOne/SpinStep

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page