Esempio n. 1
0
  public static void draw_opticflow(Mat flow, Mat cflowmap, int step, Scalar color) {
    int i = 0, j = 0;
    for (int y = 0; y < cflowmap.rows(); y += step) {
      for (int x = 0; x < cflowmap.cols(); x += step) {

        double[] fxy = Sample1View.flow.get(50, 50);

        // for testing, getting change in values sa frames at point 50,50
        // result : fxy ng.change ang value, however, kun i.add na ang 50, mura xag ma.zero??
        i = (int) Math.round(fxy[0]);
        j = (int) (50.00 + fxy[1]);

        Core.putText(
            cflowmap,
            Double.toString(j),
            new Point(10, 100),
            3 /* CV_FONT_HERSHEY_COMPLEX */,
            2,
            new Scalar(255, 0, 0, 255),
            3);

        // Core.line(cflowmap, new Point(x,y), new Point(i,j), new Scalar(0,0,255), 4);
        // Core.circle(cflowmap, new Point(i,j), 2, new Scalar(0,0,255), -1);
        // Core.circle(cflowmap, new Point(x,y), 2, color, -1);
        // System.out.print("fxy"+i+" "+j+"\n");

        // Core.circle(cflowmap, new Point(cflowmap.width(), 50), 5, new Scalar(255,255,255), -1);
        if (i < (cflowmap.width() / 2) && i != 0.0) {
          Core.circle(cflowmap, new Point(i, j), 2, new Scalar(0, 0, 255), -1);
          Core.putText(
              cflowmap,
              "LEFT",
              new Point(10, 100),
              3 /* CV_FONT_HERSHEY_COMPLEX */,
              2,
              new Scalar(255, 255, 255, 255),
              3);

        } else if (i > (cflowmap.width() / 2) && i != x) {
          Core.circle(cflowmap, new Point(i, j), 2, new Scalar(0, 0, 255), -1);
          Core.putText(
              cflowmap,
              "RIGHT",
              new Point(10, 100),
              3 /* CV_FONT_HERSHEY_COMPLEX */,
              2,
              new Scalar(255, 255, 255, 255),
              3);
        }
      }
    }
  }
Esempio n. 2
0
 public void onEvent(WarpDrawEvent event) {
   synchronized (this) {
     if ((mode == Mode.SAMPLEWARPPLANE || mode == Mode.SAMPLEWARPGLASS) && useSample) {
       if (!isSetup) setupMatrices();
       double hGlassToSmall[] = getHGlassToSmall(sampleBGR.height(), sampleBGR.width());
       if (hGlassToSmall == null) {
         Log.w(TAG, "Warp: Bad size");
         return;
       }
       double circleCenter[] = {event.getX(), event.getY(), 1};
       double circleCenterSmall[] = HMultPoint(hGlassToSmall, circleCenter);
       Core.circle(
           sampleBGR,
           new Point(circleCenterSmall[0], circleCenterSmall[1]),
           event.getRadius(),
           new Scalar(event.getR(), event.getG(), event.getB()));
     }
   }
 }
Esempio n. 3
0
  private Mat get_template(CascadeClassifier clasificator, Rect area, int size) {
    Mat template = new Mat();
    Mat mROI = mGray.submat(area);
    MatOfRect eyes = new MatOfRect();
    Point iris = new Point();
    Rect eye_template = new Rect();
    clasificator.detectMultiScale(
        mROI,
        eyes,
        1.15,
        2,
        Objdetect.CASCADE_FIND_BIGGEST_OBJECT | Objdetect.CASCADE_SCALE_IMAGE,
        new Size(30, 30),
        new Size());

    Rect[] eyesArray = eyes.toArray();
    for (int i = 0; i < eyesArray.length; i++) {
      Rect e = eyesArray[i];
      e.x = area.x + e.x;
      e.y = area.y + e.y;
      Rect eye_only_rectangle =
          new Rect(
              (int) e.tl().x,
              (int) (e.tl().y + e.height * 0.4),
              (int) e.width,
              (int) (e.height * 0.6));
      mROI = mGray.submat(eye_only_rectangle);
      Mat vyrez = mRgba.submat(eye_only_rectangle);
      Core.MinMaxLocResult mmG = Core.minMaxLoc(mROI);

      Core.circle(vyrez, mmG.minLoc, 2, new Scalar(255, 255, 255, 255), 2);
      iris.x = mmG.minLoc.x + eye_only_rectangle.x;
      iris.y = mmG.minLoc.y + eye_only_rectangle.y;
      eye_template = new Rect((int) iris.x - size / 2, (int) iris.y - size / 2, size, size);
      Core.rectangle(mRgba, eye_template.tl(), eye_template.br(), new Scalar(255, 0, 0, 255), 2);
      template = (mGray.submat(eye_template)).clone();
      return template;
    }
    return template;
  }
