/*----------------------------------------------------------------------
CUDA C extension for Python
This extension module provides auxiliary functionality for list-mode data
processing, generating look-up tables for image reconstruction.

author: Pawel Markiewicz
Copyrights: 2018
----------------------------------------------------------------------*/

#define PY_SSIZE_T_CLEAN
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION //NPY_API_VERSION

#include <Python.h>
#include <stdlib.h>
#include <numpy/arrayobject.h>
#include "def.h"
#include "norm.h"
#include "scanner_0.h"
#include "auxmath.h"


//=== START PYTHON INIT ===

//--- Available functions
static PyObject *mmr_norm(PyObject *self, PyObject *args);
static PyObject *mmr_span11LUT(PyObject *self, PyObject *args);
static PyObject *mmr_pgaps(PyObject *self, PyObject *args);
static PyObject *mmr_rgaps(PyObject *self, PyObject *args);
static PyObject *aux_varon(PyObject *self, PyObject *args);
//---


//> Module Method Table
static PyMethodDef mmr_auxe_methods[] = {
	{"norm",   mmr_norm, 		METH_VARARGS,
	 "Create 3D normalisation sinograms from provided normalisation components."},
	{"s1s11",  mmr_span11LUT,	METH_VARARGS,
	 "Create span-1 to span-11 look up table."},
	{"pgaps",  mmr_pgaps,		METH_VARARGS,
	 "Create span-11 Siemens compatible sinograms by inserting gaps into the GPU-optimised sinograms in span-11."},
	{"rgaps",  mmr_rgaps,  METH_VARARGS,
	 "Create span-11 GPU-optimised sinograms by removing the gaps in Siemens-compatible sinograms in span-11"	},
	{"varon",  aux_varon,  METH_VARARGS,
	 "Calculate variance online for the provided vector."},
	{NULL, NULL, 0, NULL} // Sentinel
};


//> Module Definition Structure
static struct PyModuleDef mmr_auxe_module = {
	PyModuleDef_HEAD_INIT,

	//> name of module
	"mmr_auxe",

	//> module documentation, may be NULL
	"Initialisation and basic processing routines for the Siemens Biograph mMR.",

	//> the module keeps state in global variables.
	-1,

	mmr_auxe_methods
};


//> Initialization function
PyMODINIT_FUNC PyInit_mmr_auxe(void) {

	Py_Initialize();

	//> load NumPy functionality
	import_array();

	return PyModule_Create(&mmr_auxe_module);
}

//=== END PYTHON INIT ===


//==============================================================================






//==============================================================================
// N O R M A L I S A T I O N  (component based)
//------------------------------------------------------------------------------

