test_imgproc.cpp 18.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/*M///////////////////////////////////////////////////////////////////////////////////////
//
//  IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
//  By downloading, copying, installing or using the software you agree to this license.
//  If you do not agree to this license, do not download, install,
//  copy or use the software.
//
//
//                           License Agreement
//                For Open Source Computer Vision Library
//
// Copyright (C) 2010-2012, Institute Of Software Chinese Academy Of Science, all rights reserved.
// Copyright (C) 2010-2012, Advanced Micro Devices, Inc., all rights reserved.
yao's avatar
yao committed
15
// Copyright (C) 2010-2012, Multicoreware, Inc., all rights reserved.
16 17 18 19 20 21 22 23 24 25
// Third party copyrights are property of their respective owners.
//
// @Authors
//    Niko Li, newlife20080214@gmail.com
//    Jia Haipeng, jiahaipeng95@gmail.com
//    Shengen Yan, yanshengen@gmail.com
//    Jiang Liyuan, lyuan001.good@163.com
//    Rock Li, Rock.Li@amd.com
//    Wu Zailong, bullet@yeah.net
//    Xu Pang, pangxu010@163.com
26
//    Sen Liu, swjtuls1987@126.com
27 28 29 30 31 32 33 34 35
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
//   * Redistribution's of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//
//   * Redistribution's in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
Andrey Pavlenko's avatar
Andrey Pavlenko committed
36
//     and/or other materials provided with the distribution.
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
//
//   * The name of the copyright holders may not 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 Intel Corporation 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.
//
//M*/

54
#include "test_precomp.hpp"
55 56 57

#ifdef HAVE_OPENCL

58
using namespace testing;
59 60
using namespace std;
using namespace cv;
61

62
///////////////////////////////////////////////////////////////////////////////
63

64 65 66 67
PARAM_TEST_CASE(ImgprocTestBase, MatType,
                int, // blockSize
                int, // border type
                bool) // roi or not
68
{
69 70
    int type, borderType, blockSize;
    bool useRoi;
71

72 73
    Mat src, dst_whole, src_roi, dst_roi;
    ocl::oclMat gsrc_whole, gsrc_roi, gdst_whole, gdst_roi;
74

75
    virtual void SetUp()
76
    {
77 78 79 80
        type = GET_PARAM(0);
        blockSize = GET_PARAM(1);
        borderType = GET_PARAM(2);
        useRoi = GET_PARAM(3);
81 82
    }

83
    virtual void random_roi()
84
    {
85 86 87
        Size roiSize = randomSize(1, MAX_VALUE);
        Border srcBorder = randomBorder(0, useRoi ? MAX_VALUE : 0);
        randomSubMat(src, src_roi, roiSize, srcBorder, type, 5, 256);
88

89 90
        Border dstBorder = randomBorder(0, useRoi ? MAX_VALUE : 0);
        randomSubMat(dst_whole, dst_roi, roiSize, dstBorder, type, 5, 16);
91

92 93
        generateOclMat(gsrc_whole, gsrc_roi, src, roiSize, srcBorder);
        generateOclMat(gdst_whole, gdst_roi, dst_whole, roiSize, dstBorder);
94 95
    }

96
    void Near(double threshold = 0.0, bool relative = false)
97
    {
98
        Mat roi, whole;
99 100
        gdst_whole.download(whole);
        gdst_roi.download(roi);
101

102 103 104 105 106 107 108 109 110 111
        if (relative)
        {
            EXPECT_MAT_NEAR_RELATIVE(dst_whole, whole, threshold);
            EXPECT_MAT_NEAR_RELATIVE(dst_roi, roi, threshold);
        }
        else
        {
            EXPECT_MAT_NEAR(dst_whole, whole, threshold);
            EXPECT_MAT_NEAR(dst_roi, roi, threshold);
        }
112
    }
113 114 115 116
};

////////////////////////////////copyMakeBorder////////////////////////////////////////////

117 118 119 120 121
PARAM_TEST_CASE(CopyMakeBorder, MatDepth, // depth
                Channels, // channels
                bool, // isolated or not
                Border, // border type
                bool) // roi or not