Esempio n. 4
0
  public Mat onCameraFrame(Mat inputFrame) {

    if (takeScanBase) {
      if (scanBase != null) {
        scanBase.release();
      }
      scanBase = new Mat();
      scanningMat = new Mat();

      Imgproc.cvtColor(inputFrame, scanBase, Imgproc.COLOR_RGB2GRAY);
      Imgproc.cvtColor(scanBase, scanningMat, Imgproc.COLOR_GRAY2RGB);
      scanBase.release();

      hasScanBase = true;
      takeScanBase = false;
    }

    if (hasScanBase) {
      Mat ret;

      Point p = findLaser(inputFrame);
      inputFrame.release();

      if (p != null) {
        if (hasThermal) {
          double temp = mThermal.read();
          data[(int) p.y][(int) p.x] = (float) temp;

          Core.circle(scanningMat, p, 3, new Scalar(255, 0, 0), -3);

        } else {
          // Log.v(TAG, "No thermal!");
        }
      }

      return scanningMat;
    } else return inputFrame;
  }
  /**
   * detection_contours, détecte les contours, les affiches et gères le traitement
   * d'authentification
   *
   * @param inmat : la matrice qui arrive pour la detection de contour
   * @param outmat : la matrice qui sort après les comptours
   */
  public void detection_contours(Mat inmat, Mat outmat) {
    Mat v = new Mat();
    Mat vv = outmat.clone();

    List<MatOfPoint> contours = new ArrayList(); // Tous les contours
    int key; // Plus gros contours
    MatOfInt hullI = new MatOfInt();
    List<MatOfPoint> hullP = new ArrayList<MatOfPoint>();
    Rect r; // Rectangle du plus gros contours

    // Trouve tous les contours
    Imgproc.findContours(vv, contours, v, Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE);
    // Calcul l'indice du plus gros contours
    key = findBiggestContour(contours);
    // S'il y a au moins un contours et le
    if (key != 0) {
      Imgproc.drawContours(inmat, contours, key, new Scalar(0, 0, 255));
      r = Imgproc.boundingRect(contours.get(key));
      Core.rectangle(currentFrame, r.br(), r.tl(), new Scalar(0, 255, 0), 1);
    }

    // Calcul les points convexes de la main
    Imgproc.convexHull(contours.get(key), hullI, false);

    // S'il y a des points de convexion
    if (hullI != null) {
      // Reinitialise les points de convexion
      hullP.clear();

      // On calcule le nombres de points de convexion
      for (int i = 0; contours.size() >= i; i++) hullP.add(new MatOfPoint());

      int[] cId = hullI.toArray();

      // On récupère dans un tableau de points, les points du contours
      Point[] contourPts = contours.get(key).toArray();

      // Réinitialisation des points de recherche dans les tableau
      int findRectA = 0;
      int findRectB = 0;
      int findRectC = 0;
      int findRectD = 0;

      // Pour chaque point de convexion
      for (int i = 0; i < cId.length; i++) {
        // Dessin du point de convexion sur la matrice
        Core.circle(inmat, contourPts[cId[i]], 2, new Scalar(241, 247, 45), -3);

        // Si le point de convexion se trouve dans un des carrés
        //     on incrémente le compteur associé
        if (isInRectangle(rectA, contourPts[cId[i]])) findRectA++;
        else if (isInRectangle(rectB, contourPts[cId[i]])) findRectB++;
        else if (isInRectangle(rectC, contourPts[cId[i]])) findRectC++;
        else if (isInRectangle(rectD, contourPts[cId[i]])) findRectD++;
      }

      // Si on a trouvé la main dans le rectangle A
      if (findRectA >= 5) {
        if (cptRect == 0) {
          numeroRect[cptRect] = 1;
          cptRect++;
          System.out.println("Haut gauche");
        } else {
          if (numeroRect[cptRect - 1] != 1) {
            numeroRect[cptRect] = 1;
            if (cptRect == 3) cptRect = 0;
            else cptRect++;
            System.out.println("Haut gauche");
          }
        }
      }

      // Si on a trouvé la main dans le rectangle B
      if (findRectB >= 5) {
        if (cptRect == 0) {
          numeroRect[cptRect] = 2;
          cptRect++;
          System.out.println("Bas gauche");
        } else {
          if (numeroRect[cptRect - 1] != 2) {
            numeroRect[cptRect] = 2;
            if (cptRect == 3) cptRect = 0;
            else cptRect++;
            System.out.println("Bas gauche");
          }
        }
      }

      // Si on a trouvé la main dans le rectangle C
      if (findRectC >= 5) {
        if (cptRect == 0) {
          numeroRect[cptRect] = 3;
          if (cptRect == 3) cptRect = 0;
          else cptRect++;
          System.out.println("Haut droite");
        } else {
          if (numeroRect[cptRect - 1] != 3) {
            numeroRect[cptRect] = 3;
            if (cptRect == 3) cptRect = 0;
            else cptRect++;
            System.out.println("Haut droite");
          }
        }
      }

      // Si on a trouvé la main dans le rectangle D
      if (findRectD >= 5) {
        if (cptRect == 0) {
          numeroRect[cptRect] = 4;
          cptRect++;
          System.out.println("Bas droite");
        } else {
          if (numeroRect[cptRect - 1] != 4) {
            numeroRect[cptRect] = 4;
            if (cptRect == 3) cptRect = 0;
            else cptRect++;

            System.out.println("Bas droite");
          }
        }
      }

      // Si on a sélectionné 3 fenètres et que cela correspond au mot de passe
      //      MOT DE PASSE : Haut Gauche - Bas Droite - Bas Gauche
      if (cptRect == 3) {
        if ((numeroRect[0] == 1) && (numeroRect[1] == 4) && (numeroRect[2] == 2))
          this.jTextField2.setText("Authenticated");
        // Réinitilisation du compteur
        cptRect = 0;
      }
    }
  }