static PyObject *mmr_norm(PyObject *self, PyObject *args)
{

	//Structure of constants
	Cnst Cnt;
	//Dictionary of scanner constants
	PyObject * o_mmrcnst;
	// structure of norm C arrays (defined in norm.h).
	NormCmp normc;
	// structure of axial LUTs in C arrays (defined in norm.h).
	axialLUT axLUT;

	//Output norm sino
	PyObject * o_sino=NULL;
	// normalisation component dictionary.
	PyObject * o_norm_cmp;
	// axial LUT dicionary. contains such LUTs: li2rno, li2sn, li2nos.
	PyObject * o_axLUT;
	// 2D sino index LUT (dead bisn are out).
	PyObject * o_aw2ali=NULL;
	// singles buckets for dead time correction
	PyObject * o_bckts=NULL;

	//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
	/* Parse the input tuple */
	if (!PyArg_ParseTuple(args, "OOOOOO", &o_sino, &o_norm_cmp, &o_bckts, &o_axLUT, &o_aw2ali, &o_mmrcnst))
		return NULL;
	//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

	/* Interpret the input objects as numpy arrays. */
	//norm components:
	PyObject* pd_geo = PyDict_GetItemString(o_norm_cmp, "geo");
	PyObject* pd_cinf = PyDict_GetItemString(o_norm_cmp, "cinf");
	PyObject* pd_ceff = PyDict_GetItemString(o_norm_cmp, "ceff");
	PyObject* pd_axe1 = PyDict_GetItemString(o_norm_cmp, "axe1");
	PyObject* pd_dtp = PyDict_GetItemString(o_norm_cmp, "dtp");
	PyObject* pd_dtnp = PyDict_GetItemString(o_norm_cmp, "dtnp");
	PyObject* pd_dtc = PyDict_GetItemString(o_norm_cmp, "dtc");
	PyObject* pd_axe2 = PyDict_GetItemString(o_norm_cmp, "axe2");
	PyObject* pd_axf1 = PyDict_GetItemString(o_norm_cmp, "axf1");
	//axial LUTs:
	PyObject* pd_li2rno = PyDict_GetItemString(o_axLUT, "li2rno");
	PyObject* pd_li2sn = PyDict_GetItemString(o_axLUT, "li2sn");
	PyObject* pd_li2nos = PyDict_GetItemString(o_axLUT, "li2nos");
	PyObject* pd_sn1sn11 = PyDict_GetItemString(o_axLUT, "sn1_sn11");
	PyObject* pd_sn1rno = PyDict_GetItemString(o_axLUT, "sn1_rno");
	PyObject* pd_sn1sn11no = PyDict_GetItemString(o_axLUT, "sn1_sn11no");

	PyObject* pd_span = PyDict_GetItemString(o_mmrcnst, "SPN");
	Cnt.SPN = (int)PyLong_AsLong(pd_span);
	PyObject* pd_log = PyDict_GetItemString(o_mmrcnst, "LOG");
	Cnt.LOG = (char)PyLong_AsLong(pd_log);
	PyObject* pd_devid = PyDict_GetItemString(o_mmrcnst, "DEVID");
	Cnt.DEVID = (char)PyLong_AsLong(pd_devid);

	//get the output sino
	PyArrayObject *p_sino = NULL;
	p_sino = (PyArrayObject *)PyArray_FROM_OTF(o_sino, NPY_FLOAT32, NPY_ARRAY_INOUT_ARRAY2);

	//-- get the arrays from the dictionaries
	//norm components
	PyArrayObject *p_geo = NULL;
	p_geo = (PyArrayObject *)PyArray_FROM_OTF(pd_geo, NPY_FLOAT32, NPY_ARRAY_IN_ARRAY);
	PyArrayObject *p_cinf = NULL;
	p_cinf = (PyArrayObject *)PyArray_FROM_OTF(pd_cinf, NPY_FLOAT32, NPY_ARRAY_IN_ARRAY);
	PyArrayObject *p_ceff = NULL;
	p_ceff = (PyArrayObject *)PyArray_FROM_OTF(pd_ceff, NPY_FLOAT32, NPY_ARRAY_IN_ARRAY);
	PyArrayObject *p_axe1 = NULL;
	p_axe1 = (PyArrayObject *)PyArray_FROM_OTF(pd_axe1, NPY_FLOAT32, NPY_ARRAY_IN_ARRAY);
	PyArrayObject *p_dtp = NULL;
	p_dtp = (PyArrayObject *)PyArray_FROM_OTF(pd_dtp, NPY_FLOAT32, NPY_ARRAY_IN_ARRAY);
	PyArrayObject *p_dtnp = NULL;
	p_dtnp = (PyArrayObject *)PyArray_FROM_OTF(pd_dtnp, NPY_FLOAT32, NPY_ARRAY_IN_ARRAY);
	PyArrayObject *p_dtc = NULL;
	p_dtc = (PyArrayObject *)PyArray_FROM_OTF(pd_dtc, NPY_FLOAT32, NPY_ARRAY_IN_ARRAY);
	PyArrayObject *p_axe2 = NULL;
	p_axe2 = (PyArrayObject *)PyArray_FROM_OTF(pd_axe2, NPY_FLOAT32, NPY_ARRAY_IN_ARRAY);
	PyArrayObject *p_axf1 = NULL;
	p_axf1 = (PyArrayObject *)PyArray_FROM_OTF(pd_axf1, NPY_FLOAT32, NPY_ARRAY_IN_ARRAY);

	//then axLUTs
	PyArrayObject *p_li2rno = NULL;
	p_li2rno = (PyArrayObject *)PyArray_FROM_OTF(pd_li2rno, NPY_INT32, NPY_ARRAY_IN_ARRAY);
	PyArrayObject *p_li2sn = NULL;
	p_li2sn = (PyArrayObject *)PyArray_FROM_OTF(pd_li2sn, NPY_INT32, NPY_ARRAY_IN_ARRAY);
	PyArrayObject *p_li2nos = NULL;
	p_li2nos = (PyArrayObject *)PyArray_FROM_OTF(pd_li2nos, NPY_INT32, NPY_ARRAY_IN_ARRAY);
	PyArrayObject *p_sn1sn11 = NULL;
	p_sn1sn11 = (PyArrayObject *)PyArray_FROM_OTF(pd_sn1sn11, NPY_INT16, NPY_ARRAY_IN_ARRAY);
	PyArrayObject *p_sn1rno = NULL;
	p_sn1rno = (PyArrayObject *)PyArray_FROM_OTF(pd_sn1rno, NPY_INT16, NPY_ARRAY_IN_ARRAY);
	PyArrayObject *p_sn1sn11no = NULL;
	p_sn1sn11no = (PyArrayObject *)PyArray_FROM_OTF(pd_sn1sn11no, NPY_INT8, NPY_ARRAY_IN_ARRAY);

	//2D sino index LUT:
	PyArrayObject *p_aw2ali = NULL;
	p_aw2ali = (PyArrayObject *)PyArray_FROM_OTF(o_aw2ali, NPY_INT32, NPY_ARRAY_IN_ARRAY);
	// single bucktes:
	PyArrayObject *p_bckts = NULL;
	p_bckts = (PyArrayObject *)PyArray_FROM_OTF(o_bckts, NPY_INT32, NPY_ARRAY_IN_ARRAY);
	//--

	/* If that didn't work, throw an exception. */
	if (p_geo == NULL || p_cinf == NULL || p_ceff == NULL || p_axe1 == NULL ||
		p_dtp == NULL || p_dtnp == NULL || p_dtc == NULL || p_axe2 == NULL ||
		p_axf1 == NULL || p_li2rno == NULL || p_li2sn == NULL || p_li2nos == NULL ||
		p_aw2ali == NULL || p_sn1sn11 == NULL || p_sn1rno == NULL || p_sn1sn11no == NULL ||
		p_sino == NULL)
	{
		Py_XDECREF(p_geo);
		Py_XDECREF(p_cinf);
		Py_XDECREF(p_ceff);
		Py_XDECREF(p_axe1);
		Py_XDECREF(p_dtp);
		Py_XDECREF(p_dtnp);
		Py_XDECREF(p_dtc);
		Py_XDECREF(p_axe2);
		Py_XDECREF(p_axf1);
		//axLUTs
		Py_XDECREF(p_li2rno);
		Py_XDECREF(p_li2sn);
		Py_XDECREF(p_li2nos);
		Py_XDECREF(p_sn1sn11);
		Py_XDECREF(p_sn1rno);
		Py_XDECREF(p_sn1sn11no);
		//2D sino LUT
		Py_XDECREF(p_aw2ali);
		//singles buckets
		Py_XDECREF(p_bckts);

		//output sino
		PyArray_DiscardWritebackIfCopy(p_sino);
		Py_XDECREF(p_sino);
		return NULL;
	}

	//-- get the pointers to the data as C-types
	//norm components
	normc.geo = (float*)PyArray_DATA(p_geo);
	normc.cinf = (float*)PyArray_DATA(p_cinf);
	normc.ceff = (float*)PyArray_DATA(p_ceff);
	normc.axe1 = (float*)PyArray_DATA(p_axe1);
	normc.dtp = (float*)PyArray_DATA(p_dtp);
	normc.dtnp = (float*)PyArray_DATA(p_dtnp);
	normc.dtc = (float*)PyArray_DATA(p_dtc);
	normc.axe2 = (float*)PyArray_DATA(p_axe2);
	normc.axf1 = (float*)PyArray_DATA(p_axf1);
	//axLUTs
	axLUT.li2rno = (int*)PyArray_DATA(p_li2rno);
	axLUT.li2sn = (int*)PyArray_DATA(p_li2sn);
	axLUT.li2nos = (int*)PyArray_DATA(p_li2nos);
	axLUT.sn1_sn11 = (short*)PyArray_DATA(p_sn1sn11);
	axLUT.sn1_rno = (short*)PyArray_DATA(p_sn1rno);
	axLUT.sn1_sn11no = (char*)PyArray_DATA(p_sn1sn11no);

	//2D sino index LUT
	int * aw2ali = (int*)PyArray_DATA(p_aw2ali);
	//singles bucktes
	int * bckts = (int*)PyArray_DATA(p_bckts);

	//--- Array size
	int Naw = (int)PyArray_DIM(p_aw2ali, 0);
	if (AW != Naw) printf("\ne> number of active bins is inconsitent !!! <<------------------<<<<<\n");

	//output sino
	float *sino = (float *)PyArray_DATA(p_sino);

	//norm components
	normc.ngeo[0] = (int)PyArray_DIM(p_geo, 0);
	normc.ngeo[1] = (int)PyArray_DIM(p_geo, 1);
	normc.ncinf[0] = (int)PyArray_DIM(p_cinf, 0);
	normc.ncinf[1] = (int)PyArray_DIM(p_cinf, 1);
	normc.nceff[0] = (int)PyArray_DIM(p_ceff, 0);
	normc.nceff[1] = (int)PyArray_DIM(p_ceff, 1);
	normc.naxe = (int)PyArray_DIM(p_axe1, 0);
	normc.nrdt = (int)PyArray_DIM(p_dtp, 0);
	normc.ncdt = (int)PyArray_DIM(p_dtc, 0);
	//axial LUTs:
	axLUT.Nli2rno[0] = (int)PyArray_DIM(p_li2rno, 0);
	axLUT.Nli2rno[1] = (int)PyArray_DIM(p_li2rno, 1);
	axLUT.Nli2sn[0] = (int)PyArray_DIM(p_li2sn, 0);
	axLUT.Nli2sn[1] = (int)PyArray_DIM(p_li2sn, 1);
	axLUT.Nli2nos = (int)PyArray_DIM(p_li2nos, 0);

	// sets the device on which to calculate
	HANDLE_ERROR(cudaSetDevice(Cnt.DEVID));

	//<><><><><><><><><><> Call the CUDA stuff now
	norm_from_components(sino, normc, axLUT, aw2ali, bckts, Cnt);
	//<><><><><><><><><><>

	//-- Clear up
	//norm components
	Py_DECREF(p_geo);
	Py_DECREF(p_cinf);
	Py_DECREF(p_ceff);
	Py_DECREF(p_axe1);
	Py_DECREF(p_dtp);
	Py_DECREF(p_dtnp);
	Py_DECREF(p_dtc);
	Py_DECREF(p_axe2);
	//axLUT
	Py_DECREF(p_li2rno);
	Py_DECREF(p_li2sn);
	Py_DECREF(p_li2nos);
	//2D sino index LUT
	Py_DECREF(p_aw2ali);
	//singles buckets
	Py_DECREF(p_bckts);

	//output sino
	PyArray_ResolveWritebackIfCopy(p_sino);
	Py_DECREF(p_sino);

	Py_INCREF(Py_None);
	return Py_None;

}