122
{
123 124
    int type, borderType;
    bool useRoi;
125

126 127
    Border border;
    Scalar val;
128

129 130
    Mat src, dst_whole, src_roi, dst_roi;
    ocl::oclMat gsrc_whole, gsrc_roi, gdst_whole, gdst_roi;
131

132
    virtual void SetUp()
133
    {
134 135
        type = CV_MAKE_TYPE(GET_PARAM(0), GET_PARAM(1));
        borderType = GET_PARAM(3);
136

137 138
        if (GET_PARAM(2))
            borderType |= BORDER_ISOLATED;
139

140
        useRoi = GET_PARAM(4);
141 142
    }

143
    void random_roi()
144
    {
145 146 147
        border = randomBorder(0, MAX_VALUE << 2);
        val = randomScalar(-MAX_VALUE, MAX_VALUE);

148 149
        Size roiSize = randomSize(1, MAX_VALUE);
        Border srcBorder = randomBorder(0, useRoi ? MAX_VALUE : 0);
150
        randomSubMat(src, src_roi, roiSize, srcBorder, type, -MAX_VALUE, MAX_VALUE);
151

152
        Border dstBorder = randomBorder(0, useRoi ? MAX_VALUE : 0);
153 154 155 156 157 158
        dstBorder.top += border.top;
        dstBorder.lef += border.lef;
        dstBorder.rig += border.rig;
        dstBorder.bot += border.bot;

        randomSubMat(dst_whole, dst_roi, roiSize, dstBorder, type, -MAX_VALUE, MAX_VALUE);
159

160 161
        generateOclMat(gsrc_whole, gsrc_roi, src, roiSize, srcBorder);
        generateOclMat(gdst_whole, gdst_roi, dst_whole, roiSize, dstBorder);
162 163
    }

164
    void Near(double threshold = 0.0)
165
    {
166 167 168
        Mat whole, roi;
        gdst_whole.download(whole);
        gdst_roi.download(roi);
169

170 171
        EXPECT_MAT_NEAR(dst_whole, whole, threshold);
        EXPECT_MAT_NEAR(dst_roi, roi, threshold);
172 173 174
    }
};

175
OCL_TEST_P(CopyMakeBorder, Mat)
176
{
177
    for (int i = 0; i < LOOP_TIMES; ++i)
178 179 180
    {
        random_roi();

181 182
        cv::copyMakeBorder(src_roi, dst_roi, border.top, border.bot, border.lef, border.rig, borderType, val);
        ocl::copyMakeBorder(gsrc_roi, gdst_roi, border.top, border.bot, border.lef, border.rig, borderType, val);
183

184
        Near();
185 186 187
    }
}

188
////////////////////////////////equalizeHist//////////////////////////////////////////////
189

190
typedef ImgprocTestBase EqualizeHist;
191

192
OCL_TEST_P(EqualizeHist, Mat)
193
{
194
    for (int j = 0; j < LOOP_TIMES; j++)
195 196 197
    {
        random_roi();

198 199
        equalizeHist(src_roi, dst_roi);
        ocl::equalizeHist(gsrc_roi, gdst_roi);
200

201
        Near(1.1);
202 203 204
    }
}

205
////////////////////////////////cornerMinEigenVal//////////////////////////////////////////
206

207 208 209 210 211 212 213 214
struct CornerTestBase :
        public ImgprocTestBase
{
    virtual void random_roi()
    {
        Mat image = readImageType("gpu/stereobm/aloe-L.png", type);
        ASSERT_FALSE(image.empty());

215 216 217 218 219 220 221 222
        bool isFP = CV_MAT_DEPTH(type) >= CV_32F;
        float val = 255.0f;
        if (isFP)
        {
            image.convertTo(image, -1, 1.0 / 255);
            val /= 255.0f;
        }

223 224 225 226
        Size roiSize = image.size();
        Border srcBorder = randomBorder(0, useRoi ? MAX_VALUE : 0);

        Size wholeSize = Size(roiSize.width + srcBorder.lef + srcBorder.rig, roiSize.height + srcBorder.top + srcBorder.bot);
227
        src = randomMat(wholeSize, type, -val, val, false);
228 229 230 231 232 233 234 235 236 237 238 239
        src_roi = src(Rect(srcBorder.lef, srcBorder.top, roiSize.width, roiSize.height));
        image.copyTo(src_roi);

        Border dstBorder = randomBorder(0, useRoi ? MAX_VALUE : 0);
        randomSubMat(dst_whole, dst_roi, roiSize, dstBorder, CV_32FC1, 5, 16);

        generateOclMat(gsrc_whole, gsrc_roi, src, roiSize, srcBorder);
        generateOclMat(gdst_whole, gdst_roi, dst_whole, roiSize, dstBorder);
    }
};