Esempio n. 6
0
  public void run() {
    System.out.println("\nRunning DetectFaceDemo");

    // Create a face detector from the cascade file in the resources
    // directory.
    // String facefilterpath =
    // getClass().getResource("../resources/haarcascade_mcs_eyepair_big.xml").getPath();
    String facefilterpath = getClass().getResource("../resources/haarcascade_eye.xml").getPath();
    facefilterpath = facefilterpath.substring(1, facefilterpath.length());
    CascadeClassifier faceDetector = new CascadeClassifier(facefilterpath);
    String pngpath = getClass().getResource("../resources/brown_eyes.jpg").getPath();
    pngpath = pngpath.substring(1, pngpath.length());
    Mat image = Highgui.imread(pngpath);

    // Detect faces in the ismage.
    // MatOfRect is a special container class for Rect.
    MatOfRect faceDetections = new MatOfRect();
    faceDetector.detectMultiScale(image, faceDetections);

    Mat image2 = image;

    Imgproc.cvtColor(image2, image, 6); // 6 = CV_BGR2GRAY not working
    Imgproc.GaussianBlur(image, image, new Size(7, 7), 4, 4);
    // Imgproc.medianBlur(image,image, 2);
    MatOfPoint3f circles = new MatOfPoint3f();
    MatOfPoint3f circles2 = new MatOfPoint3f();

    Imgproc.HoughCircles(
        image, circles, Imgproc.CV_HOUGH_GRADIENT, 5, image.rows() / 5, 100, 100, 10, 50);

    Imgproc.HoughCircles(
        image, circles2, Imgproc.CV_HOUGH_GRADIENT, 5, image.rows() / 5, 100, 100, 50, 400);

    Imgproc.cvtColor(image, image, 8); // 6 = CV_BGR2GRAY not working

    System.out.println(String.format("Detected %s faces", faceDetections));
    // Draw a bounding box around each face.
    for (Rect rect : faceDetections.toArray()) {
      // Core.rectangle(image, new Point(rect.x, rect.y), new Point(rect.x + rect.width, rect.y +
      // rect.height), new Scalar(0, 255, 0),100);
    }

    System.out.println(String.format("Detected %s circles", circles.total()));

    for (Point3 circle : circles.toArray()) {
      Point center = new Point(circle.x, circle.y);
      int radius = (int) Math.round(circle.z);
      Core.circle(image, center, 3, new Scalar(0, 255, 0), -1, 8, 0);
      Core.circle(image, center, radius, new Scalar(0, 0, 255), 3, 8, 0);
      // Core.circle(image, center, radius, new Scalar(0,255,0), 10,8, 0);
    }
    for (Point3 circle : circles2.toArray()) {
      Point center = new Point(circle.x, circle.y);
      int radius = (int) Math.round(circle.z);
      Core.circle(image, center, 3, new Scalar(0, 255, 0), -1, 8, 0);
      Core.circle(image, center, radius, new Scalar(0, 0, 255), 3, 8, 0);
      // Core.circle(image, center, radius, new Scalar(0,255,0), 10,8, 0);
    }

    // Core.circle(image, new Point(100,100), 10, new Scalar(0,255,0), 10, 8, 0);
    // Save the visualized detection.

    String filename = "faceDetection.png";
    System.out.println(String.format("Writing %s", filename));
    Highgui.imwrite(filename, image);
  }
  private Mat drawLines(Mat inputFrame) {

    int row = inputFrame.rows();
    int col = inputFrame.cols();

    if (mIsFound) {

      // Core.line(inputFrame, new Point(10,10), new Point(10,row-10), new Scalar(0, 255, 0, 255),
      // 3);
      // Core.line(inputFrame, new Point(10,10), new Point(col-10,10), new Scalar(0, 255, 0, 255),
      // 3);

      // Core.line(inputFrame, new Point(10,row-10), new Point(col-10,row-10), new Scalar(0, 255, 0,
      // 255), 3);
      // Core.line(inputFrame, new Point(col-10,10), new Point(col-10,row-10), new Scalar(0, 255, 0,
      // 255), 3);

      /*
      if(mCheckIcon == null){
      	mCheckIcon = new Mat();
      	Bitmap input2 = MatchImageUtil.scaleAndTrun(BitmapFactory.decodeResource(getResources(), R.drawable.check_icon));
          		Utils.bitmapToMat(input2, mCheckIcon);
      }

      mCheckIcon.copyTo(inputFrame);
      */

      int radius = (row > col ? col : row) / 2 - (row > col ? col : row) / 2 / 3;
      Core.circle(inputFrame, new Point(col / 2, row / 2), radius, new Scalar(0, 255, 0, 255), 15);
    }

    int baseSize = (row > col ? col : row) / 2 - (row > col ? col : row) / 2 / 5;
    Core.rectangle(
        inputFrame,
        new Point(col / 2 - baseSize, row / 2 - baseSize),
        new Point(col / 2 + baseSize, row / 2 + baseSize),
        new Scalar(255, 255, 255, 255),
        1);
    Core.line(
        inputFrame,
        new Point(col / 2 - 5, row / 2),
        new Point(col / 2 + 5, row / 2),
        new Scalar(255, 255, 255, 255),
        1);
    Core.line(
        inputFrame,
        new Point(col / 2, row / 2 - 5),
        new Point(col / 2, row / 2 + 5),
        new Scalar(255, 255, 255, 255),
        1);

    if (mIsBarDraw) {

      if (mBarReverse) {
        mBarCount -= 2;
      } else {
        mBarCount += 2;
      }

      if (!mBarReverse && mBarCount > row - 2 * MARGIN) {
        mBarReverse = true;
      }

      if (mBarReverse && mBarCount < MARGIN) {
        mBarReverse = false;
      }

      int curBarPosition = MARGIN + mBarCount;

      Core.line(
          inputFrame,
          new Point(0, curBarPosition),
          new Point(col, curBarPosition),
          new Scalar(0, 255, 255, 255),
          2);
    }

    return inputFrame;
  }
