From acf0f50ce5774cf7cc6358df6922346ef826e15c Mon Sep 17 00:00:00 2001 From: Jarek Sacha Date: Wed, 1 Dec 2021 21:33:05 -0500 Subject: [PATCH] Support Scala 3 build #17 Build setup and changes needed to compile with Scala 3 --- .scalafmt.conf | 17 + build.sbt | 135 +++++--- .../debayer2sx/DeBayer2Config.scala | 3 +- .../ij_plugins/debayer2sx/LoopUtils.scala | 173 ++++++++++ .../debayer2sx/DeBayer2Config.scala | 76 +++++ .../ij_plugins/debayer2sx/LoopUtils.scala | 187 +++++++++++ .../scala/ij_plugins/debayer2sx/DDFAPD.scala | 309 ++++++++++-------- .../ij_plugins/debayer2sx/DeBayer2.scala | 11 +- .../ij_plugins/debayer2sx/MakeBayer.scala | 3 +- .../scala/ij_plugins/debayer2sx/package.scala | 3 +- .../process/FloatProcessorMath.scala | 32 +- .../debayer2sx/process/package.scala | 58 +--- .../debayer2sx/DeBayer2Plugin.scala | 16 +- 13 files changed, 721 insertions(+), 302 deletions(-) create mode 100644 .scalafmt.conf rename ijp-debayer2sx-core/src/main/{scala => scala-2}/ij_plugins/debayer2sx/DeBayer2Config.scala (96%) create mode 100644 ijp-debayer2sx-core/src/main/scala-2/ij_plugins/debayer2sx/LoopUtils.scala create mode 100644 ijp-debayer2sx-core/src/main/scala-3/ij_plugins/debayer2sx/DeBayer2Config.scala create mode 100644 ijp-debayer2sx-core/src/main/scala-3/ij_plugins/debayer2sx/LoopUtils.scala diff --git a/.scalafmt.conf b/.scalafmt.conf new file mode 100644 index 0000000..5a6367b --- /dev/null +++ b/.scalafmt.conf @@ -0,0 +1,17 @@ +version = 3.2.0 + +runner.dialect = scala213source3 +fileOverride { + "glob:**/src/main/scala-3/**" { + runner.dialect = scala3 + } +} + +preset = IntelliJ +align.preset = more +maxColumn = 120 +docstrings.style = Asterisk +docstrings.blankFirstLine = yes +docstrings.wrap = no +importSelectors = singleLine +newlines.source = keep \ No newline at end of file diff --git a/build.sbt b/build.sbt index f662a72..713757d 100644 --- a/build.sbt +++ b/build.sbt @@ -2,39 +2,50 @@ import xerial.sbt.Sonatype.GitHubHosting import java.net.URL -// @formatter:off - name := "ijp-debayer2sx" -val _version = "1.3.0.1-SNAPSHOT" -val _scalaVersions = Seq("2.13.5", "2.12.13") -val _scalaVersion = _scalaVersions.head +ThisBuild / version := "1.3.0.1-SNAPSHOT" +ThisBuild / organization := "net.sf.ij-plugins" +ThisBuild / sonatypeProfileName := "net.sf.ij-plugins" +ThisBuild / homepage := Some(new URL("https://github.com/ij-plugins/ijp-color")) +ThisBuild / startYear := Some(2002) +ThisBuild / licenses := Seq(("LGPL-2.1", new URL("http://opensource.org/licenses/LGPL-2.1"))) + +ThisBuild / scalaVersion := "2.13.7" +ThisBuild / crossScalaVersions := Seq("2.13.7", "2.12.15", "3.0.2") -version := _version -scalaVersion := _scalaVersion publishArtifact := false -publish / skip := true -sonatypeProfileName := "net.sf.ij-plugins" +publish / skip := true + +/** Return `true` if scala version corresponds to Scala 2, `false` otherwise */ +def isScala2(scalaVersion: String): Boolean = { + CrossVersion.partialVersion(scalaVersion) match { + case Some((2, _)) => true + case _ => false + } +} val commonSettings = Seq( - version := _version, - organization := "net.sf.ij-plugins", - homepage := Some(new URL("https://github.com/ij-plugins/ijp-color")), - startYear := Some(2002), - licenses := Seq(("LGPL-2.1", new URL("http://opensource.org/licenses/LGPL-2.1"))), - // - crossScalaVersions := _scalaVersions, - scalaVersion := _scalaVersion, - // scalacOptions ++= Seq( - "-encoding", "UTF-8", - "-unchecked", - "-deprecation", - "-Xlint", + "-encoding", + "UTF-8", + "-unchecked", + "-deprecation", "-feature", - "-explaintypes", - "-release", "8", - ), + "-release", + "8" + ) ++ ( + if (isScala2(scalaVersion.value)) + Seq( + "-Xlint", + "-explaintypes", + "-Xsource:3" + ) + else + Seq( + "-explain" + ) + ), scalacOptions ++= { CrossVersion.partialVersion(scalaVersion.value) match { case Some((2, 13)) => Seq("-target:8") @@ -42,21 +53,24 @@ val commonSettings = Seq( } }, Compile / doc / scalacOptions ++= Opts.doc.title("IJP Debayer2SX API"), - Compile / doc / scalacOptions ++= Opts.doc.version(_version), + Compile / doc / scalacOptions ++= Opts.doc.version(version.value), Compile / doc / scalacOptions ++= Seq( - "-doc-footer", s"IJP Debayer2SX API v.${_version}", - "-doc-root-content", baseDirectory.value + "/src/main/scala/root-doc.creole" + "-doc-footer", + s"IJP Debayer2SX API v.${version.value}", + "-doc-root-content", + baseDirectory.value + "/src/main/scala/root-doc.creole" ), Compile / doc / scalacOptions ++= ( Option(System.getenv("GRAPHVIZ_DOT_PATH")) match { case Some(path) => Seq("-diagrams", "-diagrams-dot-path", path, "-diagrams-debug") case None => Seq.empty[String] - }), - Compile / compile / javacOptions ++= Seq("-deprecation", "-Xlint", "-source", "1.8", "-target", "1.8"), + } + ), + Compile / compile / javacOptions ++= Seq("-deprecation", "-Xlint", "-source", "1.8", "-target", "1.8"), // libraryDependencies ++= Seq( - "net.imagej" % "ij" % "1.53i", - "org.scalatest" %% "scalatest" % "3.2.8" % "test", + "net.imagej" % "ij" % "1.53i", + "org.scalatest" %% "scalatest" % "3.2.10" % "test" ), resolvers += Resolver.sonatypeRepo("snapshots"), // @@ -71,59 +85,75 @@ val commonSettings = Seq( manifestSetting, // Setup publishing publishMavenStyle := true, - sonatypeProfileName := "net.sf.ij-plugins", sonatypeProjectHosting := Some(GitHubHosting("ij-plugins", "ijp-debayer2sx", "jpsacha@gmail.com")), publishTo := sonatypePublishToBundle.value, developers := List( - Developer(id="jpsacha", name="Jarek Sacha", email="jpsacha@gmail.com", url=url("https://github.com/jpsacha")) + Developer( + id = "jpsacha", + name = "Jarek Sacha", + email = "jpsacha@gmail.com", + url = url("https://github.com/jpsacha") + ) ) ) lazy val ijp_debayer2sx_core = project.in(file("ijp-debayer2sx-core")) .settings( - name := "ijp-debayer2sx-core", + name := "ijp-debayer2sx-core", description := "IJP DeBayer2SX Core", commonSettings, - libraryDependencies += "com.beachape" %% "enumeratum" % "1.6.1", - libraryDependencies += "io.github.metarank" %% "cfor" % "0.2" + libraryDependencies ++= { + if (isScala2(scalaVersion.value)) { + Seq( + "com.beachape" %% "enumeratum" % "1.6.1", + "io.github.metarank" %% "cfor" % "0.2" + ) + } else { + Seq.empty[ModuleID] + } + } ) lazy val ijp_debayer2sx_plugins = project.in(file("ijp-debayer2sx-plugins")) .settings( name := "ijp-debayer2sx-plugins", description := "IJP DeBayer2SX ImageJ Plugins", - commonSettings, + commonSettings ) .dependsOn(ijp_debayer2sx_core) lazy val ijp_debayer2sx_demos = project.in(file("ijp-debayer2sx-demos")) - .settings(commonSettings, - name := "ijp-debayer2sx-demos", + .settings( + commonSettings, + name := "ijp-debayer2sx-demos", description := "IJP DeBayer2SX Demos", publishArtifact := false, - publish / skip := true) + publish / skip := true + ) .dependsOn(ijp_debayer2sx_core) lazy val ijp_debayer2sx_experimental = project.in(file("ijp-debayer2sx-experimental")) - .settings(commonSettings, - name := "ijp-debayer2sx-experimental", + .settings( + commonSettings, + name := "ijp-debayer2sx-experimental", description := "Experimental Features", publishArtifact := false, - publish / skip := true) + publish / skip := true + ) .dependsOn(ijp_debayer2sx_core) lazy val manifestSetting = packageOptions += { Package.ManifestAttributes( "Created-By" -> "Simple Build Tool", - "Built-By" -> Option(System.getenv("JAR_BUILT_BY")).getOrElse(System.getProperty("user.name")), + "Built-By" -> Option(System.getenv("JAR_BUILT_BY")).getOrElse(System.getProperty("user.name")), "Build-Jdk" -> System.getProperty("java.version"), - "Specification-Title" -> name.value, - "Specification-Version" -> version.value, - "Specification-Vendor" -> organization.value, - "Implementation-Title" -> name.value, - "Implementation-Version" -> version.value, + "Specification-Title" -> name.value, + "Specification-Version" -> version.value, + "Specification-Vendor" -> organization.value, + "Implementation-Title" -> name.value, + "Implementation-Version" -> version.value, "Implementation-Vendor-Id" -> organization.value, - "Implementation-Vendor" -> organization.value + "Implementation-Vendor" -> organization.value ) } @@ -135,5 +165,4 @@ ijCleanBeforePrepareRun := true // Instruct `clean` to delete created plugins subdirectory created by `ijRun`/`ijPrepareRun`. cleanFiles += ijPluginsDir.value - -addCommandAlias("ijRun", "ijp_debayer2sx_plugins/ijRun") \ No newline at end of file +addCommandAlias("ijRun", "ijp_debayer2sx_plugins/ijRun") diff --git a/ijp-debayer2sx-core/src/main/scala/ij_plugins/debayer2sx/DeBayer2Config.scala b/ijp-debayer2sx-core/src/main/scala-2/ij_plugins/debayer2sx/DeBayer2Config.scala similarity index 96% rename from ijp-debayer2sx-core/src/main/scala/ij_plugins/debayer2sx/DeBayer2Config.scala rename to ijp-debayer2sx-core/src/main/scala-2/ij_plugins/debayer2sx/DeBayer2Config.scala index 1c31444..9d26da5 100644 --- a/ijp-debayer2sx-core/src/main/scala/ij_plugins/debayer2sx/DeBayer2Config.scala +++ b/ijp-debayer2sx-core/src/main/scala-2/ij_plugins/debayer2sx/DeBayer2Config.scala @@ -74,5 +74,4 @@ object DeBayer2Config { } -case class DeBayer2Config(mosaicOrder: MosaicOrder = MosaicOrder.R_G, - demosaicing: Demosaicing = Demosaicing.DDFAPD) +case class DeBayer2Config(mosaicOrder: MosaicOrder = MosaicOrder.R_G, demosaicing: Demosaicing = Demosaicing.DDFAPD) diff --git a/ijp-debayer2sx-core/src/main/scala-2/ij_plugins/debayer2sx/LoopUtils.scala b/ijp-debayer2sx-core/src/main/scala-2/ij_plugins/debayer2sx/LoopUtils.scala new file mode 100644 index 0000000..cfc0575 --- /dev/null +++ b/ijp-debayer2sx-core/src/main/scala-2/ij_plugins/debayer2sx/LoopUtils.scala @@ -0,0 +1,173 @@ +/* + * Image/J Plugins + * Copyright (C) 2002-2021 Jarek Sacha + * Author's email: jpsacha at gmail [dot] com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Latest release available at https://github.com/ij-plugins/ijp-DeBayer2SX + */ + +package ij_plugins.debayer2sx + +import ij.process.{FloatProcessor, ImageProcessor} +import ij_plugins.debayer2sx.process.{FR, sortedRangeParams} + +import java.awt.Rectangle + +object LoopUtils { + + final def crop(src: FloatProcessor, roi: Rectangle): FloatProcessor = { + import io.github.metarank.cfor.* + + val width = src.getWidth + val pixels = src.getPixels.asInstanceOf[Array[Float]] + val roiX = roi.x + val roiY = roi.y + val roiWidth = roi.width + val roiHeight = roi.height + val ip2 = new FloatProcessor(roiWidth, roiHeight) + val pixels2 = ip2.getPixels.asInstanceOf[Array[Float]] + // for (ys <- roiY until roiY + roiHeight) { + cfor(roiY)(_ < roiY + roiHeight, _ + 1) { ys => + var offset1 = (ys - roiY) * roiWidth + var offset2 = ys * width + roiX + // for (xs <- 0 until roiWidth) { + cfor(0)(_ < roiWidth, _ + 1) { _ => + pixels2(offset1) = pixels(offset2) + offset1 += 1 + offset2 += 1 + } + } + ip2 + } + + /** + * Clip values in the image to the range specified by `bpp`. + * + * {{{ + * min = 0 + * max = 2^bpp - 1 + * }}} + * + * @param ip image to check + * @param bpp bits per pixel + */ + final def checkImg(ip: FloatProcessor, bpp: Int): Unit = { + import io.github.metarank.cfor.* + + val maxVal = (math.pow(2, bpp) - 1).toFloat + val pixels = ip.getPixels.asInstanceOf[Array[Float]] + + // for (i <- pixels.indices) { + cfor(0)(_ < pixels.length, _ + 1) { i => + val v = pixels(i) + if (v > maxVal) { + pixels(i) = maxVal + } else if (v < 0) { + pixels(i) = 0 + } + } + } + + @inline + final def slice(src: FloatProcessor, srcRangeX: Range, srcRangeY: Range): FloatProcessor = { + import io.github.metarank.cfor.* + + // bay(1:2:m,2:2:n) + val _srcRangeX = if (srcRangeX == FR) Range(0, src.getWidth) else srcRangeX + val _srcRangeY = if (srcRangeY == FR) Range(0, src.getHeight) else srcRangeY + + val (xStart, xEnd, xStep) = sortedRangeParams(_srcRangeX) + val (yStart, yEnd, yStep) = sortedRangeParams(_srcRangeY) + + val srcWidth = src.getWidth + val srcPixels = src.getPixels.asInstanceOf[Array[Float]] + + val dstWidth = _srcRangeX.length + val dstHeight = _srcRangeY.length + val dst = new FloatProcessor(dstWidth, dstHeight) + val dstPixels = dst.getPixels.asInstanceOf[Array[Float]] + + cfor(yStart)(_ < yEnd, _ + yStep) { y => + val srcOffsetY = y * srcWidth + val dstY = (y - _srcRangeY.start) / _srcRangeY.step + val dstOffsetY = dstY * dstWidth + cfor(xStart)(_ < xEnd, _ + xStep) { x => + val dstX = (x - _srcRangeX.start) / _srcRangeX.step + dstPixels(dstX + dstOffsetY) = srcPixels(x + srcOffsetY) + } + } + dst + } + + /** + * Copies elements between arrays within specified ranges + * + * MATLAB equivalent + * {{{ + * G0(:, 1:2:n) = bay(1:2:m, 1:2:n); + * }}} + * + * Scala code + * {{{ + * copyRanges( + * G0, Range(0, w, 2), FR, + * bay, Range(0, w, 2), Range(0, h, 2) + * ) + * }}} + * + * @param dstIP destination processor + * @param dstRangeX destination X range + * @param dstRangeY destination Y range + * @param srcIP source processor + * @param srcRangeX source X range + * @param srcRangeY source Y range + */ + final def copyRanges( + dstIP: ImageProcessor, + dstRangeX: Range, + dstRangeY: Range, + srcIP: ImageProcessor, + srcRangeX: Range, + srcRangeY: Range + ): Unit = { + import io.github.metarank.cfor.* + + val _dstRangeX = if (dstRangeX == FR) Range(0, dstIP.getWidth) else dstRangeX + val _dstRangeY = if (dstRangeY == FR) Range(0, dstIP.getHeight) else dstRangeY + val _srcRangeX = if (srcRangeX == FR) Range(0, srcIP.getWidth) else srcRangeX + val _srcRangeY = if (srcRangeY == FR) Range(0, srcIP.getHeight) else srcRangeY + + val (dstXStart, dstXEnd, dstXStep) = sortedRangeParams(_dstRangeX) + val (dstYStart, dstYEnd, dstYStep) = sortedRangeParams(_dstRangeY) + + cfor(dstYStart)(_ < dstYEnd, _ + dstYStep) { y => + val indexY = (y - _dstRangeY.start) / _dstRangeY.step + val srcY = _srcRangeY.start + indexY * _srcRangeY.step + val srcYOffset = srcY * srcIP.getWidth + val dstYOffset = y * dstIP.getWidth + + cfor(dstXStart)(_ < dstXEnd, _ + dstXStep) { x => + val indexX = (x - _dstRangeX.start) / _dstRangeX.step + val srcX = _srcRangeX.start + indexX * _srcRangeX.step + + val v = srcIP.getf(srcX + srcYOffset) + dstIP.setf(x + dstYOffset, v) + } + } + } + +} diff --git a/ijp-debayer2sx-core/src/main/scala-3/ij_plugins/debayer2sx/DeBayer2Config.scala b/ijp-debayer2sx-core/src/main/scala-3/ij_plugins/debayer2sx/DeBayer2Config.scala new file mode 100644 index 0000000..5bab9c0 --- /dev/null +++ b/ijp-debayer2sx-core/src/main/scala-3/ij_plugins/debayer2sx/DeBayer2Config.scala @@ -0,0 +1,76 @@ +/* + * Image/J Plugins + * Copyright (C) 2002-2021 Jarek Sacha + * Author's email: jpsacha at gmail [dot] com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Latest release available at https://github.com/ij-plugins/ijp-DeBayer2SX + */ + +package ij_plugins.debayer2sx + +import ij_plugins.debayer2sx.DeBayer2Config.{Demosaicing, MosaicOrder} + +import scala.collection.immutable + +object DeBayer2Config { + + /** + * Debayer algorithm type. + */ + enum Demosaicing(val entryName: String) { + override def toString: String = entryName + + case Replication extends Demosaicing("Replication") + case Bilinear extends Demosaicing("Bilinear") + case SmoothHue extends Demosaicing("Smooth Hue") + case AdaptiveSmoothHue extends Demosaicing("Adaptive Smooth Hue") + case DDFAPD extends Demosaicing("DDFAPD without Refining") + case DDFAPDRefined extends Demosaicing("DDFAPD with Refining") + } + + object Demosaicing { + val names: Array[String] = values.map(_.entryName).toArray + + def withName(name: String): Demosaicing = Demosaicing.valueOf(name) + + def withNameOption(name: String): Option[Demosaicing] = Option(Demosaicing.valueOf(name)) + } + + /** + * Order of filters in Bayer image. + * For instance B-G means that the first pixel in the first row is `B` the next one is `G`. + */ + enum MosaicOrder(val entryName: String, val bayer1ID: Int) { + case B_G extends MosaicOrder("B-G", 1) + case G_B extends MosaicOrder("G-B", 3) + case G_R extends MosaicOrder("G-R", 2) + case R_G extends MosaicOrder("R-G", 0) + + override def toString: String = entryName + } + + object MosaicOrder { + val names: Array[String] = values.map(_.entryName).toArray + + def withName(name: String): MosaicOrder = MosaicOrder.valueOf(name) + + def withNameOption(name: String): Option[MosaicOrder] = Option(MosaicOrder.valueOf(name)) + } + +} + +case class DeBayer2Config(mosaicOrder: MosaicOrder = MosaicOrder.R_G, demosaicing: Demosaicing = Demosaicing.DDFAPD) diff --git a/ijp-debayer2sx-core/src/main/scala-3/ij_plugins/debayer2sx/LoopUtils.scala b/ijp-debayer2sx-core/src/main/scala-3/ij_plugins/debayer2sx/LoopUtils.scala new file mode 100644 index 0000000..31e8991 --- /dev/null +++ b/ijp-debayer2sx-core/src/main/scala-3/ij_plugins/debayer2sx/LoopUtils.scala @@ -0,0 +1,187 @@ +/* + * Image/J Plugins + * Copyright (C) 2002-2021 Jarek Sacha + * Author's email: jpsacha at gmail [dot] com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Latest release available at https://github.com/ij-plugins/ijp-DeBayer2SX + */ + +package ij_plugins.debayer2sx + +import ij.process.{FloatProcessor, ImageProcessor} +import ij_plugins.debayer2sx.process.{FR, sortedRangeParams} + +import java.awt.Rectangle + +object LoopUtils { + + final def crop(src: FloatProcessor, roi: Rectangle): FloatProcessor = { + val width = src.getWidth + val pixels = src.getPixels.asInstanceOf[Array[Float]] + val roiX = roi.x + val roiY = roi.y + val roiWidth = roi.width + val roiHeight = roi.height + val ip2 = new FloatProcessor(roiWidth, roiHeight) + val pixels2 = ip2.getPixels.asInstanceOf[Array[Float]] + // for (ys <- roiY until roiY + roiHeight) { + + var ys = roiY + while (ys < roiY + roiHeight) { + var offset1 = (ys - roiY) * roiWidth + var offset2 = ys * width + roiX + // for (xs <- 0 until roiWidth) { + var xs = 0 + while (xs < roiWidth) { + pixels2(offset1) = pixels(offset2) + offset1 += 1 + offset2 += 1 + xs += 1 + } + ys += 1 + } + ip2 + } + + /** + * Clip values in the image to the range specified by `bpp`. + * + * {{{ + * min = 0 + * max = 2^bpp - 1 + * }}} + * + * @param ip image to check + * @param bpp bits per pixel + */ + final def checkImg(ip: FloatProcessor, bpp: Int): Unit = { + val maxVal = (math.pow(2, bpp) - 1).toFloat + val pixels = ip.getPixels.asInstanceOf[Array[Float]] + + // for (i <- pixels.indices) { + var i = 0 + while (i < pixels.length) { + val v = pixels(i) + if (v > maxVal) { + pixels(i) = maxVal + } else if (v < 0) { + pixels(i) = 0 + } + + i += 1 + } + } + + @inline + final def slice(src: FloatProcessor, srcRangeX: Range, srcRangeY: Range): FloatProcessor = { + // bay(1:2:m,2:2:n) + val _srcRangeX = if (srcRangeX == FR) Range(0, src.getWidth) else srcRangeX + val _srcRangeY = if (srcRangeY == FR) Range(0, src.getHeight) else srcRangeY + + val (xStart, xEnd, xStep) = sortedRangeParams(_srcRangeX) + val (yStart, yEnd, yStep) = sortedRangeParams(_srcRangeY) + + val srcWidth = src.getWidth + val srcPixels = src.getPixels.asInstanceOf[Array[Float]] + + val dstWidth = _srcRangeX.length + val dstHeight = _srcRangeY.length + val dst = new FloatProcessor(dstWidth, dstHeight) + val dstPixels = dst.getPixels.asInstanceOf[Array[Float]] + + var y = yStart + while (y < yEnd) { + val srcOffsetY = y * srcWidth + val dstY = (y - _srcRangeY.start) / _srcRangeY.step + val dstOffsetY = dstY * dstWidth + + var x = xStart + while (x < xEnd) { + val dstX = (x - _srcRangeX.start) / _srcRangeX.step + dstPixels(dstX + dstOffsetY) = srcPixels(x + srcOffsetY) + + x += xStep + } + + y += yStep + } + dst + } + + /** + * Copies elements between arrays within specified ranges + * + * MATLAB equivalent + * {{{ + * G0(:, 1:2:n) = bay(1:2:m, 1:2:n); + * }}} + * + * Scala code + * {{{ + * copyRanges( + * G0, Range(0, w, 2), FR, + * bay, Range(0, w, 2), Range(0, h, 2) + * ) + * }}} + * + * @param dstIP destination processor + * @param dstRangeX destination X range + * @param dstRangeY destination Y range + * @param srcIP source processor + * @param srcRangeX source X range + * @param srcRangeY source Y range + */ + final def copyRanges( + dstIP: ImageProcessor, + dstRangeX: Range, + dstRangeY: Range, + srcIP: ImageProcessor, + srcRangeX: Range, + srcRangeY: Range + ): Unit = { + + val _dstRangeX = if (dstRangeX == FR) Range(0, dstIP.getWidth) else dstRangeX + val _dstRangeY = if (dstRangeY == FR) Range(0, dstIP.getHeight) else dstRangeY + val _srcRangeX = if (srcRangeX == FR) Range(0, srcIP.getWidth) else srcRangeX + val _srcRangeY = if (srcRangeY == FR) Range(0, srcIP.getHeight) else srcRangeY + + val (dstXStart, dstXEnd, dstXStep) = sortedRangeParams(_dstRangeX) + val (dstYStart, dstYEnd, dstYStep) = sortedRangeParams(_dstRangeY) + + var y = dstYStart + while (y < dstYEnd) { + val indexY = (y - _dstRangeY.start) / _dstRangeY.step + val srcY = _srcRangeY.start + indexY * _srcRangeY.step + val srcYOffset = srcY * srcIP.getWidth + val dstYOffset = y * dstIP.getWidth + + var x = dstXStart + while (x < dstXEnd) { + val indexX = (x - _dstRangeX.start) / _dstRangeX.step + val srcX = _srcRangeX.start + indexX * _srcRangeX.step + + val v = srcIP.getf(srcX + srcYOffset) + dstIP.setf(x + dstYOffset, v) + + x += dstXStep + } + + y += dstYStep + } + } + +} diff --git a/ijp-debayer2sx-core/src/main/scala/ij_plugins/debayer2sx/DDFAPD.scala b/ijp-debayer2sx-core/src/main/scala/ij_plugins/debayer2sx/DDFAPD.scala index 288b7e9..d179c48 100644 --- a/ijp-debayer2sx-core/src/main/scala/ij_plugins/debayer2sx/DDFAPD.scala +++ b/ijp-debayer2sx-core/src/main/scala/ij_plugins/debayer2sx/DDFAPD.scala @@ -24,8 +24,9 @@ package ij_plugins.debayer2sx import ij.ImageStack import ij.plugin.filter.Convolver -import ij.process._ -import ij_plugins.debayer2sx.process.{FR, add, copyRanges, _} +import ij.process.* +import ij_plugins.debayer2sx.LoopUtils.{checkImg, copyRanges, crop} +import ij_plugins.debayer2sx.process.{FR, add, *} import java.awt.Rectangle @@ -40,7 +41,6 @@ object DDFAPD { // Parts of the implementation is based on the reference MATLAB code by Daniele Menon. - /** * Convert Bayer encoded image to to stack of color bands (red, green, blue). * The demosaicing is done assuming GB bayer pattern variant (top left corner pixel is G, next in row is R). @@ -59,7 +59,6 @@ object DDFAPD { cp } - /** * Convert Bayer encoded image to to stack of color bands (red, green, blue). * The demosaicing is done assuming GB bayer pattern variant (top left corner pixel is G, next in row is R). @@ -112,7 +111,8 @@ object DDFAPD { // // out=ddfapd_refining(out,interpDir); // out=check_img(out,bpp); - val (r, g, b) = if (doRefine) { + val (r, g, b) = + if (doRefine) { val r4 = refining(outR, outG, outB, interpDir) val outRRefined = r4._1 val outGRefined = r4._2 @@ -134,7 +134,6 @@ object DDFAPD { stack } - /** * Directional interpolation of the green channel. * Reconstructs two estimates of the green channel from the bayer data `bay`, @@ -164,16 +163,24 @@ object DDFAPD { // G0(:,1:2:n)=bay(1:2:m,1:2:n); val G0 = new FloatProcessor(w, h / 2) copyRanges( - G0, Range(0, w, 2), Range(0, h / 2), - bay, Range(0, w, 2), Range(0, h, 2) + G0, + Range(0, w, 2), + Range(0, h / 2), + bay, + Range(0, w, 2), + Range(0, h, 2) ) // R1=zeros(m/2,n); // R1(:,2:2:n)=bay(1:2:m,2:2:n); val R1 = new FloatProcessor(w, h / 2) copyRanges( - R1, Range(1, w, 2), Range(0, h / 2), - bay, Range(1, w, 2), Range(0, h, 2) + R1, + Range(1, w, 2), + Range(0, h / 2), + bay, + Range(1, w, 2), + Range(0, h, 2) ) // f1=filtImg(h1+[0 0 1 0 0],G0,1); @@ -272,89 +279,36 @@ object DDFAPD { // B = (length(h)-1)/2; val B = (hh.length - 1) / 2 - val y = if (dir == 1) { - // Add mirroring of the borders - // xx = [x(:,1+B:-1:2), x, x(:,n-1:-1:n-B)]; - val xx = mirrorBorderWidth(x, B) - - // y=conv2(1,h,xx,'valid'); - new Convolver().convolveFloat1D(xx, hh, hh.length, 1, 1) - val cropROI = new Rectangle(B, 0, w, h) - // xx.setRoi(cropROI) - // xx.crop().asInstanceOf[FloatProcessor] - crop(xx, cropROI) - } else if (dir == 2) { - // Add mirroring of the borders - // xx = [x(1+B:-1:2,:); x; x(m-1:-1:m-B,:)]; - val xx = mirrorBorderHeight(x: FloatProcessor, B) - - // y=conv2(h,1,xx,'valid'); - new Convolver().convolveFloat1D(xx, hh, 1, hh.length, 1) - val cropROI = new Rectangle(0, B, w, h) - // xx.setRoi(cropROI) - // xx.crop().asInstanceOf[FloatProcessor] - crop(xx, cropROI) - } else { - throw new IllegalArgumentException("Invalid `dir` value:" + dir) - } - - y - } - - private[this] def crop(src: FloatProcessor, roi: Rectangle): FloatProcessor = { - import io.github.metarank.cfor._ - - val width = src.getWidth - val pixels = src.getPixels.asInstanceOf[Array[Float]] - val roiX = roi.x - val roiY = roi.y - val roiWidth = roi.width - val roiHeight = roi.height - val ip2 = new FloatProcessor(roiWidth, roiHeight) - val pixels2 = ip2.getPixels.asInstanceOf[Array[Float]] - // for (ys <- roiY until roiY + roiHeight) { - cfor(roiY)(_ < roiY + roiHeight, _ + 1) { ys => - var offset1 = (ys - roiY) * roiWidth - var offset2 = ys * width + roiX - // for (xs <- 0 until roiWidth) { - cfor(0)(_ < roiWidth, _ + 1) { _ => - pixels2(offset1) = pixels(offset2) - offset1 += 1 - offset2 += 1 + val y = + if (dir == 1) { + // Add mirroring of the borders + // xx = [x(:,1+B:-1:2), x, x(:,n-1:-1:n-B)]; + val xx = mirrorBorderWidth(x, B) + + // y=conv2(1,h,xx,'valid'); + new Convolver().convolveFloat1D(xx, hh, hh.length, 1, 1) + val cropROI = new Rectangle(B, 0, w, h) + // xx.setRoi(cropROI) + // xx.crop().asInstanceOf[FloatProcessor] + crop(xx, cropROI) + } else if (dir == 2) { + // Add mirroring of the borders + // xx = [x(1+B:-1:2,:); x; x(m-1:-1:m-B,:)]; + val xx = mirrorBorderHeight(x: FloatProcessor, B) + + // y=conv2(h,1,xx,'valid'); + new Convolver().convolveFloat1D(xx, hh, 1, hh.length, 1) + val cropROI = new Rectangle(0, B, w, h) + // xx.setRoi(cropROI) + // xx.crop().asInstanceOf[FloatProcessor] + crop(xx, cropROI) + } else { + throw new IllegalArgumentException("Invalid `dir` value:" + dir) } - } - ip2 - } - /** - * Clip values in the image to the range specified by `bpp`. - * - * {{{ - * min = 0 - * max = 2^bpp - 1 - * }}} - * - * @param ip image to check - * @param bpp bits per pixel - */ - private[this] def checkImg(ip: FloatProcessor, bpp: Int): Unit = { - import io.github.metarank.cfor._ - - val maxVal = (math.pow(2, bpp) - 1).toFloat - val pixels = ip.getPixels.asInstanceOf[Array[Float]] - - // for (i <- pixels.indices) { - cfor(0)(_ < pixels.length, _ + 1) { i => - val v = pixels(i) - if (v > maxVal) { - pixels(i) = maxVal - } else if (v < 0) { - pixels(i) = 0 - } - } + y } - /** * The absolute norm between two values. */ @@ -381,7 +335,11 @@ object DDFAPD { * and if interpDir(i,j)==0 no estimation was performed (for the G positions) * * */ - private[debayer2sx] def decision(Gh: FloatProcessor, Gv: FloatProcessor, bay: FloatProcessor): (FloatProcessor, ByteProcessor) = { + private[debayer2sx] def decision( + Gh: FloatProcessor, + Gv: FloatProcessor, + bay: FloatProcessor + ): (FloatProcessor, ByteProcessor) = { val h = Gh.getHeight val w = Gh.getWidth val hh = h - 2 @@ -477,9 +435,15 @@ object DDFAPD { { val y = Range(3 - 1, hh, 2) val x = Range(4 - 1, ww, 2) - val vh = DH(x - 2, y - 2) + DH(x, y - 2) + DH(x - 2, y) * c + DH(x, y) * c + DH(x - 2, y + 2) + DH(x, y + 2) + DH(x - 1, y - 1) + DH(x - 1, y + 1) + val vh = DH(x - 2, y - 2) + DH(x, y - 2) + DH(x - 2, y) * c + DH(x, y) * c + DH(x - 2, y + 2) + DH(x, y + 2) + DH( + x - 1, + y - 1 + ) + DH(x - 1, y + 1) copyRanges(DeltaH, x, y, vh, FR, FR) - val vv = DV(x - 2, y - 2) + DV(x, y - 2) * c + DV(x + 2, y - 2) + DV(x - 2, y) + DV(x, y) * c + DV(x + 2, y) + DV(x - 1, y - 1) + DV(x + 1, y - 1) + val vv = DV(x - 2, y - 2) + DV(x, y - 2) * c + DV(x + 2, y - 2) + DV(x - 2, y) + DV(x, y) * c + DV(x + 2, y) + DV( + x - 1, + y - 1 + ) + DV(x + 1, y - 1) copyRanges(DeltaV, x, y, vv, FR, FR) } @@ -490,9 +454,15 @@ object DDFAPD { { val y = Range(4 - 1, hh, 2) val x = Range(3 - 1, ww, 2) - val vh = DH(x - 2, y - 2) + DH(x, y - 2) + DH(x - 2, y) * c + DH(x, y) * c + DH(x - 2, y + 2) + DH(x, y + 2) + DH(x - 1, y - 1) + DH(x - 1, y + 1) + val vh = DH(x - 2, y - 2) + DH(x, y - 2) + DH(x - 2, y) * c + DH(x, y) * c + DH(x - 2, y + 2) + DH(x, y + 2) + DH( + x - 1, + y - 1 + ) + DH(x - 1, y + 1) copyRanges(DeltaH, x, y, vh, FR, FR) - val vv = DV(x - 2, y - 2) + DV(x, y - 2) * c + DV(x + 2, y - 2) + DV(x - 2, y) + DV(x, y) * c + DV(x + 2, y) + DV(x - 1, y - 1) + DV(x + 1, y - 1) + val vv = DV(x - 2, y - 2) + DV(x, y - 2) * c + DV(x + 2, y - 2) + DV(x - 2, y) + DV(x, y) * c + DV(x + 2, y) + DV( + x - 1, + y - 1 + ) + DV(x + 1, y - 1) copyRanges(DeltaV, x, y, vv, FR, FR) } @@ -504,9 +474,15 @@ object DDFAPD { { val y = Range(2 - 1, 2) val x = Range(3 - 1, ww, 2) - val vh = DH(x - 2, y + 2) + DH(x, y + 2) + DH(x - 2, y) * c + DH(x, y) * c + DH(x - 2, y + 2) + DH(x, y + 2) + DH(x - 1, y - 1) + DH(x - 1, y + 1) + val vh = DH(x - 2, y + 2) + DH(x, y + 2) + DH(x - 2, y) * c + DH(x, y) * c + DH(x - 2, y + 2) + DH(x, y + 2) + DH( + x - 1, + y - 1 + ) + DH(x - 1, y + 1) copyRanges(DeltaH, x, y, vh, FR, FR) - val vv = DV(x - 2, y + 2) + DV(x, y + 2) * c + DV(x + 2, y + 2) + DV(x - 2, y) + DV(x, y) * c + DV(x + 2, y) + DV(x - 1, y - 1) + DV(x + 1, y - 1) + val vv = DV(x - 2, y + 2) + DV(x, y + 2) * c + DV(x + 2, y + 2) + DV(x - 2, y) + DV(x, y) * c + DV(x + 2, y) + DV( + x - 1, + y - 1 + ) + DV(x + 1, y - 1) copyRanges(DeltaV, x, y, vv, FR, FR) } @@ -517,9 +493,15 @@ object DDFAPD { { val y = Range(h - 1 - 1, h - 1) val x = Range(4 - 1, ww, 2) - val vh = DH(x - 2, y - 2) + DH(x, y - 2) + DH(x - 2, y) * c + DH(x, y) * c + DH(x - 2, y - 2) + DH(x, y - 2) + DH(x - 1, y - 1) + DH(x - 1, y + 1) + val vh = DH(x - 2, y - 2) + DH(x, y - 2) + DH(x - 2, y) * c + DH(x, y) * c + DH(x - 2, y - 2) + DH(x, y - 2) + DH( + x - 1, + y - 1 + ) + DH(x - 1, y + 1) copyRanges(DeltaH, x, y, vh, FR, FR) - val vv = DV(x - 2, y - 2) + DV(x, y - 2) * c + DV(x + 2, y - 2) + DV(x - 2, y) + DV(x, y) * c + DV(x + 2, y) + DV(x - 1, y - 1) + DV(x + 1, y - 1) + val vv = DV(x - 2, y - 2) + DV(x, y - 2) * c + DV(x + 2, y - 2) + DV(x - 2, y) + DV(x, y) * c + DV(x + 2, y) + DV( + x - 1, + y - 1 + ) + DV(x + 1, y - 1) copyRanges(DeltaV, x, y, vv, FR, FR) } @@ -530,9 +512,15 @@ object DDFAPD { { val y = Range(2 - 1, 2) val x = Range(3 - 1, hh, 2) - val vh = DH(y + 2, x - 2) + DH(y, x - 2) + DH(y + 2, x) * c + DH(y, x) * c + DH(y + 2, x + 2) + DH(y, x + 2) + DH(y - 1, x - 1) + DH(y - 1, x + 1) + val vh = DH(y + 2, x - 2) + DH(y, x - 2) + DH(y + 2, x) * c + DH(y, x) * c + DH(y + 2, x + 2) + DH(y, x + 2) + DH( + y - 1, + x - 1 + ) + DH(y - 1, x + 1) copyRanges(DeltaH, y, x, vh, FR, FR) - val vv = DV(y + 2, x - 2) + DV(y, x - 2) * c + DV(y + 2, x - 2) + DV(y + 2, x) + DV(y, x) * c + DV(y + 2, x) + DV(y - 1, x - 1) + DV(y + 1, x - 1) + val vv = DV(y + 2, x - 2) + DV(y, x - 2) * c + DV(y + 2, x - 2) + DV(y + 2, x) + DV(y, x) * c + DV(y + 2, x) + DV( + y - 1, + x - 1 + ) + DV(y + 1, x - 1) copyRanges(DeltaV, y, x, vv, FR, FR) } @@ -543,9 +531,15 @@ object DDFAPD { { val y = Range(w - 1 - 1, w - 1) val x = Range(4 - 1, hh, 2) - val vh = DH(y - 2, x - 2) + DH(y, x - 2) + DH(y - 2, x) * c + DH(y, x) * c + DH(y - 2, x + 2) + DH(y, x + 2) + DH(y - 1, x - 1) + DH(y - 1, x + 1) + val vh = DH(y - 2, x - 2) + DH(y, x - 2) + DH(y - 2, x) * c + DH(y, x) * c + DH(y - 2, x + 2) + DH(y, x + 2) + DH( + y - 1, + x - 1 + ) + DH(y - 1, x + 1) copyRanges(DeltaH, y, x, vh, FR, FR) - val vv = DV(y - 2, x - 2) + DV(y, x - 2) * c + DV(y - 2, x - 2) + DV(y - 2, x) + DV(y, x) * c + DV(y - 2, x) + DV(y - 1, x - 1) + DV(y + 1, x - 1) + val vv = DV(y - 2, x - 2) + DV(y, x - 2) * c + DV(y - 2, x - 2) + DV(y - 2, x) + DV(y, x) * c + DV(y - 2, x) + DV( + y - 1, + x - 1 + ) + DV(y + 1, x - 1) copyRanges(DeltaV, y, x, vv, FR, FR) } @@ -572,7 +566,6 @@ object DDFAPD { } } - // Reconstruction near the border of the image // outG(1,:)=Gh(1,:); copyRanges(dstG, FR, Range(0, 1), Gh, FR, Range(0, 1)) @@ -590,7 +583,14 @@ object DDFAPD { // outG(2,n-1)=Gh(2,n-1); copyRanges(dstG, Range(w - 1 - 1, w - 1), Range(1, 2), Gh, Range(w - 1 - 1, w - 1), Range(1, 2)) // outG(m-1,n-1)=Gh(m-1,n-1); - copyRanges(dstG, Range(w - 1 - 1, w - 1), Range(h - 1 - 1, h - 1), Gh, Range(w - 1 - 1, w - 1), Range(h - 1 - 1, h - 1)) + copyRanges( + dstG, + Range(w - 1 - 1, w - 1), + Range(h - 1 - 1, h - 1), + Gh, + Range(w - 1 - 1, w - 1), + Range(h - 1 - 1, h - 1) + ) (dstG, interpDir) } @@ -606,7 +606,11 @@ object DDFAPD { * @param interpDir best interpolation direction * @return Reconstructed channels red and blue. */ - private[debayer2sx] def interpRB(bay: FloatProcessor, G: FloatProcessor, interpDir: ByteProcessor): (FloatProcessor, FloatProcessor) = { + private[debayer2sx] def interpRB( + bay: FloatProcessor, + G: FloatProcessor, + interpDir: ByteProcessor + ): (FloatProcessor, FloatProcessor) = { val h = G.getHeight val w = G.getWidth @@ -712,7 +716,11 @@ object DDFAPD { for (y <- Range(1, h - 1, 2)) { for (x <- Range(2, w - 1, 2)) { if (interpDir.get(x, y) == 1) { - R.setf(x, y, B.getf(x, y) + 1 / 2f * (R.getf(x - 1, y) - B.getf(x - 1, y) + R.getf(x + 1, y) - B.getf(x + 1, y))) + R.setf( + x, + y, + B.getf(x, y) + 1 / 2f * (R.getf(x - 1, y) - B.getf(x - 1, y) + R.getf(x + 1, y) - B.getf(x + 1, y)) + ) } else { val v = B.getf(x, y) + 1 / 2f * (R.getf(x, y - 1) - B.getf(x, y - 1) + R.getf(x, y + 1) - B.getf(x, y + 1)) R.setf(x, y, v) @@ -720,7 +728,6 @@ object DDFAPD { } } - // for i=3:2:m-1, // for j=2:2:n-2, // if interpDir(i,j)==1, @@ -733,9 +740,17 @@ object DDFAPD { for (y <- Range(2, h - 1, 2)) { for (x <- Range(1, w - 2, 2)) { if (interpDir.get(x, y) == 1) { - B.setf(x, y, R.getf(x, y) + 1 / 2f * (B.getf(x - 1, y) - R.getf(x - 1, y) + B.getf(x + 1, y) - R.getf(x + 1, y))) + B.setf( + x, + y, + R.getf(x, y) + 1 / 2f * (B.getf(x - 1, y) - R.getf(x - 1, y) + B.getf(x + 1, y) - R.getf(x + 1, y)) + ) } else { - B.setf(x, y, R.getf(x, y) + 1 / 2f * (B.getf(x, y - 1) - R.getf(x, y - 1) + B.getf(x, y + 1) - R.getf(x, y + 1))) + B.setf( + x, + y, + R.getf(x, y) + 1 / 2f * (B.getf(x, y - 1) - R.getf(x, y - 1) + B.getf(x, y + 1) - R.getf(x, y + 1)) + ) } } } @@ -755,7 +770,12 @@ object DDFAPD { * @param interpDir optimal interpolation direction * @return bands of the reconstructed image */ - def refining(R: FloatProcessor, G: FloatProcessor, B: FloatProcessor, interpDir: ByteProcessor): (FloatProcessor, FloatProcessor, FloatProcessor) = { + def refining( + R: FloatProcessor, + G: FloatProcessor, + B: FloatProcessor, + interpDir: ByteProcessor + ): (FloatProcessor, FloatProcessor, FloatProcessor) = { val h = R.getHeight val w = R.getWidth @@ -766,7 +786,6 @@ object DDFAPD { val medBlessG = new FloatProcessor(w, h) val medRlessB = new FloatProcessor(w, h) - // ff=[1 1 1]/3; val ff = 1 / 3f @@ -776,7 +795,6 @@ object DDFAPD { val RlessG = R - G val BlessG = B - G - // Refining of the green // for i=2:2:m-1 // for j=3:2:n-1 @@ -789,13 +807,14 @@ object DDFAPD { // end for (y <- 2 - 1 until h - 1 by 2) { for (x <- 3 - 1 until w - 1 by 2) { - val v = if (interpDir.get(x, y) == 1) { - // medBlessG(i,j)=ff*BlessG(i,j-1:j+1).'; - ff * (BlessG.getf(x - 1, y) + BlessG.getf(x, y) + BlessG.getf(x + 1, y)) - } else { - // medBlessG(i,j)=ff*BlessG(i-1:i+1,j); - ff * (BlessG.getf(x, y - 1) + BlessG.getf(x, y) + BlessG.getf(x, y + 1)) - } + val v = + if (interpDir.get(x, y) == 1) { + // medBlessG(i,j)=ff*BlessG(i,j-1:j+1).'; + ff * (BlessG.getf(x - 1, y) + BlessG.getf(x, y) + BlessG.getf(x + 1, y)) + } else { + // medBlessG(i,j)=ff*BlessG(i-1:i+1,j); + ff * (BlessG.getf(x, y - 1) + BlessG.getf(x, y) + BlessG.getf(x, y + 1)) + } medBlessG.setf(x, y, v) } } @@ -810,26 +829,29 @@ object DDFAPD { // end for (y <- 3 - 1 until h - 1 by 2) { for (x <- 2 - 1 until w - 1 by 2) { - val v = if (interpDir.get(x, y) == 1) { - // medRlessG(i,j)=ff*RlessG(i,j-1:j+1).'; - ff * (RlessG.getf(x - 1, y) + RlessG.getf(x, y) + RlessG.getf(x + 1, y)) - } else { - // medRlessG(i,j)=ff*RlessG(i-1:i+1,j); - ff * (RlessG.getf(x, y - 1) + RlessG.getf(x, y) + RlessG.getf(x, y + 1)) - } + val v = + if (interpDir.get(x, y) == 1) { + // medRlessG(i,j)=ff*RlessG(i,j-1:j+1).'; + ff * (RlessG.getf(x - 1, y) + RlessG.getf(x, y) + RlessG.getf(x + 1, y)) + } else { + // medRlessG(i,j)=ff*RlessG(i-1:i+1,j); + ff * (RlessG.getf(x, y - 1) + RlessG.getf(x, y) + RlessG.getf(x, y + 1)) + } medRlessG.setf(x, y, v) } } // G(3:2:m-1,2:2:n-1)=R(3:2:m-1,2:2:n-1)-medRlessG(3:2:m-1,2:2:n-1); { - val v = R(Range(2 - 1, w - 1, 2), Range(3 - 1, h - 1, 2)) - medRlessG(Range(2 - 1, w - 1, 2), Range(3 - 1, h - 1, 2)) + val v = + R(Range(2 - 1, w - 1, 2), Range(3 - 1, h - 1, 2)) - medRlessG(Range(2 - 1, w - 1, 2), Range(3 - 1, h - 1, 2)) copyRanges(G, Range(2 - 1, w - 1, 2), Range(3 - 1, h - 1, 2), v, FR, FR) } // G(2:2:m-1,3:2:n-1)=B(2:2:m-1,3:2:n-1)-medBlessG(2:2:m-1,3:2:n-1); { - val v = B(Range(3 - 1, w - 1, 2), Range(2 - 1, h - 1, 2)) - medBlessG(Range(3 - 1, w - 1, 2), Range(2 - 1, h - 1, 2)) + val v = + B(Range(3 - 1, w - 1, 2), Range(2 - 1, h - 1, 2)) - medBlessG(Range(3 - 1, w - 1, 2), Range(2 - 1, h - 1, 2)) copyRanges(G, Range(3 - 1, w - 1, 2), Range(2 - 1, h - 1, 2), v, FR, FR) } } @@ -904,13 +926,14 @@ object DDFAPD { // end for (y <- 2 - 1 until h - 1 by 2) { for (x <- 3 - 1 until w - 1 by 2) { - val v = if (interpDir.get(x, y) == 1) { - // medRlessB(i,j)=ff*RlessB(i,j-1:j+1).'; - ff * (RlessB.getf(x - 1, y) + RlessB.getf(x, y) + RlessB.getf(x + 1, y)) - } else { - // medRlessB(i,j)=ff*RlessB(i-1:i+1,j); - ff * (RlessB.getf(x, y - 1) + RlessB.getf(x, y) + RlessB.getf(x, y + 1)) - } + val v = + if (interpDir.get(x, y) == 1) { + // medRlessB(i,j)=ff*RlessB(i,j-1:j+1).'; + ff * (RlessB.getf(x - 1, y) + RlessB.getf(x, y) + RlessB.getf(x + 1, y)) + } else { + // medRlessB(i,j)=ff*RlessB(i-1:i+1,j); + ff * (RlessB.getf(x, y - 1) + RlessB.getf(x, y) + RlessB.getf(x, y + 1)) + } medRlessB.setf(x, y, v) // R(i,j)=B(i,j)+medRlessB(i,j); @@ -918,7 +941,6 @@ object DDFAPD { } } - // Refining of the blue in the red pixels // for i=3:2:m-1, // for j=2:2:n-2, @@ -932,13 +954,14 @@ object DDFAPD { // end for (y <- 3 - 1 until h - 1 by 2) { for (x <- 2 - 1 until w - 2 by 2) { - val v = if (interpDir.get(x, y) == 1) { - // medRlessB(i,j)=ff*RlessB(i,j-1:j+1).'; - ff * (RlessB.getf(x - 1, y) + RlessB.getf(x, y) + RlessB.getf(x + 1, y)) - } else { - // medRlessB(i,j)=ff*RlessB(i-1:i+1,j); - ff * (RlessB.getf(x, y - 1) + RlessB.getf(x, y) + RlessB.getf(x, y + 1)) - } + val v = + if (interpDir.get(x, y) == 1) { + // medRlessB(i,j)=ff*RlessB(i,j-1:j+1).'; + ff * (RlessB.getf(x - 1, y) + RlessB.getf(x, y) + RlessB.getf(x + 1, y)) + } else { + // medRlessB(i,j)=ff*RlessB(i-1:i+1,j); + ff * (RlessB.getf(x, y - 1) + RlessB.getf(x, y) + RlessB.getf(x, y + 1)) + } medRlessB.setf(x, y, v) // B(i,j)=R(i,j)-medRlessB(i,j); diff --git a/ijp-debayer2sx-core/src/main/scala/ij_plugins/debayer2sx/DeBayer2.scala b/ijp-debayer2sx-core/src/main/scala/ij_plugins/debayer2sx/DeBayer2.scala index d6ce7b7..45f8de1 100644 --- a/ijp-debayer2sx-core/src/main/scala/ij_plugins/debayer2sx/DeBayer2.scala +++ b/ijp-debayer2sx-core/src/main/scala/ij_plugins/debayer2sx/DeBayer2.scala @@ -23,10 +23,11 @@ package ij_plugins.debayer2sx import ij.ImageStack -import ij.process._ +import ij.process.* import ij_plugins.debayer2sx.DDFAPD.debayerGR import ij_plugins.debayer2sx.DeBayer2Config.{Demosaicing, MosaicOrder} -import ij_plugins.debayer2sx.process.{FR, copyRanges} +import ij_plugins.debayer2sx.LoopUtils.copyRanges +import ij_plugins.debayer2sx.process.FR object DeBayer2 { @@ -59,8 +60,8 @@ object DeBayer2 { debayerDDFAPD(ip.convertToFloatProcessor(), bbp, doRefine = false, config.mosaicOrder) case Demosaicing.DDFAPDRefined => debayerDDFAPD(ip.convertToFloatProcessor(), bbp, doRefine = true, config.mosaicOrder) - case x => - throw new UnsupportedOperationException("Unsupported demosaicing type: " + x) + case null => + throw new UnsupportedOperationException("config.demosaicing cannot be null") } stack.setSliceLabel("Red", 1) @@ -121,7 +122,6 @@ object DeBayer2 { dst } - private def debayerDDFAPD(src: FloatProcessor, bbp: Int, doRefine: Boolean, order: MosaicOrder): ImageStack = { val w = src.getWidth @@ -184,5 +184,4 @@ object DeBayer2 { } - } diff --git a/ijp-debayer2sx-core/src/main/scala/ij_plugins/debayer2sx/MakeBayer.scala b/ijp-debayer2sx-core/src/main/scala/ij_plugins/debayer2sx/MakeBayer.scala index bc4a51b..80e2f01 100644 --- a/ijp-debayer2sx-core/src/main/scala/ij_plugins/debayer2sx/MakeBayer.scala +++ b/ijp-debayer2sx-core/src/main/scala/ij_plugins/debayer2sx/MakeBayer.scala @@ -25,7 +25,7 @@ package ij_plugins.debayer2sx import ij.ImageStack import ij.process.{ByteProcessor, ColorProcessor, FloatProcessor, ImageProcessor} import ij_plugins.debayer2sx.DeBayer2Config.MosaicOrder -import ij_plugins.debayer2sx.process.copyRanges +import ij_plugins.debayer2sx.LoopUtils.copyRanges /** * Create Bayer images from color images. This is mostly useful for testing and demos. @@ -54,7 +54,6 @@ object MakeBayer { stack } - /** * Encode color image in a Bayer pattern. * diff --git a/ijp-debayer2sx-core/src/main/scala/ij_plugins/debayer2sx/package.scala b/ijp-debayer2sx-core/src/main/scala/ij_plugins/debayer2sx/package.scala index ef396ce..8352d1c 100644 --- a/ijp-debayer2sx-core/src/main/scala/ij_plugins/debayer2sx/package.scala +++ b/ijp-debayer2sx-core/src/main/scala/ij_plugins/debayer2sx/package.scala @@ -23,11 +23,10 @@ package ij_plugins import ij.process.{ByteProcessor, ColorProcessor, FloatProcessor} -import ij_plugins.debayer2sx.process._ +import ij_plugins.debayer2sx.LoopUtils.copyRanges package object debayer2sx { - /** * The trivial reconstruction algorithm that simply copies values to corresponding band, * no interpolation or reconstruction. diff --git a/ijp-debayer2sx-core/src/main/scala/ij_plugins/debayer2sx/process/FloatProcessorMath.scala b/ijp-debayer2sx-core/src/main/scala/ij_plugins/debayer2sx/process/FloatProcessorMath.scala index be56035..14b43b9 100644 --- a/ijp-debayer2sx-core/src/main/scala/ij_plugins/debayer2sx/process/FloatProcessorMath.scala +++ b/ijp-debayer2sx-core/src/main/scala/ij_plugins/debayer2sx/process/FloatProcessorMath.scala @@ -23,10 +23,10 @@ package ij_plugins.debayer2sx.process import ij.process.{Blitter, FloatBlitter, FloatProcessor} +import ij_plugins.debayer2sx.LoopUtils.slice import scala.language.implicitConversions - object FloatProcessorMath { implicit def toFP(fpm: FloatProcessorMath): FloatProcessor = fpm.fp @@ -133,34 +133,4 @@ final class FloatProcessorMath(val fp: FloatProcessor) { r } - @inline - private[this] def slice(src: FloatProcessor, srcRangeX: Range, srcRangeY: Range): FloatProcessor = { - import io.github.metarank.cfor._ - - // bay(1:2:m,2:2:n) - val _srcRangeX = if (srcRangeX == FR) Range(0, src.getWidth) else srcRangeX - val _srcRangeY = if (srcRangeY == FR) Range(0, src.getHeight) else srcRangeY - - val (xStart, xEnd, xStep) = sortedRangeParams(_srcRangeX) - val (yStart, yEnd, yStep) = sortedRangeParams(_srcRangeY) - - val srcWidth = src.getWidth - val srcPixels = src.getPixels.asInstanceOf[Array[Float]] - - val dstWidth = _srcRangeX.length - val dstHeight = _srcRangeY.length - val dst = new FloatProcessor(dstWidth, dstHeight) - val dstPixels = dst.getPixels.asInstanceOf[Array[Float]] - - cfor(yStart)(_ < yEnd, _ + yStep) { y => - val srcOffsetY = y * srcWidth - val dstY = (y - _srcRangeY.start) / _srcRangeY.step - val dstOffsetY = dstY * dstWidth - cfor(xStart)(_ < xEnd, _ + xStep) { x => - val dstX = (x - _srcRangeX.start) / _srcRangeX.step - dstPixels(dstX + dstOffsetY) = srcPixels(x + srcOffsetY) - } - } - dst - } } diff --git a/ijp-debayer2sx-core/src/main/scala/ij_plugins/debayer2sx/process/package.scala b/ijp-debayer2sx-core/src/main/scala/ij_plugins/debayer2sx/process/package.scala index 6d1c2ed..3b66203 100644 --- a/ijp-debayer2sx-core/src/main/scala/ij_plugins/debayer2sx/process/package.scala +++ b/ijp-debayer2sx-core/src/main/scala/ij_plugins/debayer2sx/process/package.scala @@ -22,11 +22,12 @@ package ij_plugins.debayer2sx -import ij.process.{FloatProcessor, ImageProcessor} +import ij.process.FloatProcessor import scala.language.implicitConversions package object process { + /** Marker object that represent full range for a given processor. Similar to ":" in MATLAB. */ val FR = Range(Int.MinValue, Int.MaxValue) @@ -34,64 +35,11 @@ package object process { implicit def wrapFloatProcessor(fp: FloatProcessor): FloatProcessorMath = new FloatProcessorMath(fp) - def add(a: Array[Float], b: Array[Float]): Array[Float] = { require(a.length == b.length) a.zip(b).map(e => e._1 + e._2) } - /** - * Copies elements between arrays within specified ranges - * - * MATLAB equivalent - * {{{ - * G0(:, 1:2:n) = bay(1:2:m, 1:2:n); - * }}} - * - * Scala code - * {{{ - * copyRanges( - * G0, Range(0, w, 2), FR, - * bay, Range(0, w, 2), Range(0, h, 2) - * ) - * }}} - * - * @param dstIP destination processor - * @param dstRangeX destination X range - * @param dstRangeY destination Y range - * @param srcIP source processor - * @param srcRangeX source X range - * @param srcRangeY source Y range - */ - final def copyRanges(dstIP: ImageProcessor, dstRangeX: Range, dstRangeY: Range, - srcIP: ImageProcessor, srcRangeX: Range, srcRangeY: Range): Unit = { - import io.github.metarank.cfor._ - - val _dstRangeX = if (dstRangeX == FR) Range(0, dstIP.getWidth) else dstRangeX - val _dstRangeY = if (dstRangeY == FR) Range(0, dstIP.getHeight) else dstRangeY - val _srcRangeX = if (srcRangeX == FR) Range(0, srcIP.getWidth) else srcRangeX - val _srcRangeY = if (srcRangeY == FR) Range(0, srcIP.getHeight) else srcRangeY - - val (dstXStart, dstXEnd, dstXStep) = sortedRangeParams(_dstRangeX) - val (dstYStart, dstYEnd, dstYStep) = sortedRangeParams(_dstRangeY) - - cfor(dstYStart)(_ < dstYEnd, _ + dstYStep) { y => - val indexY = (y - _dstRangeY.start) / _dstRangeY.step - val srcY = _srcRangeY.start + indexY * _srcRangeY.step - val srcYOffset = srcY * srcIP.getWidth - val dstYOffset = y * dstIP.getWidth - - cfor(dstXStart)(_ < dstXEnd, _ + dstXStep) { x => - val indexX = (x - _dstRangeX.start) / _dstRangeX.step - val srcX = _srcRangeX.start + indexX * _srcRangeX.step - - val v = srcIP.getf(srcX + srcYOffset) - dstIP.setf(x + dstYOffset, v) - } - } - } - - @inline final def duplicate(src: FloatProcessor): FloatProcessor = { val dst = new FloatProcessor(src.getWidth, src.getHeight) @@ -101,7 +49,6 @@ package object process { dst } - @inline final def sortedRangeParams(range: Range): (Int, Int, Int) = { if (range.step >= 0) @@ -110,5 +57,4 @@ package object process { (range.end + 1, range.start + 1, -range.step) } - } diff --git a/ijp-debayer2sx-plugins/src/main/scala/ij_plugins/debayer2sx/DeBayer2Plugin.scala b/ijp-debayer2sx-plugins/src/main/scala/ij_plugins/debayer2sx/DeBayer2Plugin.scala index bf67c5b..bf5daaf 100644 --- a/ijp-debayer2sx-plugins/src/main/scala/ij_plugins/debayer2sx/DeBayer2Plugin.scala +++ b/ijp-debayer2sx-plugins/src/main/scala/ij_plugins/debayer2sx/DeBayer2Plugin.scala @@ -34,7 +34,7 @@ object DeBayer2Plugin { class DeBayer2Plugin extends PlugIn { - import DeBayer2Plugin._ + import DeBayer2Plugin.* private val Title = "DeBayer2" private val Description = "Convert a bayer pattern image to a color image." @@ -45,8 +45,9 @@ class DeBayer2Plugin extends PlugIn { override def run(arg: String): Unit = { // We need an input image val imp = IJ.getImage - if (imp == null) + if (imp == null) { return + } // Check for supported types imp.getType match { @@ -79,11 +80,12 @@ class DeBayer2Plugin extends PlugIn { val cp = DeBayer2.stackToColorProcessor(stack, bpp) new ImagePlus(dstTitle, cp) case ImagePlus.GRAY16 => - val ss = if (stack.getBitDepth == 16) { - stack - } else { - DeBayer2.stackToShortStack(stack, bpp, bpp) - } + val ss = + if (stack.getBitDepth == 16) { + stack + } else { + DeBayer2.stackToShortStack(stack, bpp, bpp) + } val imp1 = new ImagePlus(dstTitle, ss) new CompositeImage(imp1, CompositeImage.COMPOSITE) case _ =>