//====================================================================================================
static PyObject *mmr_pgaps(PyObject *self, PyObject *args) {

	//output sino
	PyObject * o_sino;

	// transaxial LUT dictionary (e.g., 2D sino where dead bins are out).
	PyObject * o_txLUT;

	//Dictionary of scanner constants
	PyObject * o_mmrcnst;

	//GPU input sino in span-11
	PyObject * o_sng;

	//Structure of constants
	Cnst Cnt;

	int sino_no;

	//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
	/* Parse the input tuple */
	if (!PyArg_ParseTuple(args, "OOOOi", &o_sino, &o_sng, &o_txLUT, &o_mmrcnst, &sino_no))
		return NULL;
	//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


	/* Interpret the input objects as... */
	PyObject* pd_NSN11 = PyDict_GetItemString(o_mmrcnst, "NSN11");
	Cnt.NSN11 = (int)PyLong_AsLong(pd_NSN11);
	PyObject* pd_A = PyDict_GetItemString(o_mmrcnst, "NSANGLES");
	Cnt.A = (int)PyLong_AsLong(pd_A);
	PyObject* pd_W = PyDict_GetItemString(o_mmrcnst, "NSBINS");
	Cnt.W = (int)PyLong_AsLong(pd_W);
	PyObject* pd_SPN = PyDict_GetItemString(o_mmrcnst, "SPN");
	Cnt.SPN = (int)PyLong_AsLong(pd_SPN);
	PyObject* pd_log = PyDict_GetItemString(o_mmrcnst, "LOG");
	Cnt.LOG = (char)PyLong_AsLong(pd_log);
	PyObject* pd_devid = PyDict_GetItemString(o_mmrcnst, "DEVID");
	Cnt.DEVID = (char)PyLong_AsLong(pd_devid);

	PyObject* pd_rngstrt = PyDict_GetItemString(o_mmrcnst, "RNG_STRT");
	PyObject* pd_rngend = PyDict_GetItemString(o_mmrcnst, "RNG_END");
	Cnt.RNG_STRT = (char)PyLong_AsLong(pd_rngstrt);
	Cnt.RNG_END = (char)PyLong_AsLong(pd_rngend);

	//GPU 2D linear sino index into Siemens sino index LUT
	PyObject* pd_aw2ali = PyDict_GetItemString(o_txLUT, "aw2ali");

	//GPU input sino and the above 2D LUT
	PyArrayObject *p_sng = NULL;
	p_sng = (PyArrayObject *)PyArray_FROM_OTF(o_sng, NPY_FLOAT32, NPY_ARRAY_IN_ARRAY);
	PyArrayObject *p_aw2ali = NULL;
	p_aw2ali = (PyArrayObject *)PyArray_FROM_OTF(pd_aw2ali, NPY_INT32, NPY_ARRAY_IN_ARRAY);

	//output sino
	PyArrayObject *p_sino = NULL;
	p_sino = (PyArrayObject *)PyArray_FROM_OTF(o_sino, NPY_FLOAT32, NPY_ARRAY_INOUT_ARRAY2);

	if (p_sng == NULL || p_aw2ali == NULL || p_sino == NULL) {
		Py_XDECREF(p_aw2ali);
		Py_XDECREF(p_sng);

		PyArray_DiscardWritebackIfCopy(p_sino);
		Py_XDECREF(p_sino);
	}

	int *aw2ali = (int*)PyArray_DATA(p_aw2ali);
	float *sng = (float*)PyArray_DATA(p_sng);
	//output sino
	float *sino = (float*)PyArray_DATA(p_sino);

	// sets the device on which to calculate
	HANDLE_ERROR(cudaSetDevice(Cnt.DEVID));

	//<><><><><><><><><><><><><><><><><><><><><><>
	//Run the conversion to sinos with gaps
	put_gaps(sino, sng, aw2ali, sino_no, Cnt);
	//<><><><><><><><><><><><><><><><><><><><><><>

	//Clean up
	Py_DECREF(p_aw2ali);
	Py_DECREF(p_sng);

	PyArray_ResolveWritebackIfCopy(p_sino);
	Py_DECREF(p_sino);

	Py_INCREF(Py_None);
	return Py_None;
}


