Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add python interface #1

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Copyright (c) 2006, Daisuke Okanohara
Copyright (c) 2008-2010, Cybozu Labs, Inc.
Copyright (c) 2017-2023, Lucas Theis
All rights reserved.

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

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

2. 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.

3. 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.

28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
Installation
============

pip install range-coder


Example
=======

```python
from range_coder import RangeEncoder, RangeDecoder, prob_to_cum_freq

data = [2, 0, 1, 0, 0, 0, 1, 2, 2]
prob = [0.5, 0.2, 0.3]

# convert probabilities to cumulative integer frequency table
cumFreq = prob_to_cum_freq(prob, resolution=128)

# encode data
encoder = RangeEncoder(filepath)
encoder.encode(data, cumFreq)
encoder.close()

# decode data
decoder = RangeDecoder(filepath)
dataRec = decoder.decode(len(data), cumFreq)
decoder.close()
```
59 changes: 59 additions & 0 deletions python/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from warnings import warn
from range_coder._range_coder import RangeEncoder, RangeDecoder # noqa: F401

try:
import numpy as np
except ImportError:
pass


def prob_to_cum_freq(prob, resolution=1024):
"""
Converts probability distribution into a cumulative frequency table.

Makes sure that non-zero probabilities are represented by non-zero frequencies,
provided that :samp:`len({prob}) <= {resolution}`.

Parameters
----------
prob : ndarray or list
A one-dimensional array representing a probability distribution

resolution : int
Number of hypothetical samples used to generate integer frequencies

Returns
-------
list
Cumulative frequency table
"""

if len(prob) > resolution:
warn('Resolution smaller than number of symbols.')

prob = np.asarray(prob, dtype=np.float64)
freq = np.zeros(prob.size, dtype=int)

# this is similar to gradient descent in KL divergence (convex)
with np.errstate(divide='ignore', invalid='ignore'):
for _ in range(resolution):
freq[np.nanargmax(prob / freq)] += 1

return [0] + np.cumsum(freq).tolist()


def cum_freq_to_prob(cumFreq):
"""
Converts a cumulative frequency table into a probability distribution.

Parameters
----------
cumFreq : list
Cumulative frequency table

Returns
-------
ndarray
Probability distribution
"""
return np.diff(cumFreq).astype(np.float64) / cumFreq[-1]
162 changes: 162 additions & 0 deletions python/src/module.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
#include <Python.h>
#include "range_coder_interface.h"

static PyMethodDef RangeEncoder_methods[] = {
{"encode",
(PyCFunction)RangeEncoder_encode,
METH_VARARGS | METH_KEYWORDS,
RangeEncoder_encode_doc},
{"close",
(PyCFunction)RangeEncoder_close,
METH_VARARGS | METH_KEYWORDS,
RangeEncoder_close_doc},
{0}
};


static PyGetSetDef RangeEncoder_getset[] = {
{0}
};


PyTypeObject RangeEncoder_type = {
PyVarObject_HEAD_INIT(0, 0)
"range_coder.RangeEncoder", /*tp_name*/
sizeof(RangeEncoderObject), /*tp_basicsize*/
0, /*tp_itemsize*/
(destructor)RangeEncoder_dealloc, /*tp_dealloc*/
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash */
0, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT, /*tp_flags*/
RangeEncoder_doc, /*tp_doc*/
0, /*tp_traverse*/
0, /*tp_clear*/
0, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
RangeEncoder_methods, /*tp_methods*/
0, /*tp_members*/
RangeEncoder_getset, /*tp_getset*/
0, /*tp_base*/
0, /*tp_dict*/
0, /*tp_descr_get*/
0, /*tp_descr_set*/
0, /*tp_dictoffset*/
(initproc)RangeEncoder_init, /*tp_init*/
0, /*tp_alloc*/
RangeEncoder_new, /*tp_new*/
};

static PyMethodDef RangeDecoder_methods[] = {
{"decode",
(PyCFunction)RangeDecoder_decode,
METH_VARARGS | METH_KEYWORDS,
RangeDecoder_decode_doc},
{"close",
(PyCFunction)RangeDecoder_close,
METH_VARARGS | METH_KEYWORDS,
RangeDecoder_close_doc},
{0}
};


static PyGetSetDef RangeDecoder_getset[] = {
{0}
};


PyTypeObject RangeDecoder_type = {
PyVarObject_HEAD_INIT(0, 0)
"range_coder.RangeDecoder", /*tp_name*/
sizeof(RangeDecoderObject), /*tp_basicsize*/
0, /*tp_itemsize*/
(destructor)RangeDecoder_dealloc, /*tp_dealloc*/
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequdece*/
0, /*tp_as_mapping*/
0, /*tp_hash */
0, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT, /*tp_flags*/
RangeDecoder_doc, /*tp_doc*/
0, /*tp_traverse*/
0, /*tp_clear*/
0, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
RangeDecoder_methods, /*tp_methods*/
0, /*tp_members*/
RangeDecoder_getset, /*tp_getset*/
0, /*tp_base*/
0, /*tp_dict*/
0, /*tp_descr_get*/
0, /*tp_descr_set*/
0, /*tp_dictoffset*/
(initproc)RangeDecoder_init, /*tp_init*/
0, /*tp_alloc*/
RangeDecoder_new, /*tp_new*/
};

#if PY_MAJOR_VERSION >= 3
static PyModuleDef range_coder_module = {
PyModuleDef_HEAD_INIT,
"_range_coder",
"A fast implementation of a range encoder and decoder.",
-1, 0, 0, 0, 0, 0
};
#endif


#if PY_MAJOR_VERSION >= 3
PyMODINIT_FUNC PyInit__range_coder() {
// create module object
PyObject* module = PyModule_Create(&range_coder_module);
#define RETVAL 0;
#else
PyMODINIT_FUNC init_range_coder() {
PyObject* module = Py_InitModule3(
"_range_coder", 0, "A fast implementation of a range encoder and decoder.");
#define RETVAL void();
#endif

if(!module)
return RETVAL;

// initialize types
if(PyType_Ready(&RangeEncoder_type) < 0)
return RETVAL;
if(PyType_Ready(&RangeDecoder_type) < 0)
return RETVAL;

// add types to module
Py_INCREF(&RangeEncoder_type);
PyModule_AddObject(module, "RangeEncoder", reinterpret_cast<PyObject*>(&RangeEncoder_type));
Py_INCREF(&RangeDecoder_type);
PyModule_AddObject(module, "RangeDecoder", reinterpret_cast<PyObject*>(&RangeDecoder_type));

#if PY_MAJOR_VERSION >= 3
return module;
#endif
}
Loading