コード例 #1
0
ファイル: transforms_CAPI.c プロジェクト: ddale/hexrd
static PyObject * unitRowVectors(PyObject *self, PyObject *args)
{
  PyArrayObject *vecIn, *vecOut;
  double *cIn, *cOut;
  int d;
  npy_intp m,n;

  if ( !PyArg_ParseTuple(args,"O", &vecIn)) return(NULL);
  if ( vecIn  == NULL ) return(NULL);

  assert( PyArray_ISCONTIGUOUS(vecIn) );
  assert( PyArray_ISALIGNED(vecIn) );

  d = PyArray_NDIM(vecIn);

  assert(d == 2);

  m = PyArray_DIMS(vecIn)[0];
  n = PyArray_DIMS(vecIn)[1];

  vecOut = (PyArrayObject*)PyArray_EMPTY(d,PyArray_DIMS(vecIn),NPY_DOUBLE,0);

  cIn  = (double*)PyArray_DATA(vecIn);
  cOut = (double*)PyArray_DATA(vecOut);

  unitRowVectors_cfunc(m,n,cIn,cOut);

  return((PyObject*)vecOut);
}
コード例 #2
0
ファイル: fffpy.c プロジェクト: FNNDSC/nipy
fff_array* fff_array_fromPyArray(const PyArrayObject* x) 
{
  fff_array* y;
  fff_datatype datatype; 
  unsigned int nbytes; 
  size_t dimX = 1, dimY = 1, dimZ = 1, dimT = 1; 
  size_t offX = 0, offY = 0, offZ = 0, offT = 0; 
  size_t ndims = (size_t)PyArray_NDIM(x);

  /* Check that the input array has less than four dimensions */ 
  if (ndims > 4) {
    FFF_ERROR("Input array has more than four dimensions", EINVAL);
    return NULL;
  }
  /* Check that the input array is aligned */ 
  if (! PyArray_ISALIGNED(x)) {
    FFF_ERROR("Input array is not aligned", EINVAL);
    return NULL;
  }
  /* Match the data type */
  datatype = fff_datatype_fromNumPy(PyArray_TYPE(x)); 
  if (datatype == FFF_UNKNOWN_TYPE) { 
    FFF_ERROR("Unrecognized data type", EINVAL);
    return NULL;    
  }
  
  /* Dimensions and offsets */ 
  nbytes = fff_nbytes(datatype); 
  dimX = PyArray_DIM(x, 0);
  offX = PyArray_STRIDE(x, 0)/nbytes;
  if (ndims > 1) {
    dimY = PyArray_DIM(x, 1);
    offY = PyArray_STRIDE(x, 1)/nbytes;
    if (ndims > 2) {
      dimZ = PyArray_DIM(x, 2);
      offZ = PyArray_STRIDE(x, 2)/nbytes;
      if (ndims > 3) {
	dimT = PyArray_DIM(x, 3);
	offT = PyArray_STRIDE(x, 3)/nbytes;
      }
    }
  }

  /* Create array (not owner) */ 
  y = (fff_array*)malloc(sizeof(fff_array)); 
  *y = fff_array_view(datatype, 
		      (void*) PyArray_DATA(x), 
		      dimX, dimY, dimZ, dimT, 
		      offX, offY, offZ, offT);
  
  return y;
}
コード例 #3
0
ファイル: numpy.hpp プロジェクト: azuredsky/mahotas
T ndarray_cast(PyArrayObject* a) {
    typedef typename no_ptr<T>::type base_type;
    assert(check_type<base_type>(a));
    assert(PyArray_ISALIGNED(a));
    // The reason for the intermediate ``as_voidp`` variable is the following:
    // around version 1.7.0, numpy played with having ``PyArray_DATA`` return
    // ``char*`` as opposed to ``void*``. ``reinterpret_cast`` from void* was
    // actually against the standard in C++ pre C++11 (although G++ accepts
    // it).
    //
    // This roundabout way works on all these versions.
    void* as_voidp = PyArray_DATA(a);
    return const_cast<T>(static_cast<T>(as_voidp));
}
コード例 #4
0
ファイル: numpy.cpp プロジェクト: mikest/geode
void throw_array_conversion_error(PyObject* object, int flags, int rank_range, PyArray_Descr* descr) {
  if (!PyArray_Check(object))
    PyErr_Format(PyExc_TypeError, "expected numpy array, got %s", object->ob_type->tp_name);
  else if (!PyArray_EquivTypes(PyArray_DESCR((PyArrayObject*)object), descr))
    PyErr_Format(PyExc_TypeError, "expected array type %s, got %s", descr->typeobj->tp_name, PyArray_DESCR((PyArrayObject*)object)->typeobj->tp_name);
  else if (!PyArray_CHKFLAGS((PyArrayObject*)object,flags)){
    if (flags&NPY_ARRAY_WRITEABLE && !PyArray_ISWRITEABLE((PyArrayObject*)object))
      PyErr_SetString(PyExc_TypeError, "unwriteable array");
    else if (flags&NPY_ARRAY_ALIGNED && !PyArray_ISALIGNED((PyArrayObject*)object))
      PyErr_SetString(PyExc_TypeError, "unaligned array");
    else if (flags&NPY_ARRAY_C_CONTIGUOUS && !PyArray_ISCONTIGUOUS((PyArrayObject*)object))
      PyErr_SetString(PyExc_TypeError, "noncontiguous array");
    else
      PyErr_SetString(PyExc_TypeError, "unknown array flag mismatch");}
  else if (rank_range>=0)
    PyErr_Format(PyExc_ValueError, "expected rank %d, got %d", rank_range, PyArray_NDIM((PyArrayObject*)object));
  else
    PyErr_Format(PyExc_ValueError, "expected rank at least %d, got %d", -rank_range-1, PyArray_NDIM((PyArrayObject*)object));
  throw PythonError();
}
コード例 #5
0
ファイル: fffpy.c プロジェクト: FNNDSC/nipy
/* 
   Get a fff_matrix from an input PyArray. This function acts as a
   fff_vector constructor that is compatible with fff_vector_delete.
*/ 
fff_matrix* fff_matrix_fromPyArray(const PyArrayObject* x) 
{
  fff_matrix* y;
  npy_intp dim[2];
  PyArrayObject* xd;

  /* Check that the input object is a two-dimensional array */ 
  if (PyArray_NDIM(x) != 2) {
    FFF_ERROR("Input array is not a matrix", EINVAL);
    return NULL;
  }


  /* If the PyArray is double, contiguous and aligned just wrap without
     copying */
  if ((PyArray_TYPE(x) == NPY_DOUBLE) && 
       (PyArray_ISCONTIGUOUS(x)) && 
       (PyArray_ISALIGNED(x))) {
    y = (fff_matrix*) malloc(sizeof(fff_matrix)); 
    y->size1 = (size_t) PyArray_DIM(x,0);
    y->size2 = (size_t) PyArray_DIM(x,1);
    y->tda = y->size2; 
    y->data = (double*) PyArray_DATA(x);
    y->owner = 0;
  }
  /* Otherwise, output a owner (contiguous) matrix with copied
     data */
  else {
    size_t dim0 = PyArray_DIM(x,0), dim1 = PyArray_DIM(x,1);
    y = fff_matrix_new((size_t)dim0, (size_t)dim1); 
    dim[0] = dim0;  
    dim[1] = dim1;

    xd = (PyArrayObject*) PyArray_SimpleNewFromData(2, dim, NPY_DOUBLE, (void*)y->data);
    PyArray_CastTo(xd, (PyArrayObject*)x); 
    Py_XDECREF(xd);
  }
  
  return y;
}
コード例 #6
0
ファイル: array.hpp プロジェクト: sdsk/mahotas
 bool is_aligned() const {
     return PyArray_ISALIGNED(array_);
 }