Esempio n. 8
0
  public Point findLaser(Mat inputFrame) {
    Mat mHsv = new Mat();

    Imgproc.cvtColor(inputFrame, mHsv, Imgproc.COLOR_RGB2HSV);

    // Find laser center
    Mat center = new Mat();
    Core.inRange(mHsv, new Scalar(0, 0, 250), new Scalar(180, 16, 255), center);

    Mat h = new Mat();
    List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
    Imgproc.findContours(center, contours, h, Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE);
    center.release();

    Mat center_mask = Mat.zeros(inputFrame.rows(), inputFrame.cols(), CvType.CV_8U);
    if (contours.size() > 0) {
      for (int i = 0; i < contours.size(); i++) {
        int radius = 10;
        // Point[] cont_pos = contours.get(i).toArray();
        Moments m = Imgproc.moments(contours.get(i));
        Point p = new Point();
        p.x = m.get_m10() / m.get_m00();
        p.y = m.get_m01() / m.get_m00();
        Core.circle(center_mask, p, radius * 2, new Scalar(255), -1);
      }
    }

    // Find halo
    Mat ranged = new Mat();
    Core.inRange(mHsv, new Scalar(100, 32, 225), new Scalar(150, 255, 255), ranged);
    mHsv.release();
    // Mat f_frame =ranged.clone();

    // Find halo around bright dot
    Core.bitwise_and(ranged, center_mask, ranged);
    center_mask.release();

    // Find biggest resulting contour
    for (int i = 1; i < contours.size(); i++) {
      contours.get(i).release();
    }
    contours.clear();
    Imgproc.findContours(ranged, contours, h, Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE);
    h.release();
    ranged.release();

    if (contours.size() > 0) {
      MatOfPoint biggest_cont = contours.get(0);
      double cont_size = Imgproc.contourArea(biggest_cont);
      for (int i = 1; i < contours.size(); i++) {
        MatOfPoint cur = contours.get(i);
        if (Imgproc.contourArea(cur) > cont_size) {
          biggest_cont = cur;
          cont_size = Imgproc.contourArea(cur);
        }
      }
      Moments m = Imgproc.moments(biggest_cont);
      Point p = new Point();
      p.x = m.get_m10() / m.get_m00();
      p.y = m.get_m01() / m.get_m00();
      for (int i = 1; i < contours.size(); i++) {
        contours.get(i).release();
      }
      biggest_cont.release();

      return p;
    } else {
      return null;
    }
  }
  public void processImage(Mat imageToProcess) {
    try {
      final Mat processedImage = imageToProcess.clone();
      // red
      final Mat redUpper = new Mat();
      final Mat redLower = new Mat();
      Core.inRange(processedImage, new Scalar(170, 100, 20), new Scalar(180, 255, 255), redUpper);
      Core.inRange(processedImage, new Scalar(0, 100, 20), new Scalar(20, 255, 255), redLower);
      Core.bitwise_or(redLower, redUpper, processedImage);

      // refining the binary image
      Imgproc.erode(processedImage, processedImage, new Mat(), new Point(-1, -1), 1);
      Imgproc.dilate(processedImage, processedImage, new Mat(), new Point(-1, -1), 0);

      // create a clone for the processedImage to be used in finding contours
      final Mat clone = processedImage.clone();
      Imgproc.cvtColor(processedImage, processedImage, Imgproc.COLOR_GRAY2RGB);

      // finds list of contours and draw the biggest on the processedImage
      final Scalar color1 = new Scalar(0, 0, 255);
      final Scalar color2 = new Scalar(255, 255, 0);
      final Scalar color3 = new Scalar(255, 255, 255);
      final List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
      Imgproc.findContours(
          clone, contours, new Mat(), Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_NONE);

      final List<MatOfPoint> contour = new ArrayList<MatOfPoint>(1);
      double maxArea = 0.0;
      for (int index = 0; index < contours.size(); index++) {
        double area = Imgproc.contourArea(contours.get(index));
        if (area > maxArea && area > 25 && Imgproc.boundingRect(contours.get(index)).y > 40) {
          maxArea = area;
          contour.add(0, contours.get(index));
        }
      }
      // finds bounding Rectangle and draws contours
      Rect boundingRect = new Rect();
      if (contour.size() > 0) {
        Imgproc.drawContours(processedImage, contour, -2, color1);
        boundingRect = Imgproc.boundingRect(contour.get(0));
        final double x = boundingRect.x;
        final double y = boundingRect.y;
        final double width = boundingRect.width;
        final double height = boundingRect.height;
        Core.rectangle(processedImage, new Point(x, y), new Point(x + width, y + height), color3);
      }
      // finding bounding Circle and draws it
      final Point center = new Point();
      final float[] radius = new float[1];
      if (contour.size() > 0) {
        final MatOfPoint2f contour2f = new MatOfPoint2f(contour.get(0).toArray());
        Imgproc.minEnclosingCircle(contour2f, center, radius);
        Core.circle(processedImage, center, (int) radius[0], color2);
      }

      final BallStruct redBallStruct = new BallStruct(boundingRect, center, (double) radius[0]);
      final BallTargeting ballTargeting = new BallTargeting(redBallStruct);

      synchronized (this) {
        distanceToRed = ballTargeting.getDistance();
        angleToRedInDegrees = ballTargeting.getAngle() * (180 / Math.PI);
        this.processedImage = processedImage;
      }
    } catch (Exception e) {

    }
  }
