Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-18 10:11:08

0001 #ifndef TMVA_SOFIE_ROPERATOR_POOL
0002 #define TMVA_SOFIE_ROPERATOR_POOL
0003 
0004 #include "TMVA/SOFIE_common.hxx"
0005 #include "TMVA/ROperator.hxx"
0006 #include "TMVA/RModel.hxx"
0007 
0008 #include <memory>
0009 #include <sstream>
0010 #include <algorithm>
0011 #include <stdexcept>
0012 #include <vector>
0013 #include <cassert>
0014 
0015 namespace TMVA {
0016 namespace Experimental {
0017 namespace SOFIE {
0018 
0019 struct RAttributes_Pool {
0020    // structure that contains Pool attribute
0021    std::string auto_pad = "NOTSET";
0022    int ceil_mode = 0;
0023    int count_include_pad = 0;     // not for MaxPool
0024    int storage_order = 0;         // not for AveragePool
0025    std::vector<size_t> dilations; // not for AveragePool
0026    std::vector<size_t> kernel_shape;
0027    std::vector<size_t> pads;
0028    std::vector<size_t> strides;
0029 };
0030 
0031 enum PoolOpMode { InvalidPool, MaxPool, AveragePool, GlobalAveragePool };
0032 
0033 template<typename T>
0034 class ROperator_Pool final : public ROperator
0035 {
0036 
0037 private:
0038 
0039    PoolOpMode fPoolMode;
0040 
0041    size_t fAttrCeilMode;
0042    size_t fAttrCountIncludePad;
0043    size_t fAttrStorageOrder;
0044    std::string fAttrAutopad;
0045    std::vector<size_t> fAttrDilations;
0046    std::vector<size_t> fAttrKernelShape;
0047    std::vector<size_t> fAttrPads;
0048    std::vector<size_t> fAttrStrides;
0049 
0050    std::string fNX;
0051    std::string fNY;
0052 
0053    std::vector<size_t> fShapeX;
0054    std::vector<size_t> fShapeY;
0055 
0056    std::string fType;
0057 
0058    size_t fDim;   // dimension of the MaxPool
0059    bool fUseSession = false;
0060 
0061 public:
0062 
0063    std::string Name() {
0064       if (fPoolMode == AveragePool)  return "AveragePool";
0065       if (fPoolMode == MaxPool)  return "MaxPool";
0066       return "Invalid";
0067    }
0068 
0069    ROperator_Pool() {}
0070 
0071    ROperator_Pool(PoolOpMode mode, RAttributes_Pool attr, std::string nameX, std::string nameY)
0072       : fPoolMode(mode), fAttrCeilMode(attr.ceil_mode), fAttrCountIncludePad(attr.count_include_pad),
0073         fAttrStorageOrder(attr.storage_order), fAttrAutopad(attr.auto_pad),
0074         fAttrDilations(attr.dilations), fAttrKernelShape(attr.kernel_shape), fAttrPads(attr.pads), fAttrStrides(attr.strides),
0075         fNX(UTILITY::Clean_name(nameX)), fNY(UTILITY::Clean_name(nameY))
0076    {
0077       if(std::is_same<T, float>::value) {
0078          fType = "float";
0079       } else {
0080          throw
0081             std::runtime_error("TMVA SOFIE Encountered unsupported type parsing a Pool operator");
0082       }
0083    }
0084 
0085    // return input type (defined abstract in ROperator class )
0086    std::vector<ETensorType> TypeInference(std::vector<ETensorType> input) {
0087       // only one input in Pool operators
0088       return input;
0089    }
0090 
0091    // function returning output shape given input
0092    std::vector<std::vector<size_t>> ShapeInference(std::vector<std::vector<size_t>> input) {
0093       // shape of pooling input has to be (according to ONNX): NxCxHxW
0094       // Where N is batch size, C : input  channels, H : input height, W = input width
0095       // or it can be [N, C, F1,F2,....FN] . Minimum dimension is 3
0096       if (input.size() != 1 ) {
0097          throw std::runtime_error("TMVA SOFIE" + Name() + "Op Shape inference need 1 input tensor");
0098       }
0099       if (input[0].size() < 3) {
0100          throw std::runtime_error("TMVA SOFIE" + Name() + "Op Shape inference only accept tensor with at least 3 dimensions");
0101       }
0102       // support only input tensors with dim = 3,4,5
0103       if (input[0].size() < 3 || input[0].size() >  5) {
0104          throw std::runtime_error("TMVA SOFIE" + Name() + "Op : tensors with dimension " + std::to_string(input[0].size()) + " are not yet supported");
0105       }
0106 
0107       if (input[0].size() -2 != fDim) {
0108          throw
0109             std::runtime_error("TMVA SOFIE Pool Op Shape inference - invalid inputs ");
0110       }
0111        // kernel shape
0112       size_t k1 = ((fAttrKernelShape.empty())? input[0][2] : fAttrKernelShape[0]);
0113       size_t k2 = (fDim > 1) ? ((fAttrKernelShape.empty()) ? input[0][3] : fAttrKernelShape[1]) : 1;
0114       size_t k3 = (fDim > 2) ? ((fAttrKernelShape.empty()) ? input[0][4] : fAttrKernelShape[2]) : 1;
0115 
0116 
0117       size_t i1 = (fDim > 1) ? ((fDim > 2) ? 3 : 2) : 1;
0118       size_t i2 = (fDim > 2) ? 4 : 3;
0119       size_t i3 = 5;
0120 
0121       if (fAttrDilations.empty()) {
0122          fAttrDilations = {1, 1, 1};
0123       }
0124       fAttrDilations.resize(3);
0125       if (fDim < 3) {
0126          fAttrDilations.resize(3, 1);
0127       }
0128            // Shape of the kernel
0129       fAttrKernelShape = {k1 + (fAttrDilations[0] - 1) * (k1 - 1),
0130                           k2 + (fAttrDilations[1] - 1) * (k2 - 1),
0131                           k3 + (fAttrDilations[2] - 1) * (k3 - 1)};
0132 
0133       if (fAttrAutopad == "NOTSET") {
0134          // in auto_pad is NOTSET then fAttrPads should have been set or default zero is used
0135          if (fAttrPads.empty()) {
0136             fAttrPads = {0, 0, 0, 0, 0, 0};
0137          }
0138       } else if (fAttrAutopad == "SAME_UPPER" || fAttrAutopad == "SAME_LOWER") {
0139          if (fDim == 1)
0140             fAttrPads = {fAttrKernelShape[0] / 2, fAttrKernelShape[0] / 2};
0141          else if (fDim == 2)
0142             fAttrPads = {fAttrKernelShape[0] / 2, fAttrKernelShape[1] / 2, fAttrKernelShape[0] / 2, fAttrKernelShape[1] / 2};
0143          else if (fDim == 3)
0144             fAttrPads = {fAttrKernelShape[0] / 2, fAttrKernelShape[1] / 2, fAttrKernelShape[2] / 2,
0145                          fAttrKernelShape[0] / 2, fAttrKernelShape[1] / 2, fAttrKernelShape[2] / 2};
0146          // add extra padding at beginning or end (depending if SAME_UPPER or SAME_LOWER)
0147          // need to check this!
0148          if (fAttrKernelShape[0] % 2 == 1) {
0149             (fAttrAutopad == "SAME_UPPER") ? fAttrPads[0]++ : fAttrPads[i1]++;
0150          }
0151          if (fDim > 1 && fAttrKernelShape[1] % 2 == 1) {
0152             (fAttrAutopad == "SAME_UPPER") ? fAttrPads[1]++ : fAttrPads[i2]++;
0153          }
0154          if (fDim > 2 && fAttrKernelShape[2] % 2 == 1) {
0155             (fAttrAutopad == "SAME_UPPER") ? fAttrPads[2]++ : fAttrPads[i3]++;
0156          }
0157       } else if (fAttrAutopad != "VALID") {
0158          throw
0159             std::runtime_error("TMVA SOFIE" + Name() + "Op invalid Autopad value : " + fAttrAutopad);
0160       }
0161       // to be sure pad is vector of size 6
0162       if (fDim < 3) fAttrPads.resize(6, 0);
0163 
0164       if (fAttrStrides.empty()) {
0165          fAttrStrides = {1, 1, 1};
0166       }
0167 
0168       if (fDim < 3)
0169       fAttrStrides.resize(3, 1);
0170 
0171       size_t input1 = input[0][2];
0172       size_t input2 = (fDim > 1) ? input[0][3] : 1;
0173       size_t input3 = (fDim > 2) ? input[0][4] : 1;
0174 
0175       size_t pad1 = fAttrPads[0] + fAttrPads[i1];
0176       size_t output1 = (input1 + pad1 - fAttrKernelShape[0]) / fAttrStrides[0] + 1;
0177 
0178       size_t batch_size = input[0][0];        // first element in input tensor
0179       size_t output_channels = input[0][1];   // first element in output tensor
0180 
0181       std::vector<std::vector<size_t>> ret({{ batch_size, output_channels, output1 }});
0182 
0183       if (fDim == 1)
0184          return ret;
0185 
0186       size_t pad2 = fAttrPads[1] + fAttrPads[i2];
0187       size_t output2 = (input2 + pad2 - fAttrKernelShape[1]) / fAttrStrides[1] + 1;
0188       // output is N x C x OH x OW
0189       ret[0].push_back(output2);
0190       if (fDim == 2)
0191          return ret;
0192 
0193       size_t pad3 = fAttrPads[2] + fAttrPads[i3];
0194       size_t output3 = (input3 + pad3 - fAttrKernelShape[2] ) / fAttrStrides[2] + 1;
0195 
0196       // output is N x C x OH x OW x OD
0197       ret[0].push_back(output3);
0198       return ret;
0199    }
0200 
0201    void Initialize(RModel& model) {
0202 
0203       fUseSession = model.UseSession();
0204 
0205       if (!model.CheckIfTensorAlreadyExist(fNX)) {
0206          throw
0207             std::runtime_error("TMVA SOFIE Pool op Input Tensor " + fNX + " is not found in model");
0208       }
0209       fShapeX = model.GetTensorShape(fNX);
0210       if (fShapeX.size() < 3 || fShapeX.size()  > 5) {
0211          std::cout << fNX << " : " << ConvertShapeToString(fShapeX) << std::endl;
0212          throw
0213             std::runtime_error("TMVA SOFIE Pool Op input data tensor" + fNX + " is not of 3,4 or 5 dimensions");
0214       }
0215        fDim = fShapeX.size() - 2;
0216       // case of GlobalAveragePool. It is a pool case with kernel shape == image shape
0217       if (fPoolMode == GlobalAveragePool) {
0218          fPoolMode = AveragePool;
0219          fAttrKernelShape.resize(3);
0220          fAttrKernelShape[0] = fShapeX[2];
0221          if (fDim > 1)
0222             fAttrKernelShape[1] = fShapeX[3];
0223          if (fDim > 2)
0224             fAttrKernelShape[2] = fShapeX[4];
0225          fAttrAutopad = "VALID";
0226          fAttrPads = {0, 0, 0, 0, 0, 0 };
0227          assert(fAttrStrides.empty());
0228       }
0229       // find shape of Y and add it in the list of intermediate tensors
0230       fShapeY = ShapeInference({fShapeX})[0];
0231       model.AddIntermediateTensor(fNY, model.GetTensorType(fNX), fShapeY);
0232 
0233       // need cmath for INFINITY when using MaxPool
0234       if (fPoolMode == MaxPool) model.AddNeededStdLib("cmath");
0235 
0236    }
0237 
0238    std::string GenerateInitCode() {
0239       std::stringstream out;
0240       return out.str();
0241    }
0242 
0243    // generate code for Session data members (e.g. internal vectors)
0244    virtual std::string GenerateSessionMembersCode(std::string opName)
0245    {
0246       opName = "op_" + opName;
0247       std::stringstream out;
0248       // input matrix padded with zero
0249       if(fDim == 1){
0250           out << "std::vector<" << fType << "> fVec_" << opName << "_xpad = std::vector<" << fType << ">("
0251           << fShapeX[1] * (fShapeX[2] + fAttrPads[0] + fAttrPads[2]) << ");\n";
0252       }
0253       else if(fDim == 2){
0254           out << "std::vector<" << fType << "> fVec_" << opName << "_xpad = std::vector<" << fType << ">("
0255           << fShapeX[1] * (fShapeX[2] + fAttrPads[0] + fAttrPads[2]) * (fShapeX[3] + fAttrPads[1] + fAttrPads[3])
0256           << ");\n";
0257       }
0258       else{ //dim is 3D
0259           out << "std::vector<" << fType << "> fVec_" << opName << "_xpad = std::vector<" << fType << ">("
0260           << fShapeX[1] * (fShapeX[2] + fAttrPads[0] + fAttrPads[2]) * (fShapeX[3] + fAttrPads[1] + fAttrPads[3]) *
0261           (fShapeX[4] + fAttrPads[2] + fAttrPads[4]) << ");\n";
0262       }
0263 
0264       return out.str();
0265    }
0266 
0267    std::string Generate(std::string OpName) {
0268       OpName = "op_" + OpName;
0269 
0270       if (fShapeX.empty() || fShapeY.empty()) {
0271          throw std::runtime_error("TMVA SOFIE Pool Op called to Generate without being initialized first");
0272       }
0273 
0274       std::stringstream out;
0275 
0276       out << "\n//----  operator " << Name() << "  " << OpName << "\n";
0277       out << "{\n"; // create a new scope to avoid name clash
0278 
0279       assert(fShapeX[0] == fShapeY[0]);
0280       assert(fShapeX[1] == fShapeY[1]);
0281       assert(fAttrPads.size() == 6);
0282       assert(fAttrKernelShape.size() == 3);
0283       // find lower bounds of filtered area
0284       int hmin = - fAttrPads[0];   // minimum lower bound value of filter area
0285       int hmax = fShapeX[2] + fAttrPads[1] - fAttrKernelShape[0] +1;  // maximum lower bound value + 1
0286       int wmin,wmax,dmin,dmax;
0287 
0288       if(fDim >= 2){
0289          wmin = - fAttrPads[2];   // minimum lower bound value of filter area
0290          wmax = fShapeX[3] + fAttrPads[3] - fAttrKernelShape[1] +1;  // maximum lower bound value + 1
0291       }
0292       else{
0293          wmin=1;
0294          wmax=1;
0295       }
0296       if(fDim == 3){
0297          dmin = - fAttrPads[4];   // minimum lower bound value of filter area
0298          dmax = fShapeX[4] + fAttrPads[5] - fAttrKernelShape[2] +1;  // maximum lower bound value + 1
0299       }
0300       else{
0301          dmin=1;
0302          dmax=1;
0303       }
0304       out << SP << "constexpr int hsize = " << fShapeX[2] << ";\n";
0305       out << SP << "constexpr int hmin = " << hmin << ";\n";
0306       out << SP << "constexpr int hmax = " << hmax << ";\n";
0307       out << SP << "constexpr int kh = " << fAttrKernelShape[0] << ";\n";
0308       if (fDim > 1) {
0309          size_t wsize = fShapeX[3];
0310          out << SP << "constexpr int wsize = " << wsize << ";\n";
0311          out << SP << "constexpr int wmin = " << wmin << ";\n";
0312          out << SP << "constexpr int wmax = " << wmax << ";\n";
0313          out << SP << "constexpr int kw = " << fAttrKernelShape[1] << ";\n";
0314          if (fDim > 2) {
0315             size_t dsize = fShapeX[4];
0316             out << SP << "constexpr int dsize = " << dsize << ";\n";
0317             out << SP << "constexpr int dwsize = " << dsize*wsize << ";\n"; // hstride
0318             out << SP << "constexpr int dmin = " << dmin << ";\n";
0319             out << SP << "constexpr int dmax = " << dmax << ";\n";
0320             out << SP << "constexpr int kd = " << fAttrKernelShape[2] << ";\n";
0321          }
0322       }
0323 
0324 
0325       bool doPadding = false;
0326       for ( auto & e : fAttrPads)
0327          doPadding |= (e > 0);
0328 
0329 
0330       if(fDim==1){
0331          // loop on batches and channels
0332          out << SP << "size_t outIndex = 0;\n";
0333          out << SP << "for (size_t n = 0; n < " << fShapeX[0]*fShapeX[1] << "; n++) {\n";
0334          out << SP << SP << "size_t inputOffset = n*" << fShapeX[2] << ";\n";
0335          out << SP << SP << "for (int i = hmin; i < hmax; i+=" << fAttrStrides[0] << ") {\n";
0336          // loop on elements of filter region to compute maximum
0337          if (fPoolMode == MaxPool)
0338             out << SP << SP << SP << SP << "float value = -INFINITY;\n";
0339          else if (fPoolMode == AveragePool) {
0340             out << SP << SP << SP << SP << "float value = 0;\n";
0341             if (fAttrCountIncludePad == 0 && doPadding)
0342                out << SP << SP << SP << SP << "int nsum = 0;\n";
0343             else // in case we count the pad values in average
0344                out << SP << SP << SP << SP << "constexpr int nsum = kh;\n";
0345          }
0346          // loop on rows of filtered region
0347          out << SP << SP << SP << SP  << "for (int l = i;  l < i + kh; l++) {\n";
0348          out << SP << SP << SP << SP  << SP << "if (l < 0 || l >= hsize) continue;\n";
0349          out << SP << SP << SP << SP  << SP << SP <<  "int index = inputOffset + l;\n";
0350          if (fPoolMode == MaxPool) {
0351          out << SP << SP << SP << SP << SP << SP << "auto xval = tensor_" << fNX << "[index];\n";
0352          out << SP << SP << SP << SP << SP << SP << "if (xval > value) value = xval;\n";
0353          }
0354          else if (fPoolMode == AveragePool) {
0355             // compute sum of values
0356             out << SP << SP << SP << SP << SP << SP << "value += tensor_" << fNX << "[index];\n";
0357             if (fAttrCountIncludePad == 0 && doPadding)
0358                // compute number of elements used for the average
0359                out << SP << SP << SP << SP << SP << SP << "nsum++;\n";
0360          }
0361           out << SP << SP << SP << SP << SP << "}\n"; // end loop on region elements
0362          if (fPoolMode == AveragePool) {
0363          // compute average
0364          out << SP << SP << SP << SP << "value /= float(nsum);\n";
0365          }
0366 
0367          out << SP << SP << SP << SP << "tensor_" << fNY << "[outIndex++] = value;\n";
0368 
0369          out << SP << SP << "}\n";   // end loop on i (image rows)
0370          out << SP << "}\n";  // end loop on c*b
0371       }
0372       else if(fDim==2){
0373          // loop on batches and channels
0374          out << SP << "size_t outIndex = 0;\n";
0375          out << SP << "for (size_t n = 0; n < " << fShapeX[0]*fShapeX[1] << "; n++) {\n";
0376          out << SP << SP << "size_t inputOffset = n*" << fShapeX[2]*fShapeX[3] << ";\n";
0377          out << SP << SP << "for (int i = hmin; i < hmax; i+=" << fAttrStrides[0] << ") {\n";
0378          out << SP << SP << SP << "for (int j = wmin; j < wmax; j+=" << fAttrStrides[1] << ") {\n";
0379          // loop on elements of filter region to compute maximum
0380          if (fPoolMode == MaxPool)
0381             out << SP << SP << SP << SP << "float value = -INFINITY;\n";
0382          else if (fPoolMode == AveragePool) {
0383             out << SP << SP << SP << SP << "float value = 0;\n";
0384             if (fAttrCountIncludePad == 0 && doPadding)
0385                out << SP << SP << SP << SP << "int nsum = 0;\n";
0386             else // in case we count the pad values in average
0387                out << SP << SP << SP << SP << "constexpr int nsum = kw*kh;\n";
0388          }
0389          // loop on rows of filtered region
0390          out << SP << SP << SP << SP << "for (int l = i;  l < i + kh; l++) {\n";
0391          out << SP << SP << SP << SP << SP << "if (l < 0 || l >= hsize) continue;\n";
0392          // loop on columns of filtered region
0393          out << SP << SP << SP << SP << SP << "for (int m = j; m < j + kw; m++) {\n";
0394          out << SP << SP << SP << SP << SP << SP << "if (m < 0 || m >= wsize) continue;\n";
0395          out << SP << SP << SP << SP << SP << SP << SP << "int index = inputOffset + l*wsize + m;\n";
0396          if (fPoolMode == MaxPool) {
0397          out << SP << SP << SP << SP << SP << SP << SP << "auto xval = tensor_" << fNX << "[index];\n";
0398          out << SP << SP << SP << SP << SP << SP << SP << "if (xval > value) value = xval;\n";
0399          }
0400          else if (fPoolMode == AveragePool) {
0401             // compute sum of values
0402             out << SP << SP << SP << SP << SP << SP << SP << "value += tensor_" << fNX << "[index];\n";
0403             if (fAttrCountIncludePad == 0 && doPadding)
0404                // compute number of elements used for the average
0405                out << SP << SP << SP << SP << SP << SP << SP << "nsum++;\n";
0406          }
0407          out << SP << SP << SP << SP << SP << SP << "}\n";
0408          out << SP << SP << SP << SP << SP << "}\n"; // end loop on region elements
0409          if (fPoolMode == AveragePool) {
0410             // compute average
0411             out << SP << SP << SP << SP << "value /= float(nsum);\n";
0412          }
0413          out << SP << SP << SP << SP << "tensor_" << fNY << "[outIndex++] = value;\n";
0414          out << SP << SP << SP << "}\n";   // end loop on j (columns of image)
0415          out << SP << SP << "}\n";   // end loop on i (image rows)
0416          out << SP << "}\n";  // end loop on c*b
0417       }
0418       else if(fDim==3){
0419          // loop on batches and channels
0420          out << SP << "size_t outIndex = 0;\n";
0421          out << SP << "for (size_t n = 0; n < " << fShapeX[0]*fShapeX[1] << "; n++) {\n";
0422          out << SP << SP << "size_t inputOffset = n*" << fShapeX[2]*fShapeX[3]*fShapeX[4] << ";\n";
0423          out << SP << SP << "for (int i = hmin; i < hmax; i+=" << fAttrStrides[0] << ") {\n";
0424          out << SP << SP << SP << "for (int j = wmin; j < wmax; j+=" << fAttrStrides[1] << ") {\n";
0425          out << SP << SP << SP << SP << "for (int k = dmin; k < dmax; k+=" << fAttrStrides[2] << ") {\n";
0426          // loop on elements of filter region to compute maximum
0427          if (fPoolMode == MaxPool)
0428             out << SP << SP << SP << SP << "float value = -INFINITY;\n";
0429          else if (fPoolMode == AveragePool) {
0430             out << SP << SP << SP << SP << "float value = 0;\n";
0431             if (fAttrCountIncludePad == 0 && doPadding)
0432                out << SP << SP << SP << SP << "int nsum = 0;\n";
0433             else // in case we count the pad values in average
0434                out << SP << SP << SP << SP << "constexpr int nsum = kw*kh*kd;\n";
0435          }
0436          // loop on rows of filtered region
0437          out << SP << SP << SP << SP  << "for (int l = i;  l < i + kh; l++) {\n";
0438          out << SP << SP << SP << SP  << SP << "if (l < 0 || l >= hsize) continue;\n";
0439          // loop on columns of filtered region
0440          out << SP << SP << SP << SP << SP << "for (int m = j; m < j + kw; m++) {\n";
0441          out << SP << SP << SP << SP << SP << SP << "if (m < 0 || m >= wsize) continue;\n";
0442          // loop on layers of filtered region
0443          out << SP << SP << SP << SP << SP << SP << "for (int p = k; p < k + kd; p++) {\n";
0444          out << SP << SP << SP << SP << SP << SP << SP << "if (p < 0 || p >= dsize) continue;\n";
0445          out << SP << SP << SP << SP << SP << SP << SP << SP << "int index = inputOffset + l*dwsize + m*dsize + p;\n";
0446 
0447          if (fPoolMode == MaxPool) {
0448             out << SP << SP << SP << SP << SP << SP << SP << SP << "auto xval = tensor_" << fNX << "[index];\n";
0449             out << SP << SP << SP << SP << SP << SP << SP << SP << "if (xval > value) value = xval;\n";
0450          }
0451          else if (fPoolMode == AveragePool) {
0452             // compute sum of values
0453             out << SP << SP << SP << SP << SP << SP << SP << SP << "value += tensor_" << fNX << "[index];\n";
0454             if (fAttrCountIncludePad == 0 && doPadding)
0455                // compute number of elements used for the average
0456                out << SP << SP << SP << SP << SP << SP << SP << SP << "nsum++;\n";
0457          }
0458          out << SP << SP << SP << SP << SP << SP << "}\n";
0459          out << SP << SP << SP << SP << SP << "}\n";
0460          out << SP << SP << SP << SP << "}\n"; // end loop on region elements
0461          if (fPoolMode == AveragePool) {
0462             // compute average
0463             out << SP << SP << SP << SP << "value /= float(nsum);\n";
0464          }
0465 
0466          out << SP << SP << SP << SP << "tensor_" << fNY << "[outIndex++] = value;\n";
0467          out << SP << SP << SP << SP << "}\n" ; // end loop on k (layers of image)
0468          out << SP << SP << SP << "}\n";   // end loop on j (columns of image)
0469          out << SP << SP << "}\n";   // end loop on i (image rows)
0470          out << SP << "}\n";  // end loop on c*b
0471       }
0472       // end scope
0473       out << SP << "}\n";
0474 
0475 
0476       return out.str();
0477    }
0478 };
0479 
0480 } // namespace SOFIE
0481 } // namespace Experimental
0482 } // namespace TMVA
0483 
0484 
0485 #endif