typedef CornerTestBase CornerMinEigenVal;
niko's avatar
niko committed
240

241 242 243
OCL_TEST_P(CornerMinEigenVal, Mat)
{
    for (int j = 0; j < LOOP_TIMES; j++)
244
    {
245
        random_roi();
246

247
        int apertureSize = 3;
248

249 250
        cornerMinEigenVal(src_roi, dst_roi, blockSize, apertureSize, borderType);
        ocl::cornerMinEigenVal(gsrc_roi, gdst_roi, blockSize, apertureSize, borderType);
Ilya Lavrenov's avatar
Ilya Lavrenov committed
251

252
        Near(1e-5, true);
253 254 255
    }
}

256
////////////////////////////////cornerHarris//////////////////////////////////////////
257 258 259 260 261 262 263 264 265 266 267
struct CornerHarris :
    public ImgprocTestBase
{
    void Near(double threshold = 0.0)
    {
        Mat whole, roi;
        gdst_whole.download(whole);
        gdst_roi.download(roi);

        absdiff(whole, dst_whole, whole);
        absdiff(roi, dst_roi, roi);
268

269 270 271 272 273 274 275 276 277 278
        divide(whole, dst_whole, whole);
        divide(roi, dst_roi, roi);

        absdiff(dst_whole, dst_whole, dst_whole);
        absdiff(dst_roi, dst_roi, dst_roi);

        EXPECT_MAT_NEAR(dst_whole, whole, threshold);
        EXPECT_MAT_NEAR(dst_roi, roi, threshold);
    }
};
279

280
OCL_TEST_P(CornerHarris, Mat)
281
{
282
    for (int j = 0; j < LOOP_TIMES; j++)
283
    {
284
        random_roi();
285

286
        int apertureSize = 3;
287
        double k = randomDouble(0.01, 0.9);
288

289 290
        cornerHarris(src_roi, dst_roi, blockSize, apertureSize, k, borderType);
        ocl::cornerHarris(gsrc_roi, gdst_roi, blockSize, apertureSize, k, borderType);
291

Baichuan Su's avatar
Baichuan Su committed
292
        Near(1e-5);
293
    }
294
}
295

296
//////////////////////////////////integral/////////////////////////////////////////////////
297

298
typedef ImgprocTestBase Integral;
299

300
OCL_TEST_P(Integral, Mat1)
301
{
302
    for (int j = 0; j < LOOP_TIMES; j++)
303 304 305
    {
        random_roi();

306 307
        ocl::integral(gsrc_roi, gdst_roi);
        integral(src_roi, dst_roi);
308

309
        Near();
310 311 312
    }
}

313 314
// TODO wrong output type
OCL_TEST_P(Integral, DISABLED_Mat2)
315
{
316 317
    Mat dst1;
    ocl::oclMat gdst1;
318

319
    for (int j = 0; j < LOOP_TIMES; j++)
320 321 322
    {
        random_roi();

323 324
        integral(src_roi, dst1, dst_roi);
        ocl::integral(gsrc_roi, gdst1, gdst_roi);
325

326
        Near();
327 328 329
    }
}

330 331 332 333 334
///////////////////////////////////////////////////////////////////////////////////////////////////
//// threshold

struct Threshold :
        public ImgprocTestBase
