3
\$\begingroup\$

all This script's purpose is to check whatever the acceleration component s in order. The IMU data sampled represent 2 samples of the imu measurements.

I'd love to hear your comments regarding readability and best practices.

import logging
import math
import sys

IMU_data_samples = [([-1.4120378002684528, -10.210593634959793, 3.3613594121682193],
                     [-0.002742788783767945, -0.0011996491815411065, 0.0032035080745434893]), (
                        [-1.408874821800522, -9.216022765908352, 3.3767004976616977],
                        [0.003897155186363737, -0.007772396210449728, 0.008185690588342136])]

GYRO_TOLERANCE = 0.05
ACC_TOLERANCE = 2
G_FORCE = 9.81


def make_logger():
    log = logging.getLogger(__name__)
    log.setLevel(logging.INFO)
    formatter = logging.Formatter('%(filename)s - %(asctime)s - %(levelname)s - %(message)s')
    handler = logging.StreamHandler(sys.stdout)
    handler.setFormatter(formatter)
    log.addHandler(handler)
    return log


logger = make_logger()


def check_IMU_values() -> str:
    for accelerometer, gyro in IMU_data_samples:
        for g in gyro:
            if g == 0 or abs(g) > GYRO_TOLERANCE:
                logger.exception('one of the Gyro axes is equal zero or unreasonable gyro value ')
                raise Exception

        acc_axis_values_list = []
        for acc_axis_value in accelerometer:
            acc_axis_values_list.append(acc_axis_value * acc_axis_value)
            if acc_axis_value == 0:
                logger.exception('one or more the acc axes is equal 0 !!!  ')

        acc_root_of_sum_of_squares = (math.sqrt(sum(acc_axis_values_list)))
        if not G_FORCE - ACC_TOLERANCE < acc_root_of_sum_of_squares < G_FORCE + ACC_TOLERANCE:
            logger.exception('acc value is out of scope ! ')
            raise Exception
        
    return 'The IMU is in order.'


if __name__ == '__main__':
    check_IMU_values()
\$\endgroup\$
1
  • \$\begingroup\$ Where do those data actually come from? Do you type them in manually to this file, or are they deserialized from somewhere else? \$\endgroup\$
    – Reinderien
    Commented Jul 3, 2021 at 1:55

1 Answer 1

4
\$\begingroup\$
  • Reformat your numeric data so that the decimals align, and suffix zeros until they're all the same width
  • You're using lists and tuples at (seemingly) random. In this use case, apply tuples throughout since none of your data mutate
  • The standard acceleration due to earth's gravity isn't 9.81 m/s^2 but rather 9.80665
  • There's no point in returning a string from your check routine, even if it were used (which it isn't). "Throw-or-return-None" is a perfectly fine validation strategy. Perhaps you meant to print that string - which is fine if no exceptions are raised.
  • acc_root_of_sum_of_squares is a verbose way of saying acc_norm
  • Consider making some domain-specific exceptions, and raising them instead of a broad Exception
  • Not strictly necessary to log within the check routine itself. If you want you can just log at the outer level. Logging in general seems like overkill for this application but whatever, it's good practice. Also consider logging with exc_info=True.
  • Factor out G_FORCE from your range comparison, subtracting it from the middle term.
  • Consider adding some structure and types around your data. If you make your data sample a class, validate is a natural fit as a method.
  • Drop the exclamation marks; no need to shout
  • Consider applying any to simplify your zero checks
  • Separate your "unreasonable" from "zero" check

Suggested (classes)

import logging
import math
import sys
from dataclasses import dataclass
from typing import Sequence


GYRO_TOLERANCE = 0.05
ACC_TOLERANCE = 2
G_FORCE = 9.80665


class GyroValueError(Exception):
    pass


class AccelValueError(Exception):
    pass


@dataclass
class IMUDataSample:
    accelerometer: Sequence[float]
    gyro: Sequence[float]

    @property
    def accel_norm(self) -> float:
        return math.sqrt(sum(
            a**2 for a in self.accelerometer
        ))

    def validate(self) -> None:
        if any(g == 0 for g in self.gyro):
            raise GyroValueError('one of the gyro axes is zero')

        if any(abs(g) > GYRO_TOLERANCE for g in self.gyro):
            raise GyroValueError('one of the gyro axis values is out-of-range')

        if any(acc == 0 for acc in self.accelerometer):
            raise AccelValueError('one or more of the acc axes is 0')

        if not -ACC_TOLERANCE < self.accel_norm - G_FORCE < ACC_TOLERANCE:
            raise AccelValueError('acc value is out of range')


def make_logger():
    log = logging.getLogger(__name__)
    log.setLevel(logging.INFO)
    formatter = logging.Formatter('%(filename)s - %(asctime)s - %(levelname)s - %(message)s')
    handler = logging.StreamHandler(sys.stdout)
    handler.setFormatter(formatter)
    log.addHandler(handler)
    return log


logger = make_logger()


def check_IMU_values() -> None:
    samples = (
        IMUDataSample(
            (-1.412037800268452800, -10.2105936349597930000, 3.3613594121682193000),
            (-0.002742788783767945,  -0.0011996491815411065, 0.0032035080745434893),
        ),
        IMUDataSample(
            (-1.408874821800522000,  -9.2160227659083520000, 3.3767004976616977000),
            ( 0.003897155186363737,  -0.0077723962104497280, 0.0081856905883421360),
        ),
    )

    for sample in samples:
        sample.validate()


if __name__ == '__main__':
    try:
        check_IMU_values()
        print('The IMU is in order.')
    except (GyroValueError, AccelValueError):
        logger.error('Bad IMU value(s)', exc_info=True)

Suggested (vectorization)

You've given no indication as to the scale of your real data, but this kind of thing is what Numpy was made for. It even has a built-in Frobenius norm function for you. Separate your acceleration and gyroscopic data into one array each, with shape of 3 * number-of-samples. This will likely scale better and in some ways is easier to implement.

import numpy as np

GYRO_TOLERANCE = 0.05
ACC_TOLERANCE = 2
G_FORCE = 9.80665


class GyroValueError(Exception):
    pass


class AccelValueError(Exception):
    pass


def accel_norm(accel: np.ndarray) -> np.ndarray:
    return np.linalg.norm(accel, axis=1)


def validate_accel(accel: np.ndarray) -> None:
    if np.any(accel == 0):
        raise AccelValueError('one or more of the acc axes is zero')

    norm = accel_norm(accel)

    if np.any(np.abs(norm - G_FORCE) > ACC_TOLERANCE):
        raise AccelValueError('acc value is out of range')


def validate_gyro(gyro: np.ndarray) -> None:
    if np.any(gyro == 0):
        raise GyroValueError('one of the gyro axes is zero')

    if np.any(np.abs(gyro) > GYRO_TOLERANCE):
        raise GyroValueError('one of the gyro axis values is out-of-range')


def check_IMU_values() -> None:
    accel = np.array(
        (
            (-1.412037800268452800, -10.2105936349597930000, 3.3613594121682193000),
            (-1.408874821800522000,  -9.2160227659083520000, 3.3767004976616977000),
        )
    )
    gyro = np.array(
        (
            (-0.002742788783767945,  -0.0011996491815411065, 0.0032035080745434893),
            ( 0.003897155186363737,  -0.0077723962104497280, 0.0081856905883421360),
        )
    )

    validate_accel(accel)
    validate_gyro(gyro)


if __name__ == '__main__':
    try:
        check_IMU_values()
        print('The IMU is in order.')
    except (GyroValueError, AccelValueError) as e:
        print(f'Bad IMU value(s): {e}')
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.