"Fossies" - the Fresh Open Source Software Archive

Member "rawtherapee-5.7/rtengine/iplab2rgb.cc" (10 Sep 2019, 18802 Bytes) of package /linux/misc/rawtherapee-5.7.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "iplab2rgb.cc" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 5.6_vs_5.7.

    1 /*
    2  *  This file is part of RawTherapee.
    3  *
    4  *  Copyright (c) 2004-2010 Gabor Horvath <hgabor@rawtherapee.com>
    5  *
    6  *  RawTherapee is free software: you can redistribute it and/or modify
    7  *  it under the terms of the GNU General Public License as published by
    8  *  the Free Software Foundation, either version 3 of the License, or
    9  *  (at your option) any later version.
   10  *
   11  *  RawTherapee is distributed in the hope that it will be useful,
   12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
   13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   14  *  GNU General Public License for more details.
   15  *
   16  *  You should have received a copy of the GNU General Public License
   17  *  along with RawTherapee.  If not, see <https://www.gnu.org/licenses/>.
   18  */
   19 #include "rtengine.h"
   20 #include "improcfun.h"
   21 #include <glibmm.h>
   22 #include "iccstore.h"
   23 #include "iccmatrices.h"
   24 #include "../rtgui/options.h"
   25 #include "settings.h"
   26 #include "curves.h"
   27 #include "alignedbuffer.h"
   28 #include "color.h"
   29 #include "procparams.h"
   30 
   31 namespace rtengine
   32 {
   33 
   34 extern void filmlike_clip(float *r, float *g, float *b);
   35 
   36 extern const Settings* settings;
   37 
   38 namespace {
   39 
   40 inline void copyAndClampLine(const float *src, unsigned char *dst, const int W)
   41 {
   42     for (int j = 0, iy = 0; j < W; ++j) {
   43         float r = src[iy] * MAXVALF;
   44         float g = src[iy+1] * MAXVALF;
   45         float b = src[iy+2] * MAXVALF;
   46         dst[iy] = uint16ToUint8Rounded(CLIP(r));
   47         dst[iy+1] = uint16ToUint8Rounded(CLIP(g));
   48         dst[iy+2] = uint16ToUint8Rounded(CLIP(b));
   49         iy += 3;
   50     }
   51 }
   52 
   53 
   54 inline void copyAndClamp(const LabImage *src, unsigned char *dst, const double rgb_xyz[3][3], bool multiThread)
   55 {
   56     int W = src->W;
   57     int H = src->H;
   58 
   59 #ifdef _OPENMP
   60         #pragma omp parallel for schedule(dynamic,16) if (multiThread)
   61 #endif
   62     for (int i = 0; i < H; ++i) {
   63         float* rL = src->L[i];
   64         float* ra = src->a[i];
   65         float* rb = src->b[i];
   66         int ix = i * 3 * W;
   67 
   68         float R, G, B;
   69         float x_, y_, z_;
   70 
   71         for (int j = 0; j < W; ++j) {
   72             Color::Lab2XYZ(rL[j], ra[j], rb[j], x_, y_, z_ );
   73             Color::xyz2rgb(x_, y_, z_, R, G, B, rgb_xyz);
   74 
   75             dst[ix++] = uint16ToUint8Rounded(Color::gamma2curve[R]);
   76             dst[ix++] = uint16ToUint8Rounded(Color::gamma2curve[G]);
   77             dst[ix++] = uint16ToUint8Rounded(Color::gamma2curve[B]);
   78         }
   79     }
   80 }
   81 
   82 } // namespace
   83 
   84 // Used in ImProcCoordinator::updatePreviewImage  (rtengine/improccoordinator.cc)
   85 //         Crop::update                           (rtengine/dcrop.cc)
   86 //         Thumbnail::processImage                (rtengine/rtthumbnail.cc)
   87 //
   88 // If monitorTransform, divide by 327.68 then apply monitorTransform (which can integrate soft-proofing)
   89 // otherwise divide by 327.68, convert to xyz and apply the sRGB transform, before converting with gamma2curve
   90 void ImProcFunctions::lab2monitorRgb(LabImage* lab, Image8* image)
   91 {
   92     if (monitorTransform) {
   93 
   94         int W = lab->W;
   95         int H = lab->H;
   96         unsigned char * data = image->data;
   97 
   98         // cmsDoTransform is relatively expensive
   99 #ifdef _OPENMP
  100         #pragma omp parallel firstprivate(lab, data, W, H)
  101 #endif
  102         {
  103             AlignedBuffer<float> pBuf(3 * lab->W);
  104             AlignedBuffer<float> mBuf(3 * lab->W);
  105 
  106             AlignedBuffer<float> gwBuf1;
  107             AlignedBuffer<float> gwBuf2;
  108 
  109             if (gamutWarning) {
  110                 gwBuf1.resize(3 * lab->W);
  111                 gwBuf2.resize(3 * lab->W);
  112             }
  113 
  114             float *buffer = pBuf.data;
  115             float *outbuffer = mBuf.data;
  116 
  117 #ifdef _OPENMP
  118             #pragma omp for schedule(dynamic,16)
  119 #endif
  120 
  121             for (int i = 0; i < H; i++) {
  122 
  123                 const int ix = i * 3 * W;
  124                 int iy = 0;
  125 
  126                 float* rL = lab->L[i];
  127                 float* ra = lab->a[i];
  128                 float* rb = lab->b[i];
  129 
  130                 for (int j = 0; j < W; j++) {
  131                     buffer[iy++] = rL[j] / 327.68f;
  132                     buffer[iy++] = ra[j] / 327.68f;
  133                     buffer[iy++] = rb[j] / 327.68f;
  134                 }
  135 
  136                 cmsDoTransform (monitorTransform, buffer, outbuffer, W);
  137                 copyAndClampLine(outbuffer, data + ix, W);
  138 
  139                 if (gamutWarning) {
  140                     gamutWarning->markLine(image, i, buffer, gwBuf1.data, gwBuf2.data);
  141                 }
  142             }
  143         } // End of parallelization
  144     } else {
  145         copyAndClamp(lab, image->data, sRGB_xyz, multiThread);
  146     }
  147 }
  148 
  149 
  150 
  151 // Used in ImProcCoordinator::updatePreviewImage  (rtengine/improccoordinator.cc)
  152 //         Crop::update                           (rtengine/dcrop.cc)
  153 //
  154 // Generate an Image8
  155 //
  156 // If output profile used, divide by 327.68 then apply the "profile" profile (eventually with a standard gamma)
  157 // otherwise divide by 327.68, convert to xyz and apply the RGB transform, before converting with gamma2curve
  158 Image8* ImProcFunctions::lab2rgb(LabImage* lab, int cx, int cy, int cw, int ch, const procparams::ColorManagementParams &icm, bool consider_histogram_settings)
  159 {
  160     //gamutmap(lab);
  161 
  162     if (cx < 0) {
  163         cx = 0;
  164     }
  165 
  166     if (cy < 0) {
  167         cy = 0;
  168     }
  169 
  170     if (cx + cw > lab->W) {
  171         cw = lab->W - cx;
  172     }
  173 
  174     if (cy + ch > lab->H) {
  175         ch = lab->H - cy;
  176     }
  177 
  178     Image8* image = new Image8(cw, ch);
  179     Glib::ustring profile;
  180 
  181     bool standard_gamma;
  182 
  183     if (settings->HistogramWorking && consider_histogram_settings) {
  184         profile = icm.workingProfile;
  185         standard_gamma = true;
  186     } else {
  187         profile = icm.outputProfile;
  188 
  189         if (icm.outputProfile.empty() || icm.outputProfile == ColorManagementParams::NoICMString) {
  190             profile = "sRGB";
  191         }
  192 
  193         standard_gamma = false;
  194     }
  195 
  196     cmsHPROFILE oprof = ICCStore::getInstance()->getProfile(profile);
  197 
  198     if (oprof) {
  199         cmsHPROFILE oprofG = oprof;
  200 
  201         if (standard_gamma) {
  202             oprofG = ICCStore::makeStdGammaProfile(oprof);
  203         }
  204 
  205         cmsUInt32Number flags = cmsFLAGS_NOOPTIMIZE | cmsFLAGS_NOCACHE;
  206 
  207         if (icm.outputBPC) {
  208             flags |= cmsFLAGS_BLACKPOINTCOMPENSATION;
  209         }
  210 
  211         lcmsMutex->lock();
  212         cmsHPROFILE LabIProf  = cmsCreateLab4Profile(nullptr);
  213         cmsHTRANSFORM hTransform = cmsCreateTransform (LabIProf, TYPE_Lab_DBL, oprofG, TYPE_RGB_FLT, icm.outputIntent, flags);  // NOCACHE is important for thread safety
  214         cmsCloseProfile(LabIProf);
  215         lcmsMutex->unlock();
  216 
  217         unsigned char *data = image->data;
  218 
  219         // cmsDoTransform is relatively expensive
  220 #ifdef _OPENMP
  221         #pragma omp parallel
  222 #endif
  223         {
  224             AlignedBuffer<double> pBuf(3 * cw);
  225             AlignedBuffer<float> oBuf(3 * cw);
  226             double *buffer = pBuf.data;
  227             float *outbuffer = oBuf.data;
  228             int condition = cy + ch;
  229 
  230 #ifdef _OPENMP
  231             #pragma omp for firstprivate(lab) schedule(dynamic,16)
  232 #endif
  233 
  234             for (int i = cy; i < condition; i++) {
  235                 const int ix = i * 3 * cw;
  236                 int iy = 0;
  237                 float* rL = lab->L[i];
  238                 float* ra = lab->a[i];
  239                 float* rb = lab->b[i];
  240 
  241                 for (int j = cx; j < cx + cw; j++) {
  242                     buffer[iy++] = rL[j] / 327.68f;
  243                     buffer[iy++] = ra[j] / 327.68f;
  244                     buffer[iy++] = rb[j] / 327.68f;
  245                 }
  246 
  247                 cmsDoTransform (hTransform, buffer, outbuffer, cw);
  248                 copyAndClampLine(outbuffer, data + ix, cw);
  249             }
  250         } // End of parallelization
  251 
  252         cmsDeleteTransform(hTransform);
  253 
  254         if (oprofG != oprof) {
  255             cmsCloseProfile(oprofG);
  256         }
  257     } else {
  258         const auto xyz_rgb = ICCStore::getInstance()->workingSpaceInverseMatrix(profile);
  259         copyAndClamp(lab, image->data, xyz_rgb, multiThread);
  260     }
  261 
  262     return image;
  263 }
  264 
  265 
  266 /** @brief Convert the final Lab image to the output RGB color space
  267  *
  268  * Used in processImage   (rtengine/simpleprocess.cc)
  269  *
  270  * Provide a pointer to a 7 floats array for "ga" (uninitialized ; this array will be filled with the gamma values) if you want
  271  * to use the custom gamma scenario. Those gamma values will correspond to the ones of the chosen standard output profile
  272  * (Prophoto if non standard output profile given)
  273  *
  274  * If "ga" is NULL, then we're considering standard gamma with the chosen output profile.
  275  *
  276  * Generate an Image16
  277  *
  278  * If a custom gamma profile can be created, divide by 327.68, convert to xyz and apply the custom gamma transform
  279  * otherwise divide by 327.68, convert to xyz and apply the sRGB transform, before converting with gamma2curve
  280  */
  281 Imagefloat* ImProcFunctions::lab2rgbOut(LabImage* lab, int cx, int cy, int cw, int ch, const procparams::ColorManagementParams &icm)
  282 {
  283 
  284     if (cx < 0) {
  285         cx = 0;
  286     }
  287 
  288     if (cy < 0) {
  289         cy = 0;
  290     }
  291 
  292     if (cx + cw > lab->W) {
  293         cw = lab->W - cx;
  294     }
  295 
  296     if (cy + ch > lab->H) {
  297         ch = lab->H - cy;
  298     }
  299 
  300     Imagefloat* image = new Imagefloat(cw, ch);
  301     cmsHPROFILE oprof = ICCStore::getInstance()->getProfile(icm.outputProfile);
  302 
  303     if (oprof) {
  304         cmsUInt32Number flags = cmsFLAGS_NOOPTIMIZE | cmsFLAGS_NOCACHE;
  305 
  306         if (icm.outputBPC) {
  307             flags |= cmsFLAGS_BLACKPOINTCOMPENSATION;
  308         }
  309 
  310         lcmsMutex->lock();
  311         cmsHPROFILE iprof = cmsCreateLab4Profile(nullptr);
  312         cmsHTRANSFORM hTransform = cmsCreateTransform(iprof, TYPE_Lab_FLT, oprof, TYPE_RGB_FLT, icm.outputIntent, flags);
  313         lcmsMutex->unlock();
  314 
  315         image->ExecCMSTransform(hTransform, *lab, cx, cy);
  316         cmsDeleteTransform(hTransform);
  317         image->normalizeFloatTo65535();
  318     } else {
  319         
  320 #ifdef _OPENMP
  321         #pragma omp parallel for schedule(dynamic,16) if (multiThread)
  322 #endif
  323 
  324         for (int i = cy; i < cy + ch; i++) {
  325             float R, G, B;
  326             float* rL = lab->L[i];
  327             float* ra = lab->a[i];
  328             float* rb = lab->b[i];
  329 
  330             for (int j = cx; j < cx + cw; j++) {
  331 
  332                 float fy = (Color::c1By116 * rL[j]) / 327.68f + Color::c16By116; // (L+16)/116
  333                 float fx = (0.002f * ra[j]) / 327.68f + fy;
  334                 float fz = fy - (0.005f * rb[j]) / 327.68f;
  335                 float LL = rL[j] / 327.68f;
  336 
  337                 float x_ = 65535.0f * Color::f2xyz(fx) * Color::D50x;
  338                 //float y_ = 65535.0 * Color::f2xyz(fy);
  339                 float z_ = 65535.0f * Color::f2xyz(fz) * Color::D50z;
  340                 float y_ = (LL > (float)Color::epskap) ? 65535.0f * fy * fy * fy : 65535.0f * LL / (float)Color::kappa;
  341 
  342                 Color::xyz2srgb(x_, y_, z_, R, G, B);
  343 
  344                 image->r(i - cy, j - cx) = Color::gamma2curve[CLIP(R)];
  345                 image->g(i - cy, j - cx) = Color::gamma2curve[CLIP(G)];
  346                 image->b(i - cy, j - cx) = Color::gamma2curve[CLIP(B)];
  347             }
  348         }
  349     }
  350 
  351     return image;
  352 }
  353 
  354 
  355 void ImProcFunctions::workingtrc(const Imagefloat* src, Imagefloat* dst, int cw, int ch, int mul, const Glib::ustring &profile, double gampos, double slpos, cmsHTRANSFORM &transform, bool normalizeIn, bool normalizeOut, bool keepTransForm) const
  356 {
  357     const TMatrix wprof = ICCStore::getInstance()->workingSpaceMatrix(params->icm.workingProfile);
  358 
  359     const float toxyz[3][3] = {
  360         {
  361             static_cast<float>(wprof[0][0] / ((normalizeIn ? 65535.0 : 1.0))), //I have suppressed / Color::D50x
  362             static_cast<float>(wprof[0][1] / ((normalizeIn ? 65535.0 : 1.0))),
  363             static_cast<float>(wprof[0][2] / ((normalizeIn ? 65535.0 : 1.0)))
  364         }, {
  365             static_cast<float>(wprof[1][0] / (normalizeIn ? 65535.0 : 1.0)),
  366             static_cast<float>(wprof[1][1] / (normalizeIn ? 65535.0 : 1.0)),
  367             static_cast<float>(wprof[1][2] / (normalizeIn ? 65535.0 : 1.0))
  368         }, {
  369             static_cast<float>(wprof[2][0] / ((normalizeIn ? 65535.0 : 1.0))), //I have suppressed / Color::D50z
  370             static_cast<float>(wprof[2][1] / ((normalizeIn ? 65535.0 : 1.0))),
  371             static_cast<float>(wprof[2][2] / ((normalizeIn ? 65535.0 : 1.0)))
  372         }
  373     };
  374 
  375     cmsHTRANSFORM hTransform = nullptr;
  376     if (transform) {
  377         hTransform = transform;
  378     } else {
  379 
  380         double pwr = 1.0 / gampos;
  381         double ts = slpos;
  382         int five = mul;
  383 
  384 
  385         if (gampos < 1.0) {
  386             pwr = gampos;
  387             gampos = 1. / gampos;
  388             five = -mul;
  389         }
  390 
  391         //  int select_temp = 1; //5003K
  392         constexpr double eps = 0.000000001; // not divide by zero
  393 
  394         enum class ColorTemp {
  395             D50 = 5003, // for Widegamut, ProPhoto Best, Beta -> D50
  396             D65 = 6504, // for sRGB, AdobeRGB, Bruce Rec2020  -> D65
  397             D60 = 6005  // for ACES AP0 and AP1
  398 
  399         };
  400         ColorTemp temp = ColorTemp::D50;
  401 
  402         float p[6]; //primaries
  403 
  404         //primaries for 10 working profiles ==> output profiles
  405         if (profile == "WideGamut") {
  406             p[0] = 0.7350;    //Widegamut primaries
  407             p[1] = 0.2650;
  408             p[2] = 0.1150;
  409             p[3] = 0.8260;
  410             p[4] = 0.1570;
  411             p[5] = 0.0180;
  412         } else if (profile == "Adobe RGB") {
  413             p[0] = 0.6400;    //Adobe primaries
  414             p[1] = 0.3300;
  415             p[2] = 0.2100;
  416             p[3] = 0.7100;
  417             p[4] = 0.1500;
  418             p[5] = 0.0600;
  419             temp = ColorTemp::D65;
  420         } else if (profile == "sRGB") {
  421             p[0] = 0.6400;    // sRGB primaries
  422             p[1] = 0.3300;
  423             p[2] = 0.3000;
  424             p[3] = 0.6000;
  425             p[4] = 0.1500;
  426             p[5] = 0.0600;
  427             temp = ColorTemp::D65;
  428         } else if (profile == "BruceRGB") {
  429             p[0] = 0.6400;    // Bruce primaries
  430             p[1] = 0.3300;
  431             p[2] = 0.2800;
  432             p[3] = 0.6500;
  433             p[4] = 0.1500;
  434             p[5] = 0.0600;
  435             temp = ColorTemp::D65;
  436         } else if (profile == "Beta RGB") {
  437             p[0] = 0.6888;    // Beta primaries
  438             p[1] = 0.3112;
  439             p[2] = 0.1986;
  440             p[3] = 0.7551;
  441             p[4] = 0.1265;
  442             p[5] = 0.0352;
  443         } else if (profile == "BestRGB") {
  444             p[0] = 0.7347;    // Best primaries
  445             p[1] = 0.2653;
  446             p[2] = 0.2150;
  447             p[3] = 0.7750;
  448             p[4] = 0.1300;
  449             p[5] = 0.0350;
  450         } else if (profile == "Rec2020") {
  451             p[0] = 0.7080;    // Rec2020 primaries
  452             p[1] = 0.2920;
  453             p[2] = 0.1700;
  454             p[3] = 0.7970;
  455             p[4] = 0.1310;
  456             p[5] = 0.0460;
  457             temp = ColorTemp::D65;
  458         } else if (profile == "ACESp0") {
  459             p[0] = 0.7347;    // ACES P0 primaries
  460             p[1] = 0.2653;
  461             p[2] = 0.0000;
  462             p[3] = 1.0;
  463             p[4] = 0.0001;
  464             p[5] = -0.0770;
  465             temp = ColorTemp::D60;
  466         } else if (profile == "ACESp1") {
  467             p[0] = 0.713;    // ACES P1 primaries
  468             p[1] = 0.293;
  469             p[2] = 0.165;
  470             p[3] = 0.830;
  471             p[4] = 0.128;
  472             p[5] = 0.044;
  473             temp = ColorTemp::D60;
  474         } else if (profile == "ProPhoto") {
  475             p[0] = 0.7347;    //ProPhoto and default primaries
  476             p[1] = 0.2653;
  477             p[2] = 0.1596;
  478             p[3] = 0.8404;
  479             p[4] = 0.0366;
  480             p[5] = 0.0001;
  481         } else {
  482             p[0] = 0.7347;    //default primaries always unused
  483             p[1] = 0.2653;
  484             p[2] = 0.1596;
  485             p[3] = 0.8404;
  486             p[4] = 0.0366;
  487             p[5] = 0.0001;
  488         }
  489 
  490         if (slpos == 0) {
  491             slpos = eps;
  492         }
  493 
  494         GammaValues g_a; //gamma parameters
  495         constexpr int mode = 0;
  496         Color::calcGamma(pwr, ts, mode, g_a); // call to calcGamma with selected gamma and slope : return parameters for LCMS2
  497 
  498 
  499         cmsFloat64Number gammaParams[7];
  500         gammaParams[4] = g_a[3] * ts;
  501         gammaParams[0] = gampos;
  502         gammaParams[1] = 1. / (1.0 + g_a[4]);
  503         gammaParams[2] = g_a[4] / (1.0 + g_a[4]);
  504         gammaParams[3] = 1. / slpos;
  505         gammaParams[5] = 0.0;
  506         gammaParams[6] = 0.0;
  507        // printf("ga0=%f ga1=%f ga2=%f ga3=%f ga4=%f\n", ga0, ga1, ga2, ga3, ga4);
  508 
  509         // 7 parameters for smoother curves
  510         cmsCIExyY xyD;
  511         cmsWhitePointFromTemp(&xyD, (double)temp);
  512         if (profile == "ACESp0") {
  513             xyD = {0.32168, 0.33767, 1.0};//refine white point to avoid differences
  514         }
  515 
  516         cmsToneCurve* GammaTRC[3];
  517         GammaTRC[0] = GammaTRC[1] = GammaTRC[2] = cmsBuildParametricToneCurve(NULL, five, gammaParams);//5 = more smoother than 4
  518 
  519         const cmsCIExyYTRIPLE Primaries = {
  520             {p[0], p[1], 1.0}, // red
  521             {p[2], p[3], 1.0}, // green
  522             {p[4], p[5], 1.0}  // blue
  523         };
  524         const cmsHPROFILE oprofdef = cmsCreateRGBProfile(&xyD, &Primaries, GammaTRC);
  525         cmsFreeToneCurve(GammaTRC[0]);
  526 
  527         if (oprofdef) {
  528             constexpr cmsUInt32Number flags = cmsFLAGS_NOOPTIMIZE | cmsFLAGS_NOCACHE;
  529             const cmsHPROFILE iprof = ICCStore::getInstance()->getXYZProfile();
  530             lcmsMutex->lock();
  531             hTransform = cmsCreateTransform(iprof, TYPE_RGB_FLT, oprofdef, TYPE_RGB_FLT, params->icm.outputIntent, flags);
  532             lcmsMutex->unlock();
  533         }
  534     }
  535     if (hTransform) {
  536 #ifdef _OPENMP
  537         #pragma omp parallel if (multiThread)
  538 #endif
  539         {
  540             AlignedBuffer<float> pBuf(cw * 3);
  541             const float normalize = normalizeOut ? 65535.f : 1.f;
  542 
  543 #ifdef _OPENMP
  544             #pragma omp for schedule(dynamic, 16) nowait
  545 #endif
  546 
  547             for (int i = 0; i < ch; ++i) {
  548                 float *p = pBuf.data;
  549                 for (int j = 0; j < cw; ++j) {
  550                     const float r = src->r(i, j);
  551                     const float g = src->g(i, j);
  552                     const float b = src->b(i, j);
  553 
  554                     *(p++) = toxyz[0][0] * r + toxyz[0][1] * g + toxyz[0][2] * b;
  555                     *(p++) = toxyz[1][0] * r + toxyz[1][1] * g + toxyz[1][2] * b;
  556                     *(p++) = toxyz[2][0] * r + toxyz[2][1] * g + toxyz[2][2] * b;
  557                 }
  558                 p = pBuf.data;
  559                 cmsDoTransform(hTransform, p, p, cw);
  560                 for (int j = 0; j < cw; ++j) {
  561                     dst->r(i, j) = *(p++) * normalize;
  562                     dst->g(i, j) = *(p++) * normalize;
  563                     dst->b(i, j) = *(p++) * normalize;
  564                 }
  565             }
  566         }
  567         if (!keepTransForm) {
  568             cmsDeleteTransform(hTransform);
  569             hTransform = nullptr;
  570         }
  571         transform = hTransform;
  572     }
  573 }
  574 
  575 
  576 }