Description: API details.

# riann

API details.

### RIANN

>      RIANN (onnx_path=None)

*Robust IMU-based Attitude Neural Network for orientation estimation using IMU data.

This class implements efficient quaternion-based attitude estimation from accelerometer and gyroscope data using a neural network approach. The implementation is optimized for step-by-step processing in real-time applications.*

| Type | Default | Details
--- | --- | --- | ---
onnx_path | NoneType | None | Path to the RIANN onnx file. Defaults to the file provided with the package.

    # Example 1: Basic usage with batch processing

    # Prepare sample IMU data
    sequence_length = 100
    acc = np.ones((sequence_length, 3))  # Accelerometer data [m/s²]
    gyr = np.zeros((sequence_length, 3))  # Gyroscope data [rad/s]
    fs = 200  # Sampling rate [Hz]

    # Initialize RIANN
    riann = RIANN()

    # Process all data at once
    quaternions = riann.predict(acc, gyr, fs)
    print(f"Quaternions shape: {quaternions.shape}")  # (100, 4)

    Quaternions shape: (100, 4)

    # Example 2: Real-time processing

    # Initialize RIANN for real-time processing
    riann = RIANN()
    riann.set_sampling_rate(100)  # 100 Hz
    riann.reset_state()

    # Simulated IMU data stream
    def simulate_imu_data():
        """Simulate a single IMU reading"""
        # In a real application, this would read from actual sensors
        acc = np.array([0.0, 0.0, 9.81]) + np.random.normal(0, 0.1, 3)  # Add noise
        gyr = np.array([0.0, 0.0, 0.0]) + np.random.normal(0, 0.01, 3)  # Add noise
        return acc, gyr

    # Simulated real-time loop
    orientations = []
    for _ in range(100):  # Process 100 readings
        # Read IMU data
        acc, gyr = simulate_imu_data()

        # Process data to get orientation
        quaternion = riann.predict_step(acc, gyr)
        orientations.append(quaternion)

        # In a real application, you might use the orientation here
        # for control, visualization, etc.

        # Simulate sensor sampling rate
        time.sleep(0.01)  # 100 Hz

    orientations = np.array(orientations)
    print(f"Collected {len(orientations)} orientation estimates")

    Collected 100 orientation estimates

    # Example 3: Processing existing datasets
    import h5py

    # Load data from file (example using the BROAD dataset as shown in RIANN_Example.ipynb)
    # In your application, adapt this to your data source
    try:
        with h5py.File('data_hdf5/01_undisturbed_slow_rotation_A.hdf5', 'r') as f:
            acc = f['imu_acc'][:]  # Accelerometer data
            gyr = f['imu_gyr'][:]  # Gyroscope data
            ref_quat = f['opt_quat'][:]  # Reference quaternions (if available)
            fs = f.attrs['sampling_rate']  # Sampling rate
    except FileNotFoundError:
        # Create dummy data if file not found
        sequence_length = 1000
        acc = np.ones((sequence_length, 3))
        gyr = np.zeros((sequence_length, 3))
        ref_quat = None
        fs = 200

    # Initialize RIANN
    riann = RIANN()

    # Process data
    est_quat = riann.predict(acc, gyr, fs)

    # If reference data is available, calculate error
    if ref_quat is not None:
        # Helper function for attitude error calculation
        def calculate_attitude_error(q1, q2):
            """Calculate attitude error in degrees between two quaternions"""
            # Simple dot product method (accurate for small angles)
            dot_products = np.sum(q1 * q2, axis=1)
            dot_products = np.clip(dot_products, -1.0, 1.0)  # Ensure valid acos input
            angles_rad = 2 * np.arccos(np.abs(dot_products))
            return angles_rad * 180 / np.pi  # Convert to degrees

        # Calculate errors
        errors = calculate_attitude_error(est_quat, ref_quat)

        # Plot error
        plt.figure(figsize=(10, 6))
        plt.plot(errors)
        plt.xlabel('Sample')
        plt.ylabel('Attitude Error (degrees)')
        plt.title('Orientation Estimation Error')
        plt.grid(True)
        plt.show()

        print(f"Mean error: {np.mean(errors):.2f} degrees")
        print(f"Max error: {np.max(errors):.2f} degrees")

    # Example 4: Advanced usage with step-by-step processing and state tracking

    # Create synthetic IMU data with known motion pattern
    def generate_synthetic_data(fs=100, duration=5):
        """Generate synthetic IMU data for a specific motion pattern"""
        n_samples = int(fs * duration)
        t = np.linspace(0, duration, n_samples)

        # Initialize arrays
        acc = np.zeros((n_samples, 3))
        gyr = np.zeros((n_samples, 3))

        # Create a simple rotation pattern (rotation around y-axis)
        for i, time in enumerate(t):
            # Gyroscope data (constant rotation around y-axis)
            if 1.0 <= time <= 3.0:
                gyr[i, 1] = 0.5  # 0.5 rad/s around y-axis

            # Accelerometer data (gravity vector rotated accordingly)
            if time < 1.0:
                # Initial position - gravity along z
                acc[i] = [0, 0, 9.81]
            elif time < 3.0:
                # During rotation - rotate gravity vector
                angle = 0.5 * (time - 1.0)  # 0.5 rad/s * time
                acc[i] = [9.81 * np.sin(angle), 0, 9.81 * np.cos(angle)]
            else:
                # Final position - gravity rotated
                acc[i] = [9.81 * np.sin(1.0), 0, 9.81 * np.cos(1.0)]

        # Add noise to make it realistic
        acc += np.random.normal(0, 0.1, acc.shape)
        gyr += np.random.normal(0, 0.01, gyr.shape)

        return t, acc, gyr

    # Generate data
    fs = 100
    t, acc, gyr = generate_synthetic_data(fs=fs)

    # Initialize RIANN
    riann = RIANN()

    # Process data in chunks to simulate real-time processing with state tracking
    chunk_size = 50  # Process 50 samples at a time
    n_chunks = len(acc) // chunk_size

    # Store all quaternions
    all_quaternions = []

    for chunk in range(n_chunks):
        start_idx = chunk * chunk_size
        end_idx = start_idx + chunk_size

        # Process this chunk
        if chunk == 0:
            # First chunk - initialize state
            riann.reset_state()

        # Process chunk step by step
        for i in range(start_idx, end_idx):
            quat = riann.predict_step(acc[i], gyr[i], fs)
            all_quaternions.append(quat)

        # At this point in a real application, you could:
        # 1. Use the current orientation for feedback
        # 2. Save the current state for later resume
        # 3. Transmit the orientation to another system
        print(f"Processed chunk {chunk+1}/{n_chunks}, " +
              f"current orientation: w={quat[0]:.2f}, x={quat[1]:.2f}, " +
              f"y={quat[2]:.2f}, z={quat[3]:.2f}")

    all_quaternions = np.array(all_quaternions)
    print(f"Generated {len(all_quaternions)} orientation estimates")

    Processed chunk 1/10, current orientation: w=-0.79, x=0.00, y=-0.00, z=0.61
    Processed chunk 2/10, current orientation: w=-0.81, x=-0.00, y=-0.00, z=0.59
    Processed chunk 3/10, current orientation: w=-0.90, x=-0.04, y=-0.09, z=0.42
    Processed chunk 4/10, current orientation: w=-0.98, x=-0.00, y=-0.16, z=0.09
    Processed chunk 5/10, current orientation: w=-0.94, x=0.08, y=-0.18, z=-0.28
    Processed chunk 6/10, current orientation: w=-0.87, x=0.15, y=-0.17, z=-0.44
    Processed chunk 7/10, current orientation: w=-0.75, x=0.04, y=0.02, z=0.66
    Processed chunk 8/10, current orientation: w=-0.56, x=0.08, y=0.10, z=0.82
    Processed chunk 9/10, current orientation: w=-0.53, x=0.11, y=0.13, z=0.83
    Processed chunk 10/10, current orientation: w=-0.55, x=0.15, y=0.15, z=0.81
    Generated 500 orientation estimates