Skip to content

Commit

Permalink
Add a copy of si-prefix
Browse files Browse the repository at this point in the history
This is necessary to get videojitter to build on Python 3.12.
  • Loading branch information
dechamps committed Jan 9, 2024
1 parent bf42cae commit 242db36
Show file tree
Hide file tree
Showing 8 changed files with 333 additions and 7 deletions.
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,6 @@ slow to refresh - they are not necessarily the instrument's fault.
1. Make sure you have [Python][] installed.
- On Windows, you can get it from the Microsoft Store, or just run
`winget install python`.
- Avoid Python 3.12 (and later) as it [causes issues with
`si-prefix`][si-prefix-issue11] (a dependency of videojitter).
2. Make sure you have [FFmpeg][] installed.
- On Windows, you can install it by running `winget install ffmpeg`.
- You don't need FFmpeg if you don't need to generate a test video, e.g. you
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,11 @@ videojitter-generate-video = "videojitter.generate_video:main"
strip-extras = true

[tool.black]
force-exclude = "src/si_prefix/"
preview = true # For long string line breaks

[tool.pylint.main]
ignore-paths = ["src/videojitter/_version_generated.py"]
ignore-paths = ["src/si_prefix", "src/videojitter/_version_generated.py"]
disable = [
"duplicate-code",
"missing-module-docstring",
Expand Down
1 change: 0 additions & 1 deletion requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,4 @@ ffmpeg-python
numpy
pandas
scipy
si-prefix
soundfile
2 changes: 0 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,6 @@ rpds-py==0.12.0
# referencing
scipy==1.11.3
# via -r requirements.in
si-prefix==1.2.2
# via -r requirements.in
six==1.16.0
# via python-dateutil
soundfile==0.12.1
Expand Down
29 changes: 29 additions & 0 deletions src/si_prefix/LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
BSD 3-Clause License

Copyright (c) 2017, Christian Fobel
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
10 changes: 10 additions & 0 deletions src/si_prefix/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# si-prefix

This directory contains a copy of the main code of [si-prefix v1.2.2][] by
Christian Fobel (with a couple of minor compatibility changes).

The reason why videojitter does not simply pull si-prefix as a pip dependency is
because the si-prefix build [fails on Python 3.12][].

[si-prefix v1.2.2]: https://github.com/cfobel/si-prefix/tree/v1.2.2
[fails on Python 3.12]: https://github.com/cfobel/si-prefix/issues/11
291 changes: 291 additions & 0 deletions src/si_prefix/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
# coding: utf-8
from __future__ import division
import math
import re

# Print a floating-point number in engineering notation.
# Ported from [C version][1] written by
# Jukka “Yucca” Korpela <[email protected]>.
#
# [1]: http://www.cs.tut.fi/~jkorpela/c/eng.html

#: .. versionchanged:: 1.0
#: Define as unicode string and use µ (i.e., ``\N{MICRO SIGN}``, ``\x0b5``)
#: to denote micro (not u).
#:
#: .. seealso::
#:
#: `Issue #4`_.
#:
#: `Forum post`_ discussing unicode using µ as an example.
#:
#: `The International System of Units (SI) report`_ from the Bureau
#: International des Poids et Mesures
#:
#: .. _`Issue #4`: https://github.com/cfobel/si-prefix/issues/4
#: .. _`Forum post`: https://mail.python.org/pipermail/python-list/2009-February/525913.html
#: .. _`The International System of Units (SI) report`: https://www.bipm.org/utils/common/pdf/si_brochure_8_en.pdf
SI_PREFIX_UNITS = u"yzafpnµm kMGTPEZY"
#: .. versionchanged:: 1.0
#: Use unicode string for SI unit to support micro (i.e., µ) character.
#:
#: .. seealso::
#:
#: `Issue #4`_.
#:
#: .. _`Issue #4`: https://github.com/cfobel/si-prefix/issues/4
CRE_SI_NUMBER = re.compile(r'\s*(?P<sign>[\+\-])?'
r'(?P<integer>\d+)'
r'(?P<fraction>.\d+)?\s*'
r'(?P<si_unit>[%s])?\s*' % SI_PREFIX_UNITS)


def split(value, precision=1):
'''
Split `value` into value and "exponent-of-10", where "exponent-of-10" is a
multiple of 3. This corresponds to SI prefixes.
Returns tuple, where the second value is the "exponent-of-10" and the first
value is `value` divided by the "exponent-of-10".
Args
----
value : int, float
Input value.
precision : int
Number of digits after decimal place to include.
Returns
-------
tuple
The second value is the "exponent-of-10" and the first value is `value`
divided by the "exponent-of-10".
Examples
--------
.. code-block:: python
si_prefix.split(0.04781) -> (47.8, -3)
si_prefix.split(4781.123) -> (4.8, 3)
See :func:`si_format` for more examples.
'''
negative = False
digits = precision + 1

if value < 0.:
value = -value
negative = True
elif value == 0.:
return 0., 0

expof10 = int(math.log10(value))
if expof10 > 0:
expof10 = (expof10 // 3) * 3
else:
expof10 = (-expof10 + 3) // 3 * (-3)

value *= 10 ** (-expof10)

if value >= 1000.:
value /= 1000.0
expof10 += 3
elif value >= 100.0:
digits -= 2
elif value >= 10.0:
digits -= 1

if negative:
value *= -1

return value, int(expof10)


def prefix(expof10):
'''
Args:
expof10 : Exponent of a power of 10 associated with a SI unit
character.
Returns:
str : One of the characters in "yzafpnum kMGTPEZY".
'''
prefix_levels = (len(SI_PREFIX_UNITS) - 1) // 2
si_level = expof10 // 3

if abs(si_level) > prefix_levels:
raise ValueError("Exponent out range of available prefixes.")
return SI_PREFIX_UNITS[si_level + prefix_levels]


def si_format(value, precision=1, format_str=u'{value} {prefix}',
exp_format_str=u'{value}e{expof10}'):
'''
Format value to string with SI prefix, using the specified precision.
Parameters
----------
value : int, float
Input value.
precision : int
Number of digits after decimal place to include.
format_str : str or unicode
Format string where ``{prefix}`` and ``{value}`` represent the SI
prefix and the value (scaled according to the prefix), respectively.
The default format matches the `SI prefix style`_ format.
exp_str : str or unicode
Format string where ``{expof10}`` and ``{value}`` represent the
exponent of 10 and the value (scaled according to the exponent of 10),
respectively. This format is used if the absolute exponent of 10 value
is greater than 24.
Returns
-------
unicode
:data:`value` formatted according to the `SI prefix style`_.
Examples
--------
For example, with `precision=2`:
.. code-block:: python
1e-27 --> 1.00e-27
1.764e-24 --> 1.76 y
7.4088e-23 --> 74.09 y
3.1117e-21 --> 3.11 z
1.30691e-19 --> 130.69 z
5.48903e-18 --> 5.49 a
2.30539e-16 --> 230.54 a
9.68265e-15 --> 9.68 f
4.06671e-13 --> 406.67 f
1.70802e-11 --> 17.08 p
7.17368e-10 --> 717.37 p
3.01295e-08 --> 30.13 n
1.26544e-06 --> 1.27 u
5.31484e-05 --> 53.15 u
0.00223223 --> 2.23 m
0.0937537 --> 93.75 m
3.93766 --> 3.94
165.382 --> 165.38
6946.03 --> 6.95 k
291733 --> 291.73 k
1.22528e+07 --> 12.25 M
5.14617e+08 --> 514.62 M
2.16139e+10 --> 21.61 G
9.07785e+11 --> 907.78 G
3.8127e+13 --> 38.13 T
1.60133e+15 --> 1.60 P
6.7256e+16 --> 67.26 P
2.82475e+18 --> 2.82 E
1.1864e+20 --> 118.64 E
4.98286e+21 --> 4.98 Z
2.0928e+23 --> 209.28 Z
8.78977e+24 --> 8.79 Y
3.6917e+26 --> 369.17 Y
1.55051e+28 --> 15.51e+27
6.51216e+29 --> 651.22e+27
.. versionchanged:: 1.0
Use unicode string for :data:`format_str` and SI value format string to
support micro (i.e., µ) characte, and change return type to unicode
string.
.. seealso::
`Issue #4`_.
.. _`Issue #4`: https://github.com/cfobel/si-prefix/issues/4
.. _SI prefix style:
http://physics.nist.gov/cuu/Units/checklist.html
'''
svalue, expof10 = split(value, precision)
value_format = u'%%.%df' % precision
value_str = value_format % svalue
try:
return format_str.format(value=value_str,
prefix=prefix(expof10).strip())
except ValueError:
sign = ''
if expof10 > 0:
sign = "+"
return exp_format_str.format(value=value_str,
expof10=''.join([sign, str(expof10)]))


def si_parse(value):
'''
Parse a value expressed using SI prefix units to a floating point number.
Parameters
----------
value : str or unicode
Value expressed using SI prefix units (as returned by :func:`si_format`
function).
.. versionchanged:: 1.0
Use unicode string for SI unit to support micro (i.e., µ) character.
.. seealso::
`Issue #4`_.
.. _`Issue #4`: https://github.com/cfobel/si-prefix/issues/4
'''
CRE_10E_NUMBER = re.compile(r'^\s*(?P<integer>[\+\-]?\d+)?'
r'(?P<fraction>.\d+)?\s*([eE]\s*'
r'(?P<expof10>[\+\-]?\d+))?$')
CRE_SI_NUMBER = re.compile(r'^\s*(?P<number>(?P<integer>[\+\-]?\d+)?'
r'(?P<fraction>.\d+)?)\s*'
r'(?P<si_unit>[%s])?\s*$' % SI_PREFIX_UNITS)
match = CRE_10E_NUMBER.match(value)
if match:
# Can be parse using `float`.
assert(match.group('integer') is not None or
match.group('fraction') is not None)
return float(value)
match = CRE_SI_NUMBER.match(value)
assert(match.group('integer') is not None or
match.group('fraction') is not None)
d = match.groupdict()
si_unit = d['si_unit'] if d['si_unit'] else ' '
prefix_levels = (len(SI_PREFIX_UNITS) - 1) // 2
scale = 10 ** (3 * (SI_PREFIX_UNITS.index(si_unit) - prefix_levels))
return float(d['number']) * scale


def si_prefix_scale(si_unit):
'''
Parameters
----------
si_unit : str
SI unit character, i.e., one of "yzafpnµm kMGTPEZY".
Returns
-------
int
Multiple associated with `si_unit`, e.g., 1000 for `si_unit=k`.
'''
return 10 ** si_prefix_expof10(si_unit)


def si_prefix_expof10(si_unit):
'''
Parameters
----------
si_unit : str
SI unit character, i.e., one of "yzafpnµm kMGTPEZY".
Returns
-------
int
Exponent of the power of ten associated with `si_unit`, e.g., 3 for
`si_unit=k` and -6 for `si_unit=µ`.
'''
prefix_levels = (len(SI_PREFIX_UNITS) - 1) // 2
return (3 * (SI_PREFIX_UNITS.index(si_unit) - prefix_levels))
2 changes: 1 addition & 1 deletion src/videojitter/generate_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
import altair as alt
import numpy as np
import pandas as pd
from si_prefix import si_format
from scipy import stats
from si_prefix import si_format
from videojitter import _util, _version


Expand Down

0 comments on commit 242db36

Please sign in to comment.