//====================================================================================================
static PyObject *mmr_rgaps(PyObject *self, PyObject *args) {

	//output sino with gaps removed
	PyObject * o_sng;

	// transaxial LUT dictionary (e.g., 2D sino where dead bins are out).
	PyObject * o_txLUT;

	//Dictionary of scanner constants
	PyObject * o_mmrcnst;

	//input sino to be reformated with gaps removed
	PyObject * o_sino;

	//Structure of constants
	Cnst Cnt;

	//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
	/* Parse the input tuple */
	if (!PyArg_ParseTuple(args, "OOOO", &o_sng, &o_sino, &o_txLUT, &o_mmrcnst))
		return NULL;
	//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

	/* Interpret the input objects as... PyLong_AsLong*/
	PyObject* pd_NSN11 = PyDict_GetItemString(o_mmrcnst, "NSN11");
	Cnt.NSN11 = (int)PyLong_AsLong(pd_NSN11);
	PyObject* pd_NSN1 = PyDict_GetItemString(o_mmrcnst, "NSN1");
	Cnt.NSN1 = (int)PyLong_AsLong(pd_NSN1);
	PyObject* pd_A = PyDict_GetItemString(o_mmrcnst, "NSANGLES");
	Cnt.A = (int)PyLong_AsLong(pd_A);
	PyObject* pd_W = PyDict_GetItemString(o_mmrcnst, "NSBINS");
	Cnt.W = (int)PyLong_AsLong(pd_W);
	PyObject* pd_SPN = PyDict_GetItemString(o_mmrcnst, "SPN");
	Cnt.SPN = (int)PyLong_AsLong(pd_SPN);
	PyObject* pd_log = PyDict_GetItemString(o_mmrcnst, "LOG");
	Cnt.LOG = (char)PyLong_AsLong(pd_log);
	PyObject* pd_devid = PyDict_GetItemString(o_mmrcnst, "DEVID");
	Cnt.DEVID = (char)PyLong_AsLong(pd_devid);

	//GPU 2D linear sino index into Siemens sino index LUT
	PyObject* pd_aw2ali = PyDict_GetItemString(o_txLUT, "aw2ali");

	//input sino and the above 2D LUT
	PyArrayObject *p_sino = NULL;
	p_sino = (PyArrayObject *)PyArray_FROM_OTF(o_sino, NPY_FLOAT32, NPY_ARRAY_IN_ARRAY);
	PyArrayObject *p_aw2ali = NULL;
	p_aw2ali = (PyArrayObject *)PyArray_FROM_OTF(pd_aw2ali, NPY_INT32, NPY_ARRAY_IN_ARRAY);

	// number of sinogram from the shape of the sino (can be any number especially when using reduced ring number)
	int snno = (int)PyArray_DIM(p_sino, 0);

	//output sino
	PyArrayObject *p_sng = NULL;
	p_sng = (PyArrayObject *)PyArray_FROM_OTF(o_sng, NPY_FLOAT32, NPY_ARRAY_INOUT_ARRAY2);

	if (p_sino == NULL || p_aw2ali == NULL || p_sino == NULL) {
		Py_XDECREF(p_aw2ali);
		Py_XDECREF(p_sino);

		PyArray_DiscardWritebackIfCopy(p_sng);
		Py_XDECREF(p_sng);
	}

	int *aw2ali = (int*)PyArray_DATA(p_aw2ali);
	float *sino = (float*)PyArray_DATA(p_sino);
	float *sng = (float*)PyArray_DATA(p_sng);

	// sets the device on which to calculate
	HANDLE_ERROR(cudaSetDevice(Cnt.DEVID));

	//<><><><><><><><><><><><><><><><><><><><><><>
	//Run the conversion to GPU sinos
	remove_gaps(sng, sino, snno, aw2ali, Cnt);
	//<><><><><><><><><><><><><><><><><><><><><><>

	//Clean up
	Py_DECREF(p_aw2ali);
	Py_DECREF(p_sino);

	PyArray_ResolveWritebackIfCopy(p_sng);
	Py_DECREF(p_sng);

	Py_INCREF(Py_None);
	return Py_None;

}