335
{
336
    int thresholdType;
337 338 339

    virtual void SetUp()
    {
340 341 342 343
        type = GET_PARAM(0);
        blockSize = GET_PARAM(1);
        thresholdType = GET_PARAM(2);
        useRoi = GET_PARAM(3);
344 345 346
    }
};

347
OCL_TEST_P(Threshold, Mat)
348
{
349
    for (int j = 0; j < LOOP_TIMES; j++)
350 351 352
    {
        random_roi();

353 354
        double maxVal = randomDouble(20.0, 127.0);
        double thresh = randomDouble(0.0, maxVal);
355

356 357
        threshold(src_roi, dst_roi, thresh, maxVal, thresholdType);
        ocl::threshold(gsrc_roi, gdst_roi, thresh, maxVal, thresholdType);
358

359
        Near(1);
360 361 362
    }
}

363 364
/////////////////////////////////////////////////////////////////////////////////////////
// calcHist
365

366
static void calcHistGold(const Mat &src, Mat &hist)
niko's avatar
niko committed
367
{
368
    hist = Mat(1, 256, CV_32SC1, Scalar::all(0));
niko's avatar
niko committed
369

370
    int * const hist_row = hist.ptr<int>();
niko's avatar
niko committed
371 372
    for (int y = 0; y < src.rows; ++y)
    {
373
        const uchar * const src_row = src.ptr(y);
niko's avatar
niko committed
374 375 376 377 378 379

        for (int x = 0; x < src.cols; ++x)
            ++hist_row[src_row[x]];
    }
}

380
typedef ImgprocTestBase CalcHist;
381 382

OCL_TEST_P(CalcHist, Mat)
niko's avatar
niko committed
383
{
384
    for (int j = 0; j < LOOP_TIMES; j++)
niko's avatar
niko committed
385 386 387
    {
        random_roi();

388 389
        calcHistGold(src_roi, dst_roi);
        ocl::calcHist(gsrc_roi, gdst_roi);
niko's avatar
niko committed
390

391
        Near();
niko's avatar
niko committed
392 393
    }
}
394

395 396
/////////////////////////////////////////////////////////////////////////////////////////////////////////
//// CLAHE
397

398
PARAM_TEST_CASE(CLAHETest, Size, double, bool)
399
{
400
    Size gridSize;
401
    double clipLimit;
402
    bool useRoi;
403

404 405
    Mat src, dst_whole, src_roi, dst_roi;
    ocl::oclMat gsrc_whole, gsrc_roi, gdst_whole, gdst_roi;
406 407 408

    virtual void SetUp()
    {
yao's avatar
yao committed
409
        gridSize = GET_PARAM(0);
410
        clipLimit = GET_PARAM(1);
411
        useRoi = GET_PARAM(2);
412 413
    }

414
    void random_roi()
niko's avatar
niko committed
415
    {
416 417 418
        Size roiSize = randomSize(std::max(gridSize.height, gridSize.width), MAX_VALUE);
        Border srcBorder = randomBorder(0, useRoi ? MAX_VALUE : 0);
        randomSubMat(src, src_roi, roiSize, srcBorder, CV_8UC1, 5, 256);
niko's avatar
niko committed
419

420 421
        Border dstBorder = randomBorder(0, useRoi ? MAX_VALUE : 0);
        randomSubMat(dst_whole, dst_roi, roiSize, dstBorder, CV_8UC1, 5, 16);
niko's avatar
niko committed
422

423 424
        generateOclMat(gsrc_whole, gsrc_roi, src, roiSize, srcBorder);
        generateOclMat(gdst_whole, gdst_roi, dst_whole, roiSize, dstBorder);
niko's avatar
niko committed
425
    }
426 427

    void Near(double threshold = 0.0)
niko's avatar
niko committed
428
    {
429 430 431
        Mat whole, roi;
        gdst_whole.download(whole);
        gdst_roi.download(roi);
niko's avatar
niko committed
432

433 434 435
        EXPECT_MAT_NEAR(dst_whole, whole, threshold);
        EXPECT_MAT_NEAR(dst_roi, roi, threshold);
    }
niko's avatar
niko committed
436 437
};