コード例 #7
0
ファイル: array.hpp プロジェクト: sdsk/mahotas
 aligned_iterator_type(PyArrayObject* array)
     :iterator_base<BaseType>(array) {
         assert(PyArray_ISALIGNED(array));
     }
コード例 #8
0
ファイル: accumulate.c プロジェクト: sherrinm/notebooks
static PyObject *
PyUFunc_Accumulate(PyUFuncObject *ufunc, PyArrayObject *arr, PyArrayObject *out,
                   int axis, int otype)
{
    PyArrayObject *op[2];
    PyArray_Descr *op_dtypes[2] = {NULL, NULL};
    int op_axes_arrays[2][NPY_MAXDIMS];
    int *op_axes[2] = {op_axes_arrays[0], op_axes_arrays[1]};
    npy_uint32 op_flags[2];
    int idim, ndim, otype_final;
    int needs_api, need_outer_iterator;

    NpyIter *iter = NULL, *iter_inner = NULL;

    /* The selected inner loop */
    PyUFuncGenericFunction innerloop = NULL;
    void *innerloopdata = NULL;

    const char *ufunc_name = ufunc->name ? ufunc->name : "(unknown)";

    /* These parameters come from extobj= or from a TLS global */
    int buffersize = 0, errormask = 0;

    NPY_BEGIN_THREADS_DEF;

    NPY_UF_DBG_PRINT1("\nEvaluating ufunc %s.accumulate\n", ufunc_name);

#if 0
    printf("Doing %s.accumulate on array with dtype :  ", ufunc_name);
    PyObject_Print((PyObject *)PyArray_DESCR(arr), stdout, 0);
    printf("\n");
#endif

    if (_get_bufsize_errmask(NULL, "accumulate", &buffersize, &errormask) < 0) {
        return NULL;
    }

    /* Take a reference to out for later returning */
    Py_XINCREF(out);

    otype_final = otype;
    if (get_binary_op_function(ufunc, &otype_final,
                                &innerloop, &innerloopdata) < 0) {
        PyArray_Descr *dtype = PyArray_DescrFromType(otype);
        PyErr_Format(PyExc_ValueError,
                     "could not find a matching type for %s.accumulate, "
                     "requested type has type code '%c'",
                            ufunc_name, dtype ? dtype->type : '-');
        Py_XDECREF(dtype);
        goto fail;
    }

    ndim = PyArray_NDIM(arr);

    /*
     * Set up the output data type, using the input's exact
     * data type if the type number didn't change to preserve
     * metadata
     */
    if (PyArray_DESCR(arr)->type_num == otype_final) {
        if (PyArray_ISNBO(PyArray_DESCR(arr)->byteorder)) {
            op_dtypes[0] = PyArray_DESCR(arr);
            Py_INCREF(op_dtypes[0]);
        }
        else {
            op_dtypes[0] = PyArray_DescrNewByteorder(PyArray_DESCR(arr),
                                                    NPY_NATIVE);
        }
    }
    else {
        op_dtypes[0] = PyArray_DescrFromType(otype_final);
    }
    if (op_dtypes[0] == NULL) {
        goto fail;
    }

#if NPY_UF_DBG_TRACING
    printf("Found %s.accumulate inner loop with dtype :  ", ufunc_name);
    PyObject_Print((PyObject *)op_dtypes[0], stdout, 0);
    printf("\n");
#endif

    /* Set up the op_axes for the outer loop */
    for (idim = 0; idim < ndim; ++idim) {
        op_axes_arrays[0][idim] = idim;
        op_axes_arrays[1][idim] = idim;
    }

    /* The per-operand flags for the outer loop */
    op_flags[0] = NPY_ITER_READWRITE |
                  NPY_ITER_NO_BROADCAST |
                  NPY_ITER_ALLOCATE |
                  NPY_ITER_NO_SUBTYPE;
    op_flags[1] = NPY_ITER_READONLY;

    op[0] = out;
    op[1] = arr;

    need_outer_iterator = (ndim > 1);
    /* We can't buffer, so must do UPDATEIFCOPY */
    if (!PyArray_ISALIGNED(arr) || (out && !PyArray_ISALIGNED(out)) ||
            !PyArray_EquivTypes(op_dtypes[0], PyArray_DESCR(arr)) ||
            (out &&
             !PyArray_EquivTypes(op_dtypes[0], PyArray_DESCR(out)))) {
        need_outer_iterator = 1;
    }

    if (need_outer_iterator) {
        int ndim_iter = 0;
        npy_uint32 flags = NPY_ITER_ZEROSIZE_OK|
                           NPY_ITER_REFS_OK;
        PyArray_Descr **op_dtypes_param = NULL;

        /*
         * The way accumulate is set up, we can't do buffering,
         * so make a copy instead when necessary.
         */
        ndim_iter = ndim;
        flags |= NPY_ITER_MULTI_INDEX;
        /* Add some more flags */
        op_flags[0] |= NPY_ITER_UPDATEIFCOPY|NPY_ITER_ALIGNED;
        op_flags[1] |= NPY_ITER_COPY|NPY_ITER_ALIGNED;
        op_dtypes_param = op_dtypes;
        op_dtypes[1] = op_dtypes[0];
        NPY_UF_DBG_PRINT("Allocating outer iterator\n");
        iter = NpyIter_AdvancedNew(2, op, flags,
                                   NPY_KEEPORDER, NPY_UNSAFE_CASTING,
                                   op_flags,
                                   op_dtypes_param,
                                   ndim_iter, op_axes, NULL, 0);
        if (iter == NULL) {
            goto fail;
        }

        /* In case COPY or UPDATEIFCOPY occurred */
        op[0] = NpyIter_GetOperandArray(iter)[0];
        op[1] = NpyIter_GetOperandArray(iter)[1];

        if (PyArray_SIZE(op[0]) == 0) {
            if (out == NULL) {
                out = op[0];
                Py_INCREF(out);
            }
            goto finish;
        }

        if (NpyIter_RemoveAxis(iter, axis) != NPY_SUCCEED) {
            goto fail;
        }
        if (NpyIter_RemoveMultiIndex(iter) != NPY_SUCCEED) {
            goto fail;
        }
    }

    /* Get the output */
    if (out == NULL) {
        if (iter) {
            op[0] = out = NpyIter_GetOperandArray(iter)[0];
            Py_INCREF(out);
        }
        else {
            PyArray_Descr *dtype = op_dtypes[0];
            Py_INCREF(dtype);
            op[0] = out = (PyArrayObject *)PyArray_NewFromDescr(
                                    &PyArray_Type, dtype,
                                    ndim, PyArray_DIMS(op[1]), NULL, NULL,
                                    0, NULL);
            if (out == NULL) {
                goto fail;
            }

        }
    }

    /*
     * If the reduction axis has size zero, either return the reduction
     * unit for UFUNC_REDUCE, or return the zero-sized output array
     * for UFUNC_ACCUMULATE.
     */
    if (PyArray_DIM(op[1], axis) == 0) {
        goto finish;
    }
    else if (PyArray_SIZE(op[0]) == 0) {
        goto finish;
    }

    if (iter && NpyIter_GetIterSize(iter) != 0) {
        char *dataptr_copy[3];
        npy_intp stride_copy[3];
        npy_intp count_m1, stride0, stride1;

        NpyIter_IterNextFunc *iternext;
        char **dataptr;

        int itemsize = op_dtypes[0]->elsize;

        /* Get the variables needed for the loop */
        iternext = NpyIter_GetIterNext(iter, NULL);
        if (iternext == NULL) {
            goto fail;
        }
        dataptr = NpyIter_GetDataPtrArray(iter);


        /* Execute the loop with just the outer iterator */
        count_m1 = PyArray_DIM(op[1], axis)-1;
        stride0 = 0, stride1 = PyArray_STRIDE(op[1], axis);

        NPY_UF_DBG_PRINT("UFunc: Reduce loop with just outer iterator\n");

        stride0 = PyArray_STRIDE(op[0], axis);

        stride_copy[0] = stride0;
        stride_copy[1] = stride1;
        stride_copy[2] = stride0;

        needs_api = NpyIter_IterationNeedsAPI(iter);

        NPY_BEGIN_THREADS_NDITER(iter);

        do {
            dataptr_copy[0] = dataptr[0];
            dataptr_copy[1] = dataptr[1];
            dataptr_copy[2] = dataptr[0];

            /*
             * Copy the first element to start the reduction.
             *
             * Output (dataptr[0]) and input (dataptr[1]) may point to
             * the same memory, e.g. np.add.accumulate(a, out=a).
             */
            if (otype == NPY_OBJECT) {
                /*
                 * Incref before decref to avoid the possibility of the
                 * reference count being zero temporarily.
                 */
                Py_XINCREF(*(PyObject **)dataptr_copy[1]);
                Py_XDECREF(*(PyObject **)dataptr_copy[0]);
                *(PyObject **)dataptr_copy[0] =
                                    *(PyObject **)dataptr_copy[1];
            }
            else {
                memmove(dataptr_copy[0], dataptr_copy[1], itemsize);
            }

            if (count_m1 > 0) {
                /* Turn the two items into three for the inner loop */
                dataptr_copy[1] += stride1;
                dataptr_copy[2] += stride0;
                NPY_UF_DBG_PRINT1("iterator loop count %d\n",
                                                (int)count_m1);
                innerloop(dataptr_copy, &count_m1,
                            stride_copy, innerloopdata);
            }
        } while (iternext(iter));

        NPY_END_THREADS;
    }
    else if (iter == NULL) {
        char *dataptr_copy[3];
        npy_intp stride_copy[3];

        int itemsize = op_dtypes[0]->elsize;

        /* Execute the loop with no iterators */
        npy_intp count = PyArray_DIM(op[1], axis);
        npy_intp stride0 = 0, stride1 = PyArray_STRIDE(op[1], axis);

        NPY_UF_DBG_PRINT("UFunc: Reduce loop with no iterators\n");

        if (PyArray_NDIM(op[0]) != PyArray_NDIM(op[1]) ||
                !PyArray_CompareLists(PyArray_DIMS(op[0]),
                                      PyArray_DIMS(op[1]),
                                      PyArray_NDIM(op[0]))) {
            PyErr_SetString(PyExc_ValueError,
                    "provided out is the wrong size "
                    "for the reduction");
            goto fail;
        }
        stride0 = PyArray_STRIDE(op[0], axis);

        stride_copy[0] = stride0;
        stride_copy[1] = stride1;
        stride_copy[2] = stride0;

        /* Turn the two items into three for the inner loop */
        dataptr_copy[0] = PyArray_BYTES(op[0]);
        dataptr_copy[1] = PyArray_BYTES(op[1]);
        dataptr_copy[2] = PyArray_BYTES(op[0]);

        /*
         * Copy the first element to start the reduction.
         *
         * Output (dataptr[0]) and input (dataptr[1]) may point to the
         * same memory, e.g. np.add.accumulate(a, out=a).
         */
        if (otype == NPY_OBJECT) {
            /*
             * Incref before decref to avoid the possibility of the
             * reference count being zero temporarily.
             */
            Py_XINCREF(*(PyObject **)dataptr_copy[1]);
            Py_XDECREF(*(PyObject **)dataptr_copy[0]);
            *(PyObject **)dataptr_copy[0] =
                                *(PyObject **)dataptr_copy[1];
        }
        else {
            memmove(dataptr_copy[0], dataptr_copy[1], itemsize);
        }

        if (count > 1) {
            --count;
            dataptr_copy[1] += stride1;
            dataptr_copy[2] += stride0;

            NPY_UF_DBG_PRINT1("iterator loop count %d\n", (int)count);

            needs_api = PyDataType_REFCHK(op_dtypes[0]);

            if (!needs_api) {
                NPY_BEGIN_THREADS_THRESHOLDED(count);
            }

            innerloop(dataptr_copy, &count,
                        stride_copy, innerloopdata);

            NPY_END_THREADS;
        }
    }

finish:
    Py_XDECREF(op_dtypes[0]);
    NpyIter_Deallocate(iter);
    NpyIter_Deallocate(iter_inner);

    return (PyObject *)out;

fail:
    Py_XDECREF(out);
    Py_XDECREF(op_dtypes[0]);

    NpyIter_Deallocate(iter);
    NpyIter_Deallocate(iter_inner);

    return NULL;
}
コード例 #9
0
ファイル: fastpng.hpp プロジェクト: dvberkel/mypaint
PyObject *
save_png_fast_progressive (char *filename,
                           int w, int h,
                           bool has_alpha,
                           PyObject *data_generator,
                           bool write_legacy_png)
{
  png_structp png_ptr = NULL;
  png_infop info_ptr = NULL;
  PyObject * result = NULL;
  int bpc;
  FILE * fp = NULL;
  PyObject *iterator = NULL;

  /* TODO: try if this silliness helps
#if defined(PNG_LIBPNG_VER) && (PNG_LIBPNG_VER >= 10200)
  png_uint_32 mask, flags;
  
  flags = png_get_asm_flags(png_ptr);
  mask = png_get_asm_flagmask(PNG_SELECT_READ | PNG_SELECT_WRITE);
  png_set_asm_flags(png_ptr, flags | mask);
#endif
  */

  bpc = 8;
  
  fp = fopen(filename, "wb");
  if (!fp) {
    PyErr_SetFromErrno(PyExc_IOError);
    //PyErr_Format(PyExc_IOError, "Could not open PNG file for writing: %s", filename);
    goto cleanup;
  }

  png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, (png_voidp)NULL, png_write_error_callback, NULL);
  if (!png_ptr) {
    PyErr_SetString(PyExc_MemoryError, "png_create_write_struct() failed");
    goto cleanup;
  }

  info_ptr = png_create_info_struct(png_ptr);
  if (!info_ptr) {
    PyErr_SetString(PyExc_MemoryError, "png_create_info_struct() failed");
    goto cleanup;
  }

  if (setjmp(png_jmpbuf(png_ptr))) {
    goto cleanup;
  }

  png_init_io(png_ptr, fp);

  png_set_IHDR (png_ptr, info_ptr,
                w, h, bpc,
                has_alpha ? PNG_COLOR_TYPE_RGB_ALPHA : PNG_COLOR_TYPE_RGB,
                PNG_INTERLACE_NONE,
                PNG_COMPRESSION_TYPE_BASE,
                PNG_FILTER_TYPE_BASE);

  if (! write_legacy_png) {
    // Internal data is sRGB by the time it gets here.
    // Explicitly save with the recommended chunks to advertise that fact.
    png_set_sRGB_gAMA_and_cHRM (png_ptr, info_ptr, PNG_sRGB_INTENT_PERCEPTUAL);
  }

  // default (all filters enabled):                 1350ms, 3.4MB
  //png_set_filter(png_ptr, 0, PNG_FILTER_NONE);  // 790ms, 3.8MB
  //png_set_filter(png_ptr, 0, PNG_FILTER_PAETH); // 980ms, 3.5MB
  png_set_filter(png_ptr, 0, PNG_FILTER_SUB);     // 760ms, 3.4MB

  //png_set_compression_level(png_ptr, 0); // 0.49s, 32MB
  //png_set_compression_level(png_ptr, 1); // 0.98s, 9.6MB
  png_set_compression_level(png_ptr, 2);   // 1.08s, 9.4MB
  //png_set_compression_level(png_ptr, 9); // 18.6s, 9.3MB

  png_write_info(png_ptr, info_ptr);

  if (!has_alpha) {
    // input array format format is rgbu
    png_set_filler(png_ptr, 0, PNG_FILLER_AFTER);
  }

  {
    iterator = PyObject_GetIter(data_generator);
    if (!iterator) goto cleanup;

    int y = 0;
    while (y < h) {
      int rows;
      PyObject * arr = PyIter_Next(iterator);
      if (PyErr_Occurred()) goto cleanup;
      assert(arr); // iterator should have data
      assert(PyArray_ISALIGNED(arr));
      assert(PyArray_NDIM(arr) == 3);
      assert(PyArray_DIM(arr, 1) == w);
      assert(PyArray_DIM(arr, 2) == 4); // rgbu
      assert(PyArray_TYPE(arr) == NPY_UINT8);
      assert(PyArray_STRIDE(arr, 1) == 4);
      assert(PyArray_STRIDE(arr, 2) == 1);

      rows = PyArray_DIM(arr, 0);
      assert(rows > 0);
      y += rows;
      png_bytep p = (png_bytep)PyArray_DATA(arr);
      for (int row=0; row<rows; row++) {
        png_write_row (png_ptr, p);
        p += PyArray_STRIDE(arr, 0);
      }
      Py_DECREF(arr);
    }
    assert(y == h);
    PyObject * obj = PyIter_Next(iterator);
    assert(!obj); // iterator should be finished
    if (PyErr_Occurred()) goto cleanup;
  }

  png_write_end (png_ptr, NULL);

  result = Py_BuildValue("{}");

 cleanup:
  if (iterator) Py_DECREF(iterator);
  if (info_ptr) png_destroy_write_struct(&png_ptr, &info_ptr);
  if (fp) fclose(fp);
  return result;
}
コード例 #10
0
ファイル: array.hpp プロジェクト: luispedro/mlsegment
 aligned_array(PyArrayObject* array)
     :array_base<BaseType>(array)
     ,is_carray_(PyArray_ISCARRAY(array))
     {
         assert(PyArray_ISALIGNED(array));
     }