//====================================================================================================
static PyObject *mmr_span11LUT(PyObject *self, PyObject *args) {
	//Dictionary of scanner constants
	PyObject * o_mmrcnst;

	//Structure of constants
	Cnst Cnt;

	//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
	/* Parse the input tuple */
	if (!PyArg_ParseTuple(args, "O", &o_mmrcnst))
		return NULL;
	//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

	/* Interpret the input objects as... */
	PyObject* pd_Naw = PyDict_GetItemString(o_mmrcnst, "Naw");
	Cnt.aw = (int)PyLong_AsLong(pd_Naw);
	PyObject* pd_NSN1 = PyDict_GetItemString(o_mmrcnst, "NSN1");
	Cnt.NSN1 = (int)PyLong_AsLong(pd_NSN1);
	PyObject* pd_NSN11 = PyDict_GetItemString(o_mmrcnst, "NSN11");
	Cnt.NSN11 = (int)PyLong_AsLong(pd_NSN11);
	PyObject* pd_NRNG = PyDict_GetItemString(o_mmrcnst, "NRNG");
	Cnt.NRNG = (int)PyLong_AsLong(pd_NRNG);


	span11LUT span11 = span1_span11(Cnt);

	npy_intp dims[2];
	dims[0] = Cnt.NSN1;
	PyArrayObject *s1s11_out = (PyArrayObject *)PyArray_SimpleNewFromData(1, dims, NPY_INT16, span11.li2s11);
	dims[0] = Cnt.NSN11;
	PyArrayObject *s1nos_out = (PyArrayObject *)PyArray_SimpleNewFromData(1, dims, NPY_INT8, span11.NSinos);

	PyObject *o_out = PyTuple_New(2);
	PyTuple_SetItem(o_out, 0, PyArray_Return(s1s11_out));
	PyTuple_SetItem(o_out, 1, PyArray_Return(s1nos_out));


	return o_out;
}