438
OCL_TEST_P(CLAHETest, Accuracy)
niko's avatar
niko committed
439
{
440 441 442 443 444 445
    for (int i = 0; i < LOOP_TIMES; ++i)
    {
        random_roi();

        Ptr<CLAHE> clahe = ocl::createCLAHE(clipLimit, gridSize);
        clahe->apply(gsrc_roi, gdst_roi);
niko's avatar
niko committed
446

447 448
        Ptr<CLAHE> clahe_gold = createCLAHE(clipLimit, gridSize);
        clahe_gold->apply(src_roi, dst_roi);
449

450 451 452
        Near(1.0);
    }
}
niko's avatar
niko committed
453

454
/////////////////////////////Convolve//////////////////////////////////
niko's avatar
niko committed
455

456 457 458 459 460 461 462
static void convolve_gold(const Mat & src, const Mat & kernel, Mat & dst)
{
    for (int i = 0; i < src.rows; i++)
    {
        float * const dstptr = dst.ptr<float>(i);

        for (int j = 0; j < src.cols; j++)
niko's avatar
niko committed
463
        {
464
            float temp = 0;
465 466 467 468 469

            for (int m = 0; m < kernel.rows; m++)
            {
                const float * const kptr = kernel.ptr<float>(m);
                for (int n = 0; n < kernel.cols; n++)
niko's avatar
niko committed
470
                {
471 472 473 474
                    int r = clipInt(i - kernel.rows / 2 + m, 0, src.rows - 1);
                    int c = clipInt(j - kernel.cols / 2 + n, 0, src.cols - 1);

                    temp += src.ptr<float>(r)[c] * kptr[n];
niko's avatar
niko committed
475
                }
476 477 478
            }

            dstptr[j] = temp;
niko's avatar
niko committed
479
        }
480
    }
niko's avatar
niko committed
481
}
482

483 484
typedef ImgprocTestBase Convolve;

485
OCL_TEST_P(Convolve, Mat)
niko's avatar
niko committed
486
{
487 488 489 490 491
    Mat kernel, kernel_roi;
    ocl::oclMat gkernel, gkernel_roi;
    const Size roiSize(7, 7);

    for (int j = 0; j < LOOP_TIMES; j++)
niko's avatar
niko committed
492 493 494
    {
        random_roi();

495 496 497
        Border kernelBorder = randomBorder(0, useRoi ? MAX_VALUE : 0);
        randomSubMat(kernel, kernel_roi, roiSize, kernelBorder, type, 5, 16);
        generateOclMat(gkernel, gkernel_roi, kernel, roiSize, kernelBorder);
498

499 500
        convolve_gold(src_roi, kernel_roi, dst_roi);
        ocl::convolve(gsrc_roi, gkernel_roi, gdst_roi);
niko's avatar
niko committed
501

502
        Near(1);
niko's avatar
niko committed
503 504
    }
}
niko's avatar
niko committed
505

506
////////////////////////////////// ColumnSum //////////////////////////////////////
507

508
static void columnSum_gold(const Mat & src, Mat & dst)
yao's avatar
yao committed
509
{
510 511
    float * prevdptr = dst.ptr<float>(0);
    const float * sptr = src.ptr<float>(0);
yao's avatar
yao committed
512

513 514 515 516
    for (int x = 0; x < src.cols; ++x)
        prevdptr[x] = sptr[x];

    for (int y = 1; y < src.rows; ++y)
yao's avatar
yao committed
517
    {
518 519
        sptr = src.ptr<float>(y);
        float * const dptr = dst.ptr<float>(y);
yao's avatar
yao committed
520

521 522
        for (int x = 0; x < src.cols; ++x)
            dptr[x] = prevdptr[x] + sptr[x];
yao's avatar
yao committed
523

524 525 526
        prevdptr = dptr;
    }
}
yao's avatar
yao committed
527

528
typedef ImgprocTestBase ColumnSum;
yao's avatar
yao committed
529