Esempio n. 10
0
  private void drawCircles(Mat img, Mat circles) {
    logMsg("Found " + circles.cols() + " circles");

    /** If two circles and the difference in their y axis is less than or equal to 20 */
    if (circles.cols() == 2 && Math.abs(circles.get(0, 0)[1] - (circles.get(0, 1)[1])) <= 20) {
      drawCircles = true;
      setCircleParameters(circles);
      /// Finds midpoint between the two circles
      double midCircle = (circle1 + circle2) / 2;
      /// Substrate from center in order to get negative distance on the left side
      double distance = center - midCircle;
      /// Calculate angle
      double tempAngle = Math.atan(distance / img.height());
      // Calculate angle change from previous
      double angle_destination_change = (tempAngle - prevAngle) / (endTime - startTime);
      prevAngle = tempAngle;
      // use gyro information from arduino to get rotation rate of heading
      double[] gyro = getGyro();
      double drz = 0;
      if (gyro != null) drz = gyro[2];

      /// Get angle from PD controller
      angle = tempAngle * Kp + (angle_destination_change - drz) * Kd;

      // Ensure angle is within bounds
      if (angle < -1.0) angle = -1.0;
      else if (angle > 1.0) angle = 1.0;

      /** Set the angle of the twist and send twist to server */
      twist.drz(angle);
      sendTwist(twist);
      logMsg("Angle: " + angle);
    } else if (circles.cols() <= 1) {
      if (minRadius >= 10)
      /** Minimum radius is 5 */
      minRadius -= 5;
      if (minDistance >= 30)
      /** Minimum Distance is 20 */
      minDistance -= 10;
      if (maxRadius <= 70)
      /** Max Radius is 80 */
      maxRadius += 10;
    } else {
      if (minRadius + 5 < maxRadius) minRadius += 5;
      if (maxRadius - 10 > minRadius) maxRadius -= 10;
      if (minDistance + 10 <= 100) minDistance += 10;
    }

    /** If it has not seen the circles in 10 frames something is wrong */
    if (drawCircles) {
      frameCount = 0;
    } else if (frameCount > 10) {
      sendTwist(new Twist(0, 0, 0, 0, 0, 0));
      frameCount = 0;
      logMsg("Lost the circles");
    }

    /** Draw the circles */
    for (int x = 0; drawCircles == true && circles.cols() > 1 && x < circles.cols(); x++) {
      double circle[] = circles.get(0, x);
      int ptx = (int) Math.round(circle[0]), pty = (int) Math.round(circle[1]);
      Point pt = new Point(ptx, pty);
      int radius = (int) Math.round(circle[2]);

      /** Draw the circle outline */
      Core.circle(img, pt, radius, new Scalar(0, 255, 0), 3, 8, 0);
      /** Draw the circle center */
      Core.circle(img, pt, 3, new Scalar(0, 255, 0), -1, 8, 0);

      // Core.putText(img, ""+ angle, new Point(50,100), Core.FONT_HERSHEY_COMPLEX, .8, new
      // Scalar(255,0,0));
    }
  }