//====================================================================================================
static PyObject *aux_varon(PyObject *self, PyObject *args) {

	// M1 (mean) vector
	PyObject * o_m1;
	// M2 (variance) vector
	PyObject * o_m2;
	//input of instance data X
	PyObject * o_x;
	//Dictionary of scanner constants
	PyObject * o_mmrcnst;

	//Structure of constants
	Cnst Cnt;
	//realisation number
	int b;

	//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
	/* Parse the input tuple */
	if (!PyArg_ParseTuple(args, "OOOiO", &o_m1, &o_m2, &o_x, &b, &o_mmrcnst))
		return NULL;
	//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

	PyObject* pd_log = PyDict_GetItemString(o_mmrcnst, "LOG");
	Cnt.LOG = (char)PyLong_AsLong(pd_log);
	PyObject* pd_devid = PyDict_GetItemString(o_mmrcnst, "DEVID");
	Cnt.DEVID = (char)PyLong_AsLong(pd_devid);

	//input sino and the above 2D LUT
	PyArrayObject *p_m1 = NULL;
	p_m1 = (PyArrayObject *)PyArray_FROM_OTF(o_m1, NPY_FLOAT32, NPY_ARRAY_INOUT_ARRAY2);
	PyArrayObject *p_m2 = NULL;
	p_m2 = (PyArrayObject *)PyArray_FROM_OTF(o_m2, NPY_FLOAT32, NPY_ARRAY_INOUT_ARRAY2);
	PyArrayObject *p_x  = NULL;
	p_x  = (PyArrayObject *)PyArray_FROM_OTF(o_x, NPY_FLOAT32, NPY_ARRAY_IN_ARRAY);

	if (p_m1 == NULL || p_m2 == NULL || p_x == NULL) {
		PyArray_DiscardWritebackIfCopy(p_m1);
		PyArray_DiscardWritebackIfCopy(p_m2);
		Py_XDECREF(p_m1);
		Py_XDECREF(p_m2);
		Py_XDECREF(p_x);
	}

	float *m1 = (float*)PyArray_DATA(p_m1);
	float *m2 = (float*)PyArray_DATA(p_m2);
	float *x = (float*)PyArray_DATA(p_x);
	int  ndim = PyArray_NDIM(p_x);
	size_t nele = 1;
	for (int i = 0; i<ndim; i++) {
		nele *= PyArray_DIM(p_x, i);
	}

	printf("i> number of elements in data array: %lu\n", nele);

	// sets the device on which to calculate
	HANDLE_ERROR(cudaSetDevice(Cnt.DEVID));

	//<><><><><><><><><><><><><><><><><><><><><><>
	//Update variance online (M1, M2) using data instance X
	var_online(m1, m2, x, b, nele);
	//<><><><><><><><><><><><><><><><><><><><><><>

	//Clean up
	PyArray_ResolveWritebackIfCopy(p_m1);
	PyArray_ResolveWritebackIfCopy(p_m2);
	Py_DECREF(p_m1);
	Py_DECREF(p_m2);
	Py_DECREF(p_x);

	Py_INCREF(Py_None);
	return Py_None;

}