530 531 532
OCL_TEST_P(ColumnSum, Accuracy)
{
    for (int i = 0; i < LOOP_TIMES; ++i)
yao's avatar
yao committed
533
    {
534
        random_roi();
yao's avatar
yao committed
535

536 537 538 539
        columnSum_gold(src_roi, dst_roi);
        ocl::columnSum(gsrc_roi, gdst_roi);

        Near(1e-5);
yao's avatar
yao committed
540 541
    }
}
542

yao's avatar
yao committed
543 544
/////////////////////////////////////////////////////////////////////////////////////

545 546 547 548 549
INSTANTIATE_TEST_CASE_P(Imgproc, EqualizeHist, Combine(
                            Values((MatType)CV_8UC1),
                            Values(0), // not used
                            Values(0), // not used
                            Bool()));
niko's avatar
niko committed
550

551
INSTANTIATE_TEST_CASE_P(Imgproc, CornerMinEigenVal, Combine(
552 553 554
                            Values((MatType)CV_8UC1, (MatType)CV_32FC1),
                            Values(3, 5),
                            Values((int)BORDER_CONSTANT, (int)BORDER_REPLICATE, (int)BORDER_REFLECT, (int)BORDER_REFLECT101),
555 556 557
                            Bool()));

INSTANTIATE_TEST_CASE_P(Imgproc, CornerHarris, Combine(
558
                            Values((MatType)CV_8UC1, CV_32FC1),
559
                            Values(3, 5),
560
                            Values( (int)BORDER_CONSTANT, (int)BORDER_REPLICATE, (int)BORDER_REFLECT, (int)BORDER_REFLECT_101),
561 562 563
                            Bool()));

INSTANTIATE_TEST_CASE_P(Imgproc, Integral, Combine(
564
                            Values((MatType)CV_8UC1), // TODO does not work with CV_32F, CV_64F
565
                            Values(0), // not used
566
                            Values(0), // not used
567
                            Bool()));
568 569

INSTANTIATE_TEST_CASE_P(Imgproc, Threshold, Combine(
570 571 572
                            Values(CV_8UC1, CV_8UC2, CV_8UC3, CV_8UC4,
                                   CV_16SC1, CV_16SC2, CV_16SC3, CV_16SC4,
                                   CV_32FC1, CV_32FC2, CV_32FC3, CV_32FC4),
573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600
                            Values(0),
                            Values(ThreshOp(THRESH_BINARY),
                                   ThreshOp(THRESH_BINARY_INV), ThreshOp(THRESH_TRUNC),
                                   ThreshOp(THRESH_TOZERO), ThreshOp(THRESH_TOZERO_INV)),
                            Bool()));

INSTANTIATE_TEST_CASE_P(Imgproc, CalcHist, Combine(
                            Values((MatType)CV_8UC1),
                            Values(0), // not used
                            Values(0), // not used
                            Bool()));

INSTANTIATE_TEST_CASE_P(Imgproc, CLAHETest, Combine(
                            Values(Size(4, 4), Size(32, 8), Size(8, 64)),
                            Values(0.0, 10.0, 62.0, 300.0),
                            Bool()));

INSTANTIATE_TEST_CASE_P(Imgproc, Convolve, Combine(
                            Values((MatType)CV_32FC1),
                            Values(0), // not used
                            Values(0), // not used
                            Bool()));

INSTANTIATE_TEST_CASE_P(Imgproc, ColumnSum, Combine(
                            Values(MatType(CV_32FC1)),
                            Values(0), // not used
                            Values(0), // not used
                            Bool()));
601

602
INSTANTIATE_TEST_CASE_P(ImgprocTestBase, CopyMakeBorder, Combine(
603 604
                            testing::Values((MatDepth)CV_8U, (MatDepth)CV_16S, (MatDepth)CV_32S, (MatDepth)CV_32F),
                            testing::Values(Channels(1), Channels(3), (Channels)4),
605
                            Bool(), // border isolated or not
606 607
                            Values((Border)BORDER_REPLICATE, (Border)BORDER_REFLECT,
                                   (Border)BORDER_WRAP, (Border)BORDER_REFLECT_101),
608
                            Bool()));
yao's avatar
yao committed
609

610
#endif // HAVE_OPENCL