All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.sciss.fscape.stream.PeakCentroid2D.scala Maven / Gradle / Ivy

/*
 *  PeakCentroid2D.scala
 *  (FScape)
 *
 *  Copyright (c) 2001-2016 Hanns Holger Rutz. All rights reserved.
 *
 *  This software is published under the GNU General Public License v2+
 *
 *
 *  For further information, please contact Hanns Holger Rutz at
 *  [email protected]
 */

package de.sciss.fscape
package stream

import akka.stream.Attributes
import de.sciss.fscape.stream.impl.{FilterLogicImpl, In6Out3Impl, In6Out3Shape, StageImpl, NodeImpl, WindowedLogicImpl}
import de.sciss.numbers

object PeakCentroid2D {
  def apply(in: OutD, width: OutI, height: OutI, thresh1: OutD, thresh2: OutD, radius: OutI)
           (implicit b: Builder): (OutD, OutD, OutD) = {
    val stage0  = new Stage
    val stage   = b.add(stage0)
    b.connect(in     , stage.in0)
    b.connect(width  , stage.in1)
    b.connect(height , stage.in2)
    b.connect(thresh1, stage.in3)
    b.connect(thresh2, stage.in4)
    b.connect(radius , stage.in5)
    (stage.out0, stage.out1, stage.out2)
  }

  private final val name = "PeakCentroid2D"

  private type Shape = In6Out3Shape[BufD, BufI, BufI, BufD, BufD, BufI, BufD, BufD, BufD]

  private final class Stage(implicit ctrl: Control) extends StageImpl[Shape](name) {
    val shape = new In6Out3Shape(
      in0  = InD (s"$name.in"        ),
      in1  = InI (s"$name.width"     ),
      in2  = InI (s"$name.height"    ),
      in3  = InD (s"$name.thresh1"   ),
      in4  = InD (s"$name.thresh2"   ),
      in5  = InI (s"$name.radius"    ),
      out0 = OutD(s"$name.translateX"),
      out1 = OutD(s"$name.translateY"),
      out2 = OutD(s"$name.peak"      )
    )

    def createLogic(attr: Attributes) = new Logic(shape)
  }

  // XXX TODO -- abstract over data type (BufD vs BufI)?
  private final class Logic(shape: Shape)(implicit ctrl: Control)
    extends NodeImpl(name, shape)
      with WindowedLogicImpl[Shape]
      with FilterLogicImpl[BufD, Shape]
      with In6Out3Impl[BufD, BufI, BufI, BufD, BufD, BufI, BufD, BufD, BufD] {

    override def toString = s"$name-L@${hashCode.toHexString}"

    protected def allocOutBuf0(): BufD = ctrl.borrowBufD()
    protected def allocOutBuf1(): BufD = ctrl.borrowBufD()
    protected def allocOutBuf2(): BufD = ctrl.borrowBufD()
    
    private[this] var width  : Int    = _
    private[this] var height : Int    = _
    private[this] var thresh1: Double = _
    private[this] var thresh2: Double = _
    private[this] var radius : Int    = _

    private[this] var winBuf      : Array[Double] = _
    private[this] var size        : Int = _

    private[this] var translateX  : Double = _
    private[this] var translateY  : Double = _
    private[this] var peak        : Double = _

    protected def startNextWindow(inOff: Int): Int = {
      val oldSize = size
      if (bufIn1 != null && inOff < bufIn1.size) {
        width = math.max(1, bufIn1.buf(inOff))
      }
      if (bufIn2 != null && inOff < bufIn2.size) {
        height = math.max(1, bufIn2.buf(inOff))
      }
      size = width * height
      if (size != oldSize) {
        winBuf = new Array[Double](size)
      }

      if (bufIn3 != null && inOff < bufIn3.size) thresh1 = bufIn3.buf(inOff)
      if (bufIn4 != null && inOff < bufIn4.size) thresh2 = bufIn4.buf(inOff)
      if (bufIn5 != null && inOff < bufIn5.size) radius  = math.max(1, bufIn5.buf(inOff))

//      println(s"width = $width, height = $height; thresh1 = $thresh1, thresh2 = $thresh2; radius = $radius")

      size
    }

    protected def copyInputToWindow(inOff: Int, writeToWinOff: Int, chunk: Int): Unit =
      Util.copy(bufIn0.buf, inOff, winBuf, writeToWinOff, chunk)

    // private var COUNT = 0

    protected def copyWindowToOutput(readFromWinOff: Int, outOff: Int, chunk: Int): Unit = {
      assert(readFromWinOff == 0 && chunk == 1)
      // Util.copy(winBuf, readFromWinOff, bufOut0.buf, outOff, chunk)
      bufOut0.buf(outOff) = translateX
      bufOut1.buf(outOff) = translateY
      bufOut2.buf(outOff) = peak
//      COUNT += 1
//      println(f"elapsed = ${COUNT * 1024 / 44100.0}%1.2f sec - tx $translateX%1.2f; ty $translateY%1.2f, pk $peak%1.2f")
      println(f"tx ${(translateX + 0.5).toInt}, pk = $peak%1.2f")
    }

    @inline
    private def pixel(x: Int, y: Int): Double = winBuf(y * width + x)

    protected def processWindow(writeToWinOff: Int): Int = {
      if (writeToWinOff < size) Util.clear(winBuf, writeToWinOff, size - writeToWinOff)

      var i     = 0
      var max   = Double.NegativeInfinity
      while (i < size) {
        val q = winBuf(i)
        if (q > max) {
          max = q
        }
        i += 1
      }
//      println(f"max = $max%1.3f")

      // ---- first run ----

      val threshM1 = thresh1 * max
      var cx = 0.0
      var cy = 0.0
      var cs = 0.0

      val wh  = width/2
      val hh  = height/2
      var y = 0
      while (y < height) {
        var x = 0
        while (x < width) {
          //        val q = image.pixel(x, y)
          //        if (q > threshM) {
          val q = pixel(x, y) - threshM1
          if (q > 0.0) {
            cx += q * (if (x >= wh) x - width  else x)
            cy += q * (if (y >= hh) y - height else y)
            cs += q
          }
          x += 1
        }
        y += 1
      }

      cx /= cs
      cy /= cs

      // ---- second run ----
      import numbers.Implicits._
      val wm      = width - 1
      val hm      = height - 1
      val xMin    = ((cx + 0.5).toInt - radius).wrap(0, wm)
      val yMin    = ((cy + 0.5).toInt - radius).wrap(0, hm)
      val trDiam  = radius + radius + 1
      val xMax    = (xMin + trDiam).wrap(0, wm)
      val yMax    = (yMin + trDiam).wrap(0, hm)

//      println(f"peak run 1 - ($cx%1.2f, $cy%1.2f, $cs%1.2f)")

      val threshM2 = thresh2 * max
      cx = 0.0
      cy = 0.0
      cs = 0.0
      y  = yMin
      do {
        var x = xMin
        do {
          val q = pixel(x, y)
          if (q > threshM2) {
            // val q = image.pixel(x, y) - threshM2
            // if (q > 0.0) {
            cx += q * (if (x >= wh) x - width  else x)
            cy += q * (if (y >= hh) y - height else y)
            cs += q
          }
          x = (x + 1) % width
        } while (x != xMax)
        y = (y + 1) % height
      } while (y != yMax)

      if (cs > 0) {
        cx /= cs
        cy /= cs
      }

//      println(f"peak run 2 - ($cx%1.2f, $cy%1.2f, $cs%1.2f)")

//      val peak = Product(translateX = cx, translateY = cy, peak = cs)
//      peak

      translateX = cx // bufOut0.buf(0) = cx
      translateY = cx // bufOut1.buf(0) = cy
      peak       = cs // bufOut2.buf(0) = cs
      1
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy