diff --git a/ijp-color-ui/src/main/scala/ij_plugins/color/ui/calibration/ColorCalibratorUIModel.scala b/ijp-color-ui/src/main/scala/ij_plugins/color/ui/calibration/ColorCalibratorUIModel.scala index 7f0aa37..a277c90 100644 --- a/ijp-color-ui/src/main/scala/ij_plugins/color/ui/calibration/ColorCalibratorUIModel.scala +++ b/ijp-color-ui/src/main/scala/ij_plugins/color/ui/calibration/ColorCalibratorUIModel.scala @@ -207,7 +207,7 @@ class ColorCalibratorUIModel(val image: ImagePlus, parentWindow: Window) extends for { currentChart <- referenceChartOptionWrapper.value } yield { - newChart2.copyWithEnabled(currentChart.enabled.toArray) + newChart2.copyWithEnabled(currentChart.enabled) } ).getOrElse(newChart2) } diff --git a/ijp-color-ui/src/main/scala/ij_plugins/color/ui/calibration/tasks/CalibrateTask.scala b/ijp-color-ui/src/main/scala/ij_plugins/color/ui/calibration/tasks/CalibrateTask.scala index c12ae03..912ab70 100644 --- a/ijp-color-ui/src/main/scala/ij_plugins/color/ui/calibration/tasks/CalibrateTask.scala +++ b/ijp-color-ui/src/main/scala/ij_plugins/color/ui/calibration/tasks/CalibrateTask.scala @@ -1,6 +1,6 @@ /* * Image/J Plugins - * Copyright (C) 2002-2021 Jarek Sacha + * Copyright (C) 2002-2022 Jarek Sacha * Author's email: jpsacha at gmail dot com * * This library is free software; you can redistribute it and/or @@ -114,12 +114,11 @@ object CalibrateTask { } - private def showScatterChart( - x: Array[Array[Double]], - y: Array[Array[Double]], - seriesLabels: Array[String], - chartTitle: String - ): Unit = { + private def showScatterChart(x: IndexedSeq[IndexedSeq[Double]], + y: IndexedSeq[IndexedSeq[Double]], + seriesLabels: Array[String], + chartTitle: String + ): Unit = { require(x.length == y.length) require(seriesLabels.length == 3) @@ -431,13 +430,12 @@ class CalibrateTask( "\n") } - private def showColorErrorChart( - x: Array[Array[Double]], - y: Array[Array[Double]], - columnNames: Array[String], - seriesLabels: Array[String], - titlePrefix: String - ): Unit = { + private def showColorErrorChart(x: IndexedSeq[IndexedSeq[Double]], + y: IndexedSeq[IndexedSeq[Double]], + columnNames: Array[String], + seriesLabels: Array[String], + titlePrefix: String + ): Unit = { assert(x.length == y.length) assert(x.length == columnNames.length) @@ -454,18 +452,15 @@ class CalibrateTask( PlotUtils.createBarPlot(titlePrefix + " - Individual Chip Error", data, "Chip", "Error", barColors) } - private def showResidualScatterChart(x: Array[Array[Double]], y: Array[Array[Double]], chartTitle: String): Unit = { + private def showResidualScatterChart(x: IndexedSeq[IndexedSeq[Double]], y: IndexedSeq[IndexedSeq[Double]], chartTitle: String): Unit = { - val dy = new Array[Array[Double]](x.length) - for (i <- x.indices) { - val xi = x(i) - val yi = y(i) - val dd = new Array[Double](xi.length) - for (j <- xi.indices) { - dd(j) = math.abs(yi(j) - xi(j)) - } - dy(i) = dd - } + val dy = + x.zipWithIndex + .map { case (xi, i) => + val yi = y(i) + xi.zipWithIndex + .map { case (xij, j) => math.abs(yi(j) - xij) } + } showScatterChart(x, dy, referenceColorSpace().bandsNames, chartTitle) } diff --git a/ijp-color-ui/src/main/scala/ij_plugins/color/ui/calibration/tasks/ColorCorrectionBatchItem.scala b/ijp-color-ui/src/main/scala/ij_plugins/color/ui/calibration/tasks/ColorCorrectionBatchItem.scala index 1be3d9f..2df4353 100644 --- a/ijp-color-ui/src/main/scala/ij_plugins/color/ui/calibration/tasks/ColorCorrectionBatchItem.scala +++ b/ijp-color-ui/src/main/scala/ij_plugins/color/ui/calibration/tasks/ColorCorrectionBatchItem.scala @@ -1,6 +1,6 @@ /* * Image/J Plugins - * Copyright (C) 2002-2021 Jarek Sacha + * Copyright (C) 2002-2022 Jarek Sacha * Author's email: jpsacha at gmail dot com * * This library is free software; you can redistribute it and/or @@ -71,7 +71,9 @@ object ColorCorrectionBatchItem { val files = inputDir.listFiles().filter(_.getName.endsWith(inputExt)) - files.map(file => new ColorCorrectionBatchItem(recipe, file, dstDir = outputDir, config = itemConfig)) + files + .map(file => new ColorCorrectionBatchItem(recipe, file, dstDir = outputDir, config = itemConfig)) + .toSeq } def nameWithoutExtension(file: File): String = { diff --git a/ijp-color-ui/src/main/scala/ij_plugins/color/ui/calibration/tasks/SelectEnabledChipsTask.scala b/ijp-color-ui/src/main/scala/ij_plugins/color/ui/calibration/tasks/SelectEnabledChipsTask.scala index dcc3277..db3ad94 100644 --- a/ijp-color-ui/src/main/scala/ij_plugins/color/ui/calibration/tasks/SelectEnabledChipsTask.scala +++ b/ijp-color-ui/src/main/scala/ij_plugins/color/ui/calibration/tasks/SelectEnabledChipsTask.scala @@ -1,6 +1,6 @@ /* * Image/J Plugins - * Copyright (C) 2002-2021 Jarek Sacha + * Copyright (C) 2002-2022 Jarek Sacha * Author's email: jpsacha at gmail dot com * * This library is free software; you can redistribute it and/or @@ -159,8 +159,8 @@ class SelectEnabledChipsTask(chart: GridColorChart, parentWindow: Option[Window] dialog.showDialog() if (dialog.wasOKed) { - val enabled: Array[Boolean] = data.sortBy(_.index).map(_.enabled.value).toArray - val c = chart.copyWithEnabled(enabled) + val enabled = data.sortBy(_.index).map(_.enabled.value).toIndexedSeq + val c = chart.copyWithEnabled(enabled) Option(c) } else { None diff --git a/ijp-color-ui/src/main/scala/ij_plugins/color/ui/converter/ColorConverterView.scala b/ijp-color-ui/src/main/scala/ij_plugins/color/ui/converter/ColorConverterView.scala index bd9890c..0e0310a 100644 --- a/ijp-color-ui/src/main/scala/ij_plugins/color/ui/converter/ColorConverterView.scala +++ b/ijp-color-ui/src/main/scala/ij_plugins/color/ui/converter/ColorConverterView.scala @@ -1,6 +1,6 @@ /* * Image/J Plugins - * Copyright (C) 2002-2021 Jarek Sacha + * Copyright (C) 2002-2022 Jarek Sacha * Author's email: jpsacha at gmail dot com * * This library is free software; you can redistribute it and/or @@ -157,10 +157,10 @@ class ColorConverterView(val model: ColorConverterModel) { addColorUI(row, buttonRGB, rgbUI.control, Some("[0-255]")) row += 1 - addChoiceBox(row, "Ref. White", model.referenceWhite, ReferenceWhite.values) + addChoiceBox(row, "Ref. White", model.referenceWhite, ReferenceWhite.values.toSeq) row += 1 - addChoiceBox(row, "RGB Model", model.rgbWorkingSpace, RGBWorkingSpace.values) + addChoiceBox(row, "RGB Model", model.rgbWorkingSpace, RGBWorkingSpace.values.toSeq) gp.add( new Label { id = "ijp-label" diff --git a/ijp-color/src/main/scala-2/ij_plugins/color/calibration/chart/ReferenceColorSpace.scala b/ijp-color/src/main/scala-2/ij_plugins/color/calibration/chart/ReferenceColorSpace.scala index 5a59e5b..6bd4949 100644 --- a/ijp-color/src/main/scala-2/ij_plugins/color/calibration/chart/ReferenceColorSpace.scala +++ b/ijp-color/src/main/scala-2/ij_plugins/color/calibration/chart/ReferenceColorSpace.scala @@ -1,6 +1,6 @@ /* * Image/J Plugins - * Copyright (C) 2002-2021 Jarek Sacha + * Copyright (C) 2002-2022 Jarek Sacha * Author's email: jpsacha at gmail dot com * * This library is free software; you can redistribute it and/or @@ -58,25 +58,27 @@ sealed abstract class ReferenceColorSpace(override val entryName: String, bands: } /** - * Convert color value from the current color space to CIE L*a*b* - * - * @param refWhite - * reference white of the reference color chart. - */ + * Convert color value from the current color space to CIE L*a*b* + * + * @param refWhite + * reference white of the reference color chart. + */ def toLab(c: Array[Double], refWhite: ReferenceWhite): ColorTriple.Lab = { require(c.length == 3) toLab(c(0), c(1), c(2), refWhite) } + def toLab(c: IndexedSeq[Double], refWhite: ReferenceWhite): ColorTriple.Lab = toLab(c.toArray, refWhite) + /** - * Convert color value from the current color space to CIE L*a*b* - * - * @param refWhite - * reference white of the reference color chart. - */ + * Convert color value from the current color space to CIE L*a*b* + * + * @param refWhite + * reference white of the reference color chart. + */ def toLab(fps: Array[FloatProcessor], refWhite: ReferenceWhite): Array[FloatProcessor] = { require(fps.length == 3) - val w = fps(0).getWidth + val w = fps(0).getWidth val h = fps(0).getHeight val n = w * h val lFP = new FloatProcessor(w, h) diff --git a/ijp-color/src/main/scala-3/ij_plugins/color/calibration/chart/ReferenceColorSpace.scala b/ijp-color/src/main/scala-3/ij_plugins/color/calibration/chart/ReferenceColorSpace.scala index 7b3f65b..730c978 100644 --- a/ijp-color/src/main/scala-3/ij_plugins/color/calibration/chart/ReferenceColorSpace.scala +++ b/ijp-color/src/main/scala-3/ij_plugins/color/calibration/chart/ReferenceColorSpace.scala @@ -62,27 +62,30 @@ enum ReferenceColorSpace(val name: String, bands: Array[String]) extends WithNam } /** - * Convert color value from the current color space to CIE L*a*b* - * - * @param refWhite - * reference white of the reference color chart. - */ - def toLab(c: Array[Double], refWhite: ReferenceWhite): ColorTriple.Lab = { - require(c.length == 3) - toLab(c(0), c(1), c(2), refWhite) + * Convert color value from the current color space to CIE L*a*b* + * + * @param refWhite + * reference white of the reference color chart. + */ + def toLab (c: Array[Double], refWhite: ReferenceWhite): ColorTriple.Lab = { + require (c.length == 3) + toLab (c (0), c (1), c (2), refWhite) } + def toLab (c: IndexedSeq[Double], refWhite: ReferenceWhite): ColorTriple.Lab = toLab (c.toArray, refWhite) + + /** - * Convert color value from the current color space to CIE L*a*b* - * - * @param refWhite - * reference white of the reference color chart. - */ - def toLab(fps: Array[FloatProcessor], refWhite: ReferenceWhite): Array[FloatProcessor] = { - require(fps.length == 3) - val w = fps(0).getWidth - val h = fps(0).getHeight - val n = w * h + * Convert color value from the current color space to CIE L*a*b* + * + * @param refWhite + * reference white of the reference color chart. + */ + def toLab (fps: Array[FloatProcessor], refWhite: ReferenceWhite): Array[FloatProcessor] = { + require (fps.length == 3) + val w = fps (0).getWidth + val h = fps (0).getHeight + val n = w * h val lFP = new FloatProcessor(w, h) val aFP = new FloatProcessor(w, h) val bFP = new FloatProcessor(w, h) diff --git a/ijp-color/src/main/scala/ij_plugins/color/calibration/ColorCalibrator.scala b/ijp-color/src/main/scala/ij_plugins/color/calibration/ColorCalibrator.scala index 04bac36..f1a70a8 100644 --- a/ijp-color/src/main/scala/ij_plugins/color/calibration/ColorCalibrator.scala +++ b/ijp-color/src/main/scala/ij_plugins/color/calibration/ColorCalibrator.scala @@ -1,6 +1,6 @@ /* * Image/J Plugins - * Copyright (C) 2002-2021 Jarek Sacha + * Copyright (C) 2002-2022 Jarek Sacha * Author's email: jpsacha at gmail dot com * * This library is free software; you can redistribute it and/or @@ -29,29 +29,29 @@ import ij_plugins.color.calibration.chart.{ColorChart, ReferenceColorSpace} import ij_plugins.color.calibration.regression.{CubicPolynomialTriple, MappingFactory, MappingMethod} import ij_plugins.color.util.Utils.{clipUInt8D, delta} +import scala.reflect.ClassTag + /** Color calibration helper methods */ object ColorCalibrator { /** - * Results of computing color calibration. - * - * @param reference - * reference color values - * @param observed - * observed color values - * @param corrected - * color values after calibration - * @param corrector - * coefficients of the polynomial mapping functions. - */ - case class CalibrationFit( - reference: Array[Array[Double]], - observed: Array[Array[Double]], - corrected: Array[Array[Double]], - corrector: CubicPolynomialTriple - ) { + * Results of computing color calibration. + * + * @param reference + * reference color values + * @param observed + * observed color values + * @param corrected + * color values after calibration + * @param corrector + * coefficients of the polynomial mapping functions. + */ + case class CalibrationFit(reference: IndexedSeq[IndexedSeq[Double]], + observed: IndexedSeq[IndexedSeq[Double]], + corrected: IndexedSeq[IndexedSeq[Double]], + corrector: CubicPolynomialTriple) { // Validate inputs - require(reference.length > 0) + require(reference.nonEmpty) require(reference.forall(_.length == 3)) require(observed.length == reference.length) require(observed.forall(_.length == 3)) @@ -59,8 +59,8 @@ object ColorCalibrator { require(corrected.forall(_.length == 3)) /** - */ - def correctedDeltas: Array[Double] = reference zip corrected map (p => delta(p._1, p._2)) + */ + def correctedDeltas: IndexedSeq[Double] = reference zip corrected map (p => delta(p._1, p._2)) } /** Create instance of ColorCalibrator */ @@ -115,50 +115,52 @@ class ColorCalibrator( import ColorCalibrator.* /** - * Compute coefficients of a polynomial color mapping between the reference and observed colors. - * - * @param observed - * Actually observed color values. - * @return - * color mapping coefficients. - * @throws java.lang.IllegalArgumentException - * when the reference and observed values are not sufficient to compute mapping polynomial coefficients, for - * instance, if the desired polynomial order is too high given the number of reference colors. - */ - def computeCalibrationMapping(observed: Array[Array[Double]]): CalibrationFit = { + * Compute coefficients of a polynomial color mapping between the reference and observed colors. + * + * @param observed + * Actually observed color values. + * @return + * color mapping coefficients. + * @throws java.lang.IllegalArgumentException + * when the reference and observed values are not sufficient to compute mapping polynomial coefficients, for + * instance, if the desired polynomial order is too high given the number of reference colors. + */ + def computeCalibrationMapping(observed: IndexedSeq[IndexedSeq[Double]]): CalibrationFit = { require( observed.length == chart.referenceChipsEnabled.length, s"Expecting ${chart.referenceChipsEnabled.length} observations, got ${observed.length}." ) require(observed.forall(_.length == 3)) - val reference = chart.referenceColorEnabled(referenceColorSpace).clone() - if (clipReferenceRGB && ReferenceColorSpace.sRGB == referenceColorSpace) { - reference.foreach(r => { + + val referenceClipped = { + val reference1 = chart.referenceColorEnabled(referenceColorSpace) + + if (clipReferenceRGB && ReferenceColorSpace.sRGB == referenceColorSpace) { // Values should be clipped, but decimal precision should net be truncated - r(0) = clipUInt8D(r(0)) - r(1) = clipUInt8D(r(1)) - r(2) = clipUInt8D(r(2)) - }) + reference1.map(r => r.map(clipUInt8D)) + } else { + reference1 + } } - require(reference.length == observed.length) + require(referenceClipped.length == observed.length) - val corrector = MappingFactory.createCubicPolynomialTriple(reference, observed, mappingMethod) + val corrector = MappingFactory.createCubicPolynomialTriple(referenceClipped, observed, mappingMethod) val corrected = observed.map(corrector.map) - CalibrationFit(reference = reference, observed = observed, corrected = corrected, corrector) + CalibrationFit(reference = referenceClipped, observed = observed, corrected = corrected, corrector) } /** - * Estimate calibration coefficient. This method does not clip reference color values. - * - * @param bands - * input image bands to measure observed color value of chart's chips. - * @throws java.lang.IllegalArgumentException - * if one of the calibration mapping functions cannot be computed. - */ - def computeCalibrationMapping[T <: ImageProcessor](bands: Array[T]): CalibrationFit = { + * Estimate calibration coefficient. This method does not clip reference color values. + * + * @param bands + * input image bands to measure observed color value of chart's chips. + * @throws java.lang.IllegalArgumentException + * if one of the calibration mapping functions cannot be computed. + */ + def computeCalibrationMapping[T <: ImageProcessor : ClassTag](bands: IndexedSeq[T]): CalibrationFit = { require( (bands.forall(_.isInstanceOf[ByteProcessor]) | bands.forall(_.isInstanceOf[ShortProcessor]) | @@ -200,7 +202,7 @@ class ColorCalibrator( val src = image.getProcessor.asInstanceOf[ColorProcessor] computeCalibrationMapping(src) case (GRAY8, 3) | (GRAY16, 3) | (GRAY32, 3) => - val src = (1 to 3).map(image.getStack.getProcessor).toArray + val src = (1 to 3).map(image.getStack.getProcessor) computeCalibrationMapping(src) case _ => throw new IllegalArgumentException( diff --git a/ijp-color/src/main/scala/ij_plugins/color/calibration/Corrector.scala b/ijp-color/src/main/scala/ij_plugins/color/calibration/Corrector.scala index fb7593c..5f5d0b1 100644 --- a/ijp-color/src/main/scala/ij_plugins/color/calibration/Corrector.scala +++ b/ijp-color/src/main/scala/ij_plugins/color/calibration/Corrector.scala @@ -1,6 +1,6 @@ /* * Image/J Plugins - * Copyright (C) 2002-2021 Jarek Sacha + * Copyright (C) 2002-2022 Jarek Sacha * Author's email: jpsacha at gmail dot com * * This library is free software; you can redistribute it and/or @@ -42,13 +42,13 @@ trait Corrector { protected def evaluate(src: Array[Double], dest: Array[Double]): Unit /** - * Map color triplet. - * - * @param src - * input color values. The array must be of size 3. - * @return - * new color triplet. - */ + * Map color triplet. + * + * @param src + * input color values. The array must be of size 3. + * @return + * new color triplet. + */ def map(src: Array[Double]): Array[Double] = { require(src.length == 3, "Color space mapping can only be done when input image has 3 color bands") @@ -57,25 +57,27 @@ trait Corrector { dest } + def map(src: IndexedSeq[Double]): IndexedSeq[Double] = map(src.toArray).toIndexedSeq + def map(a: Array[Array[Double]]): Array[Array[Double]] = { a.map(map) } /** - * Color calibrate input image `src`, convert result to sRGB color space. - * - * Calibration mapping must be set before calling this method. It is critical to only use this method on the same type - * of an image as it was used for computing the calibration mapping. - * - * @param src - * image to be calibrated. - * @return - * converted image in sRGB color space. - * @throws java.lang.IllegalArgumentException - * if the mapping was not set. - * @see - * #map[T <: ImageProcessor](src: Array[T]) - */ + * Color calibrate input image `src`, convert result to sRGB color space. + * + * Calibration mapping must be set before calling this method. It is critical to only use this method on the same type + * of an image as it was used for computing the calibration mapping. + * + * @param src + * image to be calibrated. + * @return + * converted image in sRGB color space. + * @throws java.lang.IllegalArgumentException + * if the mapping was not set. + * @see + * #map[T <: ImageProcessor](src: Array[T]) + */ def mapToFloat(src: ColorProcessor): Array[FloatProcessor] = { map(ImageJUtils.splitRGB(src)) } diff --git a/ijp-color/src/main/scala/ij_plugins/color/calibration/LOOCrossValidation.scala b/ijp-color/src/main/scala/ij_plugins/color/calibration/LOOCrossValidation.scala index f34f34c..6355729 100644 --- a/ijp-color/src/main/scala/ij_plugins/color/calibration/LOOCrossValidation.scala +++ b/ijp-color/src/main/scala/ij_plugins/color/calibration/LOOCrossValidation.scala @@ -1,6 +1,6 @@ /* * Image/J Plugins - * Copyright (C) 2002-2021 Jarek Sacha + * Copyright (C) 2002-2022 Jarek Sacha * Author's email: jpsacha at gmail dot com * * This library is free software; you can redistribute it and/or @@ -63,7 +63,7 @@ object LOOCrossValidation { mappingMethod: MappingMethod, image: ImagePlus ): IndexedSeq[Deltas] = { - val observed: Array[Array[Double]] = chart.averageChipColor(image) + val observed: IndexedSeq[IndexedSeq[Double]] = chart.averageChipColor(image) crossValidation(chart, referenceColorSpace, mappingMethod, observed) } @@ -80,19 +80,19 @@ object LOOCrossValidation { * deltaA, deltaB) */ def crossValidation( - chart: ColorChart, - referenceColorSpace: ReferenceColorSpace, - mappingMethod: MappingMethod, - observedSamples: Array[Array[Double]] - ): IndexedSeq[Deltas] = { + chart: ColorChart, + referenceColorSpace: ReferenceColorSpace, + mappingMethod: MappingMethod, + observedSamples: IndexedSeq[IndexedSeq[Double]] + ): IndexedSeq[Deltas] = { require(chart.referenceChipsEnabled.length == observedSamples.length) val n = chart.referenceChips.size // A hack to match indices in a full chart with indices in observations - inset null observations - val observedSamples2: Array[Array[Double]] = { - val a2 = new Array[Array[Double]](n) + val observedSamples2: IndexedSeq[IndexedSeq[Double]] = { + val a2 = new Array[IndexedSeq[Double]](n) var ii = 0 for (i <- 0 until n) { if (chart.enabled(i)) { @@ -103,7 +103,7 @@ object LOOCrossValidation { a2(i) = null } } - a2 + a2.toIndexedSeq } val excludeInvalidRGBRef = false @@ -119,8 +119,7 @@ object LOOCrossValidation { for (i <- 0 until n if enabledChips(i)) yield { // Disable i-th chip when computing calibration coefficients - val enabled = enabledChips.clone() - enabled(i) = false + val enabled = enabledChips.updated(i, false) val leaveOneOutChart = chart.copyWithEnabled(enabled) // Compute color mapping coefficients @@ -159,17 +158,17 @@ object LOOCrossValidation { image: ImagePlus ): CrossValidationData = { - val observedSamples: Array[Array[Double]] = chart.averageChipColor(image) + val observedSamples: IndexedSeq[IndexedSeq[Double]] = chart.averageChipColor(image) crossValidationStats(chart, referenceColorSpace, mappingMethod, observedSamples) } def crossValidationStats( - chart: ColorChart, - referenceColorSpace: ReferenceColorSpace, - mappingMethod: MappingMethod, - observedSamples: Array[Array[Double]] - ): CrossValidationData = { + chart: ColorChart, + referenceColorSpace: ReferenceColorSpace, + mappingMethod: MappingMethod, + observedSamples: IndexedSeq[IndexedSeq[Double]] + ): CrossValidationData = { val _statsDeltaE = new DescriptiveStatistics() val _statsDeltaL = new DescriptiveStatistics() val _statsDeltaA = new DescriptiveStatistics() @@ -198,20 +197,21 @@ object LOOCrossValidation { mappingMethods: Seq[MappingMethod] ): Seq[CrossValidationData] = { - val observedSamples: Array[Array[Double]] = chart.averageChipColorEnabled(image) + val observedSamples: IndexedSeq[IndexedSeq[Double]] = chart.averageChipColorEnabled(image) crossValidationStatsAll(chart, observedSamples, referenceColorSpaces, mappingMethods) } def crossValidationStatsAll( - chart: ColorChart, - observedSamples: Array[Array[Double]], - referenceColorSpaces: Seq[ReferenceColorSpace], - mappingMethods: Seq[MappingMethod] - ): Seq[CrossValidationData] = { + chart: ColorChart, + observedSamples: IndexedSeq[IndexedSeq[Double]], + referenceColorSpaces: Seq[ReferenceColorSpace], + mappingMethods: Seq[MappingMethod] + ): Seq[CrossValidationData] = { + val refSpaceMethods = for ( - rcs <- referenceColorSpaces; + rcs <- referenceColorSpaces; method <- mappingMethods ) yield (rcs, method) diff --git a/ijp-color/src/main/scala/ij_plugins/color/calibration/chart/ColorChart.scala b/ijp-color/src/main/scala/ij_plugins/color/calibration/chart/ColorChart.scala index b4c8cc7..dd7238b 100644 --- a/ijp-color/src/main/scala/ij_plugins/color/calibration/chart/ColorChart.scala +++ b/ijp-color/src/main/scala/ij_plugins/color/calibration/chart/ColorChart.scala @@ -57,44 +57,44 @@ trait ColorChart { def alignmentTransform: PerspectiveTransform /** - * Compute average color within aligned chips from an RGB image. - * - * @param cp - * RGB image - */ - def averageChipColor(cp: ColorProcessor): Array[Array[Double]] = - averageChipColor(ImageJUtils.splitRGB(cp)) + * Compute average color within aligned chips from an RGB image. + * + * @param cp + * RGB image + */ + def averageChipColor(cp: ColorProcessor): IndexedSeq[IndexedSeq[Double]] = + averageChipColor(ImageJUtils.splitRGB(cp).toIndexedSeq) - def averageChipColorEnabled(cp: ColorProcessor): Array[Array[Double]] = filterEnabled(averageChipColor(cp)) + def averageChipColorEnabled(cp: ColorProcessor): IndexedSeq[IndexedSeq[Double]] = filterEnabled(averageChipColor(cp)) /** - * Compute average color within aligned chips from an an image. - * - * Input images is represented by 3 bands. There is no assumption made about image color space. - * - * @param src - * image represented by 3 bands - */ - def averageChipColor[T <: ImageProcessor](src: Array[T]): Array[Array[Double]] - - def averageChipColorEnabled[T <: ImageProcessor](src: Array[T]): Array[Array[Double]] = + * Compute average color within aligned chips from an an image. + * + * Input images is represented by 3 bands. There is no assumption made about image color space. + * + * @param src + * image represented by 3 bands + */ + def averageChipColor[T <: ImageProcessor](src: IndexedSeq[T]): IndexedSeq[IndexedSeq[Double]] + + def averageChipColorEnabled[T <: ImageProcessor](src: IndexedSeq[T]): IndexedSeq[IndexedSeq[Double]] = filterEnabled(averageChipColor(src)) /** - * Compute average color within aligned chips from an an image. - * - * @param image - * is either single slice RGB image (ColorProcessor) or three slices of gray processors representing color bands. - * @return - * average for each band in each color chip (array of triples). - */ - def averageChipColor(image: ImagePlus): Array[Array[Double]] = { + * Compute average color within aligned chips from an an image. + * + * @param image + * is either single slice RGB image (ColorProcessor) or three slices of gray processors representing color bands. + * @return + * average for each band in each color chip (array of triples). + */ + def averageChipColor(image: ImagePlus): IndexedSeq[IndexedSeq[Double]] = { (image.getType, image.getStackSize) match { case (COLOR_RGB, 1) => val src = image.getProcessor.asInstanceOf[ColorProcessor] averageChipColor(src) case (GRAY8, 3) | (GRAY16, 3) | (GRAY32, 3) => - val src = (1 to 3).map(image.getStack.getProcessor).toArray + val src = (1 to 3).map(image.getStack.getProcessor) averageChipColor(src) case _ => throw new IllegalArgumentException( @@ -103,56 +103,56 @@ trait ColorChart { } } - def averageChipColorEnabled(image: ImagePlus): Array[Array[Double]] = filterEnabled(averageChipColor(image)) + def averageChipColorEnabled(image: ImagePlus): IndexedSeq[IndexedSeq[Double]] = filterEnabled(averageChipColor(image)) /** Return reference colors represented in given color space. */ - def referenceColor(colorSpace: ReferenceColorSpace): Array[Array[Double]] = colorSpace match { - case ReferenceColorSpace.XYZ => referenceColorXYZ + def referenceColor(colorSpace: ReferenceColorSpace): IndexedSeq[IndexedSeq[Double]] = colorSpace match { + case ReferenceColorSpace.XYZ => referenceColorXYZ case ReferenceColorSpace.sRGB => referenceColorSRGB } /** Return reference colors represented in given color space. Only enabled chips are returned. */ - def referenceColorEnabled(colorSpace: ReferenceColorSpace): Array[Array[Double]] = + def referenceColorEnabled(colorSpace: ReferenceColorSpace): IndexedSeq[IndexedSeq[Double]] = filterEnabled(referenceColor(colorSpace)) /** Return reference color expressed in CIE XYZ color space assuming default reference white for this chart. */ - def referenceColorXYZ: Array[Array[Double]] + def referenceColorXYZ: IndexedSeq[IndexedSeq[Double]] /** Return reference color expressed in sRGB color space. */ - def referenceColorSRGB: Array[Array[Double]] = { - referenceColorXYZ.map(xyz => colorConverter.xyzToRGB(xyz(0), xyz(1), xyz(2)).toArray) + def referenceColorSRGB: IndexedSeq[IndexedSeq[Double]] = { + referenceColorXYZ.map(xyz => colorConverter.xyzToRGB(xyz(0), xyz(1), xyz(2)).toIndexedSeq) } /** - * Creates a copy of this chart in which some chips cn be enabled/disabled. - * - * @param enabled - * array with indexes corresponding to ones returned by `referenceColor` methods. If value is `true` chip with - * corresponding index is enabled, if `false` it is disabled. - * @return - */ - def copyWithEnabled(enabled: Array[Boolean]): ColorChart + * Creates a copy of this chart in which some chips cn be enabled/disabled. + * + * @param enabled + * array with indexes corresponding to ones returned by `referenceColor` methods. If value is `true` chip with + * corresponding index is enabled, if `false` it is disabled. + * @return + */ + def copyWithEnabled(enabled: IndexedSeq[Boolean]): ColorChart /** - * Create a copy with all chips enabled - */ - def copyWithEnabledAll: ColorChart = copyWithEnabled(Array.fill(enabled.length)(true)) + * Create a copy with all chips enabled + */ + def copyWithEnabledAll: ColorChart = copyWithEnabled(IndexedSeq.fill(enabled.length)(true)) /** - * Which chips should be used in computations. If value is 'true' chip is active' if 'false' not used in computations. - */ - def enabled: Seq[Boolean] + * Which chips should be used in computations. If value is 'true' chip is active' if 'false' not used in computations. + */ + def enabled: IndexedSeq[Boolean] /** Reference chips without transform applied to their coordinates. */ - def referenceChips: immutable.Seq[ColorChip] + def referenceChips: immutable.IndexedSeq[ColorChip] /** Reference chips without transform applied to their coordinates. Only enabled chips are returned. */ - def referenceChipsEnabled: immutable.Seq[ColorChip] = filterEnabled(referenceChips) + def referenceChipsEnabled: immutable.IndexedSeq[ColorChip] = filterEnabled(referenceChips) /** Color chips with alignment transform applied to their outline. */ def alignedChips: immutable.IndexedSeq[ColorChip] - private def filterEnabled[T](s: immutable.Seq[T]): immutable.Seq[T] = { + private def filterEnabled[T](s: immutable.IndexedSeq[T]): immutable.IndexedSeq[T] = { require(s.length == enabled.length) s.zip(enabled).filter(_._2).map(_._1) } diff --git a/ijp-color/src/main/scala/ij_plugins/color/calibration/chart/ColorCharts.scala b/ijp-color/src/main/scala/ij_plugins/color/calibration/chart/ColorCharts.scala index 14cf922..8bd9649 100644 --- a/ijp-color/src/main/scala/ij_plugins/color/calibration/chart/ColorCharts.scala +++ b/ijp-color/src/main/scala/ij_plugins/color/calibration/chart/ColorCharts.scala @@ -1,6 +1,6 @@ /* * Image/J Plugins - * Copyright (C) 2002-2021 Jarek Sacha + * Copyright (C) 2002-2022 Jarek Sacha * Author's email: jpsacha at gmail dot com * * This library is free software; you can redistribute it and/or @@ -41,7 +41,7 @@ object ColorCharts { ColorChartType.GretagMacbethColorChecker.name, 6, 4, - List( + IndexedSeq( ("Dark skin", Lab(40.59, 14.68, 16.83)), ("Light skin", Lab(68.22, 22.21, 20.33)), ("Blue sky", Lab(49.31, -9.20, -23.05)), @@ -81,7 +81,7 @@ object ColorCharts { ColorChartType.XRitePassportColorChecker.name, 6, 4, - List( + IndexedSeq( ("Dark Skin", Lab(38.96, 12.13, 13.84)), ("Light Skin", Lab(65.50, 15.59, 16.81)), ("Blue Sky", Lab(50.69, -2.09, -21.75)), @@ -120,7 +120,7 @@ object ColorCharts { ColorChartType.ImageScienceColorGaugeMatte.name, 6, 5, - List( + IndexedSeq( ("1", Lab(38.76, 13.81, 14.69)), ("2", Lab(65.15, 19.21, 17.92)), ("3", Lab(49.61, -4.20, -21.33)), @@ -157,7 +157,7 @@ object ColorCharts { ) /** All pre-defined color charts */ - val values = List(GretagMacbethColorChecker, XRitePassportColorChecker, ImageScienceColorGaugeMatte) + val values: Seq[GridColorChart] = Seq(GretagMacbethColorChecker, XRitePassportColorChecker, ImageScienceColorGaugeMatte) def withColorChartType(colorChartType: ColorChartType): Option[GridColorChart] = { require(colorChartType != null, "'colorChartType' cannot be null.") @@ -165,25 +165,25 @@ object ColorCharts { } /** - * Load chart reference values from a CSV file represented in CIE L*a*b* color space. - * - * The file is expected to have at least 4 columns: "SAMPLE_NAME", "LAB_L", "LAB_A", "LAB_B". Any additional columns - * will be ignored. - * - * Example of a file with 4 chips: - * - * {{{ - * SAMPLE_NAME,LAB_L,LAB_A,LAB_B - * 1,38.675,12.907,14.358,19.306 - * 2,65.750,19.811,17.790,26.626 - * 3,50.373,-3.646,-22.360,22.656 - * 4,43.697,-13.342,22.858,26.466 - * }}} - * - * @return - * list of tuples representing chip name and reference value in CIE L*a*b* - */ - def loadReferenceValues(file: File): List[(String, ColorTriple.Lab)] = { + * Load chart reference values from a CSV file represented in CIE L*a*b* color space. + * + * The file is expected to have at least 4 columns: "SAMPLE_NAME", "LAB_L", "LAB_A", "LAB_B". Any additional columns + * will be ignored. + * + * Example of a file with 4 chips: + * + * {{{ + * SAMPLE_NAME,LAB_L,LAB_A,LAB_B + * 1,38.675,12.907,14.358,19.306 + * 2,65.750,19.811,17.790,26.626 + * 3,50.373,-3.646,-22.360,22.656 + * 4,43.697,-13.342,22.858,26.466 + * }}} + * + * @return + * list of tuples representing chip name and reference value in CIE L*a*b* + */ + def loadReferenceValues(file: File): IndexedSeq[(String, ColorTriple.Lab)] = { require(file.exists(), "File must exist: " + file.getCanonicalPath) val rt = ResultsTable.open(file.getCanonicalPath) @@ -193,22 +193,21 @@ object ColorCharts { } val headingName = "SAMPLE_NAME" - val headingL = "LAB_L" - val headingA = "LAB_A" - val headingB = "LAB_B" + val headingL = "LAB_L" + val headingA = "LAB_A" + val headingB = "LAB_B" Seq(headingName, headingL, headingA, headingB).foreach(checkForColumn) val chips = (0 until rt.size()) .map { r => val name = rt.getStringValue(headingName, r) - val l = rt.getValue(headingL, r) - val a = rt.getValue(headingA, r) - val b = rt.getValue(headingB, r) + val l = rt.getValue(headingL, r) + val a = rt.getValue(headingA, r) + val b = rt.getValue(headingB, r) (name, ColorTriple.Lab(l, a, b)) } - .toList chips } diff --git a/ijp-color/src/main/scala/ij_plugins/color/calibration/chart/GridColorChart.scala b/ijp-color/src/main/scala/ij_plugins/color/calibration/chart/GridColorChart.scala index 2e45b05..b33c4aa 100644 --- a/ijp-color/src/main/scala/ij_plugins/color/calibration/chart/GridColorChart.scala +++ b/ijp-color/src/main/scala/ij_plugins/color/calibration/chart/GridColorChart.scala @@ -1,6 +1,6 @@ /* * Image/J Plugins - * Copyright (C) 2002-2021 Jarek Sacha + * Copyright (C) 2002-2022 Jarek Sacha * Author's email: jpsacha at gmail dot com * * This library is free software; you can redistribute it and/or @@ -32,33 +32,32 @@ import java.awt.geom.Point2D import scala.collection.immutable /** - * Chart consisting of a regular grid of square chips, arranged in rows and columns. - * - * @param name - * chart's name - * @param nbColumns - * number of columns - * @param nbRows - * number of rows - * @param chips - * chip names and CIE L*a*b* / D65 color values, row by row, starting at (0,0) or top left corner. - * @param chipMargin - * reduction of chip from their maximum size on the grid (as fraction of its width or height). Value of the margin - * must be between 0 and 0.5. - * @param enabled - * which chips are active. If value is 'true' chip is active' if 'false' not used in computations. - */ -final class GridColorChart( - val name: String, - nbColumns: Int, - nbRows: Int, - val chips: Seq[(String, Lab)], - chipMargin: Double, - override val enabled: Seq[Boolean], - override val refWhite: ReferenceWhite, - alignmentTransform: PerspectiveTransform -) extends GridChartFrame(nbColumns, nbRows, chipMargin, alignmentTransform) - with ColorChart { + * Chart consisting of a regular grid of square chips, arranged in rows and columns. + * + * @param name + * chart's name + * @param nbColumns + * number of columns + * @param nbRows + * number of rows + * @param chips + * chip names and CIE L*a*b* / D65 color values, row by row, starting at (0,0) or top left corner. + * @param chipMargin + * reduction of chip from their maximum size on the grid (as fraction of its width or height). Value of the margin + * must be between 0 and 0.5. + * @param enabled + * which chips are active. If value is 'true' chip is active' if 'false' not used in computations. + */ +final class GridColorChart(val name: String, + nbColumns: Int, + nbRows: Int, + val chips: IndexedSeq[(String, Lab)], + chipMargin: Double, + override val enabled: IndexedSeq[Boolean], + override val refWhite: ReferenceWhite, + alignmentTransform: PerspectiveTransform + ) extends GridChartFrame(nbColumns, nbRows, chipMargin, alignmentTransform) + with ColorChart { require(chipMargin >= 0 && chipMargin < 0.5, s"Margin value must at least 0 but less than 0.5, got $chipMargin.") require(nbColumns > 0, s"Number of columns must be greater than 0, got $nbColumns.") @@ -76,27 +75,27 @@ final class GridColorChart( require(alignmentTransform != null, "'alignmentTransform', cannot be null.") /** - * Construct chart with all chips enabled. - * - * @param name - * chart's name - * @param nbColumns - * number of columns - * @param nbRows - * number of rows - * @param chips - * chip names and CIE L*a*b* / D65 color values, row by row, starting at (0,0) or top left corner. - */ + * Construct chart with all chips enabled. + * + * @param name + * chart's name + * @param nbColumns + * number of columns + * @param nbRows + * number of rows + * @param chips + * chip names and CIE L*a*b* / D65 color values, row by row, starting at (0,0) or top left corner. + */ def this( - name: String, - nbColumns: Int, - nbRows: Int, - chips: Seq[(String, Lab)], - chipMargin: Double = 0, - refWhite: ReferenceWhite, - alignmentTransform: PerspectiveTransform = new PerspectiveTransform() - ) = { - this(name, nbColumns, nbRows, chips, chipMargin, List.fill(nbColumns * nbRows)(true), refWhite, alignmentTransform) + name: String, + nbColumns: Int, + nbRows: Int, + chips: IndexedSeq[(String, Lab)], + chipMargin: Double = 0, + refWhite: ReferenceWhite, + alignmentTransform: PerspectiveTransform = new PerspectiveTransform() + ) = { + this(name, nbColumns, nbRows, chips, chipMargin, IndexedSeq.fill(nbColumns * nbRows)(true), refWhite, alignmentTransform) } private val n = nbColumns * nbRows @@ -107,27 +106,27 @@ final class GridColorChart( val referenceGrid: ChartGrid = new ChartGrid(nbColumns, nbRows) /** - * Reference chips with they size reduced by margin (as fraction of its width or height). - * - * Value of the margin must be between 0 and 0.5. - */ + * Reference chips with they size reduced by margin (as fraction of its width or height). + * + * Value of the margin must be between 0 and 0.5. + */ def referenceChips: immutable.IndexedSeq[ColorChip] = { require(chips.length == referenceChipOutlines.length) - for (case (chip, outline) <- chips.toIndexedSeq.zip(referenceChipOutlines)) yield { + for (case (chip, outline) <- chips.zip(referenceChipOutlines)) yield { new ColorChip(chip._1, chip._2, outline) } } ensuring (_.length == chips.size) - override def referenceColorXYZ: Array[Array[Double]] = { - chips.map { v => colorConverter.toXYZ(v._2).toArray }.toArray + override def referenceColorXYZ: IndexedSeq[IndexedSeq[Double]] = { + chips.map { v => colorConverter.toXYZ(v._2).toIndexedSeq } } /** - * Return the aligned outline of a chip in given `column` and `row`. - * - * Chip linear size is reduced by a margin expressed as a fraction of width/length. - */ + * Return the aligned outline of a chip in given `column` and `row`. + * + * Chip linear size is reduced by a margin expressed as a fraction of width/length. + */ @deprecated("Unused method, will be removed", since = "0.8") def outlineChipAt(column: Int, row: Int, margin: Double): Array[Point2D] = { val ref = referenceGrid.chipAt(column, row, margin) @@ -143,10 +142,10 @@ final class GridColorChart( override def toString: String = name - override def averageChipColor[T <: ImageProcessor](src: Array[T]): Array[Array[Double]] = { + override def averageChipColor[T <: ImageProcessor](src: IndexedSeq[T]): IndexedSeq[IndexedSeq[Double]] = { val chips = alignedChips - val r = for (chip <- chips) yield ImageJUtils.measureColor(src, chip.outline.toArray) - r.toArray + val r = for (chip <- chips) yield ImageJUtils.measureColor(src, chip.outline.toIndexedSeq) + r } /** @@ -161,23 +160,23 @@ final class GridColorChart( } /** - * Creates a copy of this chart in which some chips cn be enabled/disabled. - * - * @param enabled - * array with indexes corresponding to ones returned by `referenceColor` methods. If value is `true` chip with - * corresponding index is enabled, if `false` it is disabled. - * @return - */ - override def copyWithEnabled(enabled: Array[Boolean]): GridColorChart = { + * Creates a copy of this chart in which some chips cn be enabled/disabled. + * + * @param enabled + * array with indexes corresponding to ones returned by `referenceColor` methods. If value is `true` chip with + * corresponding index is enabled, if `false` it is disabled. + * @return + */ + override def copyWithEnabled(enabled: IndexedSeq[Boolean]): GridColorChart = { require( chips.length == enabled.length, "Expecting " + chips.length + " elements in the input array, got " + enabled.length ) - new GridColorChart(name, nbColumns, nbRows, chips, chipMargin, enabled.toList, refWhite, alignmentTransform) + new GridColorChart(name, nbColumns, nbRows, chips, chipMargin, enabled, refWhite, alignmentTransform) } - override def copyWithEnabledAll: GridColorChart = copyWithEnabled(Array.fill(enabled.length)(true)) + override def copyWithEnabledAll: GridColorChart = copyWithEnabled(IndexedSeq.fill(enabled.length)(true)) /** Creates a copy of this chart with different `chipMargin`. Value of the margin must be between 0 and 0.5. */ override def copyWithChipMargin(newChipMargin: Double): GridColorChart = diff --git a/ijp-color/src/main/scala/ij_plugins/color/calibration/regression/MappingFactory.scala b/ijp-color/src/main/scala/ij_plugins/color/calibration/regression/MappingFactory.scala index 6c64592..7e86a6e 100644 --- a/ijp-color/src/main/scala/ij_plugins/color/calibration/regression/MappingFactory.scala +++ b/ijp-color/src/main/scala/ij_plugins/color/calibration/regression/MappingFactory.scala @@ -30,25 +30,25 @@ import org.apache.commons.math3.linear.MatrixUtils * regression. */ object MappingFactory { - def createCubicPolynomialTriple( - standard: Array[Array[Double]], - observed: Array[Array[Double]], - method: MappingMethod - ): CubicPolynomialTriple = { + + def createCubicPolynomialTriple(standard: IndexedSeq[IndexedSeq[Double]], + observed: IndexedSeq[IndexedSeq[Double]], + method: MappingMethod + ): CubicPolynomialTriple = { validateStandardAndObserved(standard, observed) - val standardM = MatrixUtils.createRealMatrix(standard) - val observedM = MatrixUtils.createRealMatrix(observed) + val standardM = MatrixUtils.createRealMatrix(toArrayArray(standard)) + val observedM = MatrixUtils.createRealMatrix(toArrayArray(observed)) val (redBandCoefficients, greenBandCoefficients, blueBandCoefficients) = method match { case LinearNoIntercept => ( - createLinearNoIntercept(standardM.getColumn(0), observedM.getColumn(0), 0), - createLinearNoIntercept(standardM.getColumn(1), observedM.getColumn(1), 1), - createLinearNoIntercept(standardM.getColumn(2), observedM.getColumn(2), 2) - ) + createLinearNoIntercept(standardM.getColumn(0), observedM.getColumn(0), 0), + createLinearNoIntercept(standardM.getColumn(1), observedM.getColumn(1), 1), + createLinearNoIntercept(standardM.getColumn(2), observedM.getColumn(2), 2) + ) case LinearNoInterceptCrossBand => ( - createLinearNoInterceptXBand(standardM.getColumn(0), observed), - createLinearNoInterceptXBand(standardM.getColumn(1), observed), - createLinearNoInterceptXBand(standardM.getColumn(2), observed) - ) + createLinearNoInterceptXBand(standardM.getColumn(0), observed), + createLinearNoInterceptXBand(standardM.getColumn(1), observed), + createLinearNoInterceptXBand(standardM.getColumn(2), observed) + ) case Linear => ( createLinear(standardM.getColumn(0), observedM.getColumn(0), 0), createLinear(standardM.getColumn(1), observedM.getColumn(1), 1), @@ -118,18 +118,15 @@ object MappingFactory { validateStandardAndObserved(standard, observed) require(observed.length >= 1, "Linear No-intercept fit needs at least 1 observation, got " + observed.length) - val data = Array.ofDim[Double](observed.length, 1) - for (i <- observed.indices) { - data(i)(0) = observed(i) - } + val data = observed.map(v => Array(v).toIndexedSeq).toIndexedSeq + Regression.regression(standard, data, noIntercept = true) } - private[regression] def createLinearNoInterceptXBand( - standard: Array[Double], - observation: Array[Array[Double]] - ): CubicPolynomial = { - validateStandardAndObserved(standard, observation) + private[regression] def createLinearNoInterceptXBand(standard: Array[Double], + observation: IndexedSeq[IndexedSeq[Double]] + ): CubicPolynomial = { + validateStandardAndObserved2(standard, observation) require(observation.length >= 4, "Linear cross-band fit needs at least 4 observations, got " + observation.length) val rr = Regression.regression(standard, observation, noIntercept = true) @@ -172,38 +169,34 @@ object MappingFactory { validateStandardAndObserved(standard, observed) require(observed.length >= 2, "Linear fit needs at least 2 observations, got " + observed.length) - val data = Array.ofDim[Double](observed.length, 1) - for (i <- observed.indices) { - data(i)(0) = observed(i) - } + val data = observed.map(v => Array(v).toIndexedSeq).toIndexedSeq Regression.regression(standard, data, noIntercept = false) } - private[regression] def createLinearXBand( - standard: Array[Double], - observation: Array[Array[Double]] - ): CubicPolynomial = { - validateStandardAndObserved(standard, observation) + private[regression] def createLinearXBand(standard: Array[Double], + observation: IndexedSeq[IndexedSeq[Double]] + ): CubicPolynomial = { + validateStandardAndObserved2(standard, observation) require(observation.length >= 4, "Linear cross-band fit needs at least 4 observations, got " + observation.length) val rr = Regression.regression(standard, observation, noIntercept = false) toCubicPolynomial(rr) } private[regression] def createQuadratic( - standard: Array[Double], - observed: Array[Double], - band: Int - ): CubicPolynomial = { + standard: Array[Double], + observed: Array[Double], + band: Int + ): CubicPolynomial = { validateStandardAndObserved(standard, observed) val regressionResult = createQuadratic(standard, observed) - val intercept = regressionResult.beta(0) + val intercept = regressionResult.beta(0) band match { case 0 => CubicPolynomial( - intercept = intercept, - a = regressionResult.beta(1), - aa = regressionResult.beta(2), - regressionResult = Some(regressionResult) - ) + intercept = intercept, + a = regressionResult.beta(1), + aa = regressionResult.beta(2), + regressionResult = Some(regressionResult) + ) case 1 => CubicPolynomial( intercept = intercept, b = regressionResult.beta(1), @@ -224,65 +217,64 @@ object MappingFactory { private[regression] def createQuadratic(standard: Array[Double], observed: Array[Double]): Regression.Result = { validateStandardAndObserved(standard, observed) require(observed.length >= 3, "Quadratic fit needs at least 3 observations, got " + observed.length) - val data: Array[Array[Double]] = Array.ofDim[Double](observed.length, 2) - for (i <- observed.indices) { - val o: Double = observed(i) - data(i)(0) = o - data(i)(1) = o * o - } + val data = observed.map { o => + Array(o, o * o).toIndexedSeq + }.toIndexedSeq + Regression.regression(standard, data, noIntercept = false) } - private[regression] def createQuadraticXBand( - standard: Array[Double], - observation: Array[Array[Double]] - ): CubicPolynomial = { - validateStandardAndObserved(standard, observation) + private[regression] def createQuadraticXBand(standard: Array[Double], + observation: IndexedSeq[IndexedSeq[Double]] + ): CubicPolynomial = { + validateStandardAndObserved2(standard, observation) require( observation.length >= 10, "Quadratic cross-band fit needs at least 10 observations, got " + observation.length ) - val data = Array.ofDim[Double](observation.length, 9) - - for (i <- observation.indices) { - val o: Array[Double] = observation(i) - assert(o.length == 3) - val a = o(0) - val b = o(1) - val c = o(2) - data(i)(0) = a - data(i)(1) = b - data(i)(2) = c - data(i)(3) = a * a - data(i)(4) = a * b - data(i)(5) = a * c - data(i)(6) = b * b - data(i)(7) = b * c - data(i)(8) = c * c - } + + val data = + observation.map { o => + assert(o.length == 3) + val a = o(0) + val b = o(1) + val c = o(2) + Array( + a, + b, + c, + a * a, + a * b, + a * c, + b * b, + b * c, + c * c + ).toIndexedSeq + } + val rr = Regression.regression(standard, data, noIntercept = false) toCubicPolynomial(rr) } private[regression] def createCubic(standard: Array[Double], observed: Array[Double], band: Int): CubicPolynomial = { validateStandardAndObserved(standard, observed) - val regressionResult = createCubic(standard, observed) - val intercept = regressionResult.beta(0) + val regressionResult = createCubic(standard, observed.toIndexedSeq) + val intercept = regressionResult.beta(0) band match { case 0 => CubicPolynomial( - intercept = intercept, - a = regressionResult.beta(1), - aa = regressionResult.beta(2), - aaa = regressionResult.beta(3), - regressionResult = Some(regressionResult) - ) + intercept = intercept, + a = regressionResult.beta(1), + aa = regressionResult.beta(2), + aaa = regressionResult.beta(3), + regressionResult = Some(regressionResult) + ) case 1 => CubicPolynomial( - intercept = intercept, - b = regressionResult.beta(1), - bb = regressionResult.beta(2), - bbb = regressionResult.beta(3), - regressionResult = Some(regressionResult) - ) + intercept = intercept, + b = regressionResult.beta(1), + bb = regressionResult.beta(2), + bbb = regressionResult.beta(3), + regressionResult = Some(regressionResult) + ) case 2 => CubicPolynomial( intercept = intercept, c = regressionResult.beta(1), @@ -295,45 +287,50 @@ object MappingFactory { } } - private[regression] def createCubic(standard: Array[Double], observed: Array[Double]): Regression.Result = { + private[regression] def createCubic(standard: Array[Double], observed: IndexedSeq[Double]): Regression.Result = { validateStandardAndObserved(standard, observed) require(observed.length >= 4, "Cubic fit needs at least 4 observations, got " + observed.length) - val data: Array[Array[Double]] = Array.ofDim[Double](observed.length, 3) - for (i <- observed.indices) { - val o: Double = observed(i) - data(i)(0) = o - data(i)(1) = o * o - data(i)(2) = o * o * o - } + val data = + observed.map { o => + IndexedSeq( + o, + o * o, + o * o * o + ) + } + Regression.regression(standard, data, noIntercept = false) } - private[regression] def createCubicXBand(standard: Array[Double], observed: Array[Array[Double]]): CubicPolynomial = { - validateStandardAndObserved(standard, observed) + private[regression] def createCubicXBand(standard: Array[Double], + observed: IndexedSeq[IndexedSeq[Double]]): CubicPolynomial = { + validateStandardAndObserved2(standard, observed) require(observed.length >= 14, "Cubic cross-band fit needs at least 14 observations, got " + observed.length) - val data: Array[Array[Double]] = Array.ofDim[Double](observed.length, 13) - - for (i <- observed.indices) { - val o: Array[Double] = observed(i) - assert(o.length == 3) - val a = o(0) - val b = o(1) - val c = o(2) - data(i)(0) = a - data(i)(1) = b - data(i)(2) = c - data(i)(3) = a * a - data(i)(4) = a * b - data(i)(5) = a * c - data(i)(6) = b * b - data(i)(7) = b * c - data(i)(8) = c * c - data(i)(9) = a * a * a - data(i)(10) = a * b * c - data(i)(11) = b * b * b - data(i)(12) = c * c * c - } + + val data = + observed.map { o => + assert(o.length == 3) + val a = o(0) + val b = o(1) + val c = o(2) + Array( + a, + b, + c, + a * a, + a * b, + a * c, + b * b, + b * c, + c * c, + a * a * a, + a * b * c, + b * b * b, + c * c * c, + ).toIndexedSeq + } + val rr = Regression.regression(standard, data, noIntercept = false) toCubicPolynomial(rr) } @@ -358,19 +355,25 @@ object MappingFactory { ) } - private[regression] def validateStandardAndObserved( - standard: Array[Double], - observation: Array[Array[Double]] - ): Unit = { + private[regression] def validateStandardAndObserved(standard: Array[Double], observed: IndexedSeq[Double]): Unit = { + require(observed != null) + require(standard != null) + require( + observed.length == standard.length, + "Number of Standard and Observed values are not equal reference.length='" + standard.length + "' observed.length='" + observed.length + "'" + ) + } + + + private[regression] def validateStandardAndObserved2(standard: Array[Double], + observation: IndexedSeq[IndexedSeq[Double]]): Unit = { require(standard != null) require(observation != null) require(observation.length == standard.length) } - private[regression] def validateStandardAndObserved( - standard: Array[Array[Double]], - observed: Array[Array[Double]] - ): Unit = { + private[regression] def validateStandardAndObserved(standard: IndexedSeq[IndexedSeq[Double]], + observed: IndexedSeq[IndexedSeq[Double]]): Unit = { require(observed != null) require(standard != null) require( diff --git a/ijp-color/src/main/scala/ij_plugins/color/calibration/regression/Regression.scala b/ijp-color/src/main/scala/ij_plugins/color/calibration/regression/Regression.scala index 22f6c49..4c88c18 100644 --- a/ijp-color/src/main/scala/ij_plugins/color/calibration/regression/Regression.scala +++ b/ijp-color/src/main/scala/ij_plugins/color/calibration/regression/Regression.scala @@ -1,6 +1,6 @@ /* * Image/J Plugins - * Copyright (C) 2002-2021 Jarek Sacha + * Copyright (C) 2002-2022 Jarek Sacha * Author's email: jpsacha at gmail dot com * * This library is free software; you can redistribute it and/or @@ -38,41 +38,40 @@ object Regression { ) /** - * Compute linear fit coefficient - * {{{ - * s = A*o. - * }}} - * - * @param standard - * array of expected output values. - * @param observation - * array of input values - * @return - * linear fit coefficients - * @see - * #regression(double[], double[][], boolean) - */ - def regression(standard: Array[Double], observation: Array[Array[Double]]): Regression.Result = { + * Compute linear fit coefficient + * {{{ + * s = A*o. + * }}} + * + * @param standard + * array of expected output values. + * @param observation + * array of input values + * @return + * linear fit coefficients + * @see + * #regression(double[], double[][], boolean) + */ + def regression(standard: Array[Double], observation: IndexedSeq[IndexedSeq[Double]]): Regression.Result = { regression(standard, observation, noIntercept = true) } /** - * Compute linear fit coefficient `s = A*o` if `noIntercept` is true or `s = A*o + b` if `noIntercept` is `false`. - * - * @param standard - * array of expected output values. - * @param observation - * array of input values - * @param noIntercept - * true means the model is to be estimated without an intercept term - * @return - * linear fit coefficients - */ - def regression( - standard: Array[Double], - observation: Array[Array[Double]], - noIntercept: Boolean - ): Regression.Result = { + * Compute linear fit coefficient `s = A*o` if `noIntercept` is true or `s = A*o + b` if `noIntercept` is `false`. + * + * @param standard + * array of expected output values. + * @param observation + * array of input values + * @param noIntercept + * true means the model is to be estimated without an intercept term + * @return + * linear fit coefficients + */ + def regression(standard: Array[Double], + observation: IndexedSeq[IndexedSeq[Double]], + noIntercept: Boolean + ): Regression.Result = { require(standard != null, "Argument `standard` cannot be null.") require(observation != null, "Argument `observation` cannot be null.") require(observation.length == standard.length) @@ -87,7 +86,7 @@ object Regression { val regression = new OLSMultipleLinearRegression() regression.setNoIntercept(noIntercept) - regression.newSampleData(standard, observation) + regression.newSampleData(standard, toArrayArray(observation)) Regression.Result( numberOfSamples = standard.length, beta = regression.estimateRegressionParameters, @@ -99,16 +98,16 @@ object Regression { } /** - * Compute linear fit coefficients that map observations to a reference: `s = A*[o, 1]`. - * - * @param standard - * reference values. - * @param observation - * observed values. - * @return - * linear fit coefficients. - */ - def createLinear(standard: Array[Double], observation: Array[Array[Double]]): Regression.Result = { + * Compute linear fit coefficients that map observations to a reference: `s = A*[o, 1]`. + * + * @param standard + * reference values. + * @param observation + * observed values. + * @return + * linear fit coefficients. + */ + def createLinear(standard: Array[Double], observation: IndexedSeq[IndexedSeq[Double]]): Regression.Result = { regression(standard, observation, noIntercept = false) } } diff --git a/ijp-color/src/main/scala/ij_plugins/color/calibration/regression/package.scala b/ijp-color/src/main/scala/ij_plugins/color/calibration/regression/package.scala index 4db4278..51dbb2a 100644 --- a/ijp-color/src/main/scala/ij_plugins/color/calibration/regression/package.scala +++ b/ijp-color/src/main/scala/ij_plugins/color/calibration/regression/package.scala @@ -1,6 +1,6 @@ /* * Image/J Plugins - * Copyright (C) 2002-2021 Jarek Sacha + * Copyright (C) 2002-2022 Jarek Sacha * Author's email: jpsacha at gmail dot com * * This library is free software; you can redistribute it and/or @@ -23,6 +23,8 @@ package ij_plugins.color.calibration /** - * Color correction mappings based on polynomial regression. - */ -package object regression + * Color correction mappings based on polynomial regression. + */ +package object regression { + private[regression] def toArrayArray(s: IndexedSeq[IndexedSeq[Double]]): Array[Array[Double]] = s.map(_.toArray).toArray +} diff --git a/ijp-color/src/main/scala/ij_plugins/color/converter/ColorTriple.scala b/ijp-color/src/main/scala/ij_plugins/color/converter/ColorTriple.scala index 7786346..8b70c27 100644 --- a/ijp-color/src/main/scala/ij_plugins/color/converter/ColorTriple.scala +++ b/ijp-color/src/main/scala/ij_plugins/color/converter/ColorTriple.scala @@ -1,6 +1,6 @@ /* * Image/J Plugins - * Copyright (C) 2002-2021 Jarek Sacha + * Copyright (C) 2002-2022 Jarek Sacha * Author's email: jpsacha at gmail dot com * * This library is free software; you can redistribute it and/or @@ -29,7 +29,11 @@ trait ColorTriple { def apply(index: Int): Double /** Convert color representation to an Array */ + @inline def toArray: Array[Double] = Array(apply(0), apply(1), apply(2)) + + @inline + def toIndexedSeq: IndexedSeq[Double] = toArray.toIndexedSeq } /** Color triples with band/channel names specific to color spaces. */ diff --git a/ijp-color/src/main/scala/ij_plugins/color/util/ImageJUtils.scala b/ijp-color/src/main/scala/ij_plugins/color/util/ImageJUtils.scala index 942560c..19d5170 100644 --- a/ijp-color/src/main/scala/ij_plugins/color/util/ImageJUtils.scala +++ b/ijp-color/src/main/scala/ij_plugins/color/util/ImageJUtils.scala @@ -138,33 +138,36 @@ object ImageJUtils { } /** - * Measure color within ROI. - * - * @param tri - * three bands of an image, may represent only color space. - * @param outline - * outline of the region of interest. - * @return - * average color in the ROI. - * @see - * #measureColorXY(ij.process.ImageProcessor[], ij.gui.Roi) - */ + * Measure color within ROI. + * + * @param tri + * three bands of an image, may represent only color space. + * @param outline + * outline of the region of interest. + * @return + * average color in the ROI. + * @see + * #measureColorXY(ij.process.ImageProcessor[], ij.gui.Roi) + */ def measureColor[T <: ImageProcessor](tri: Array[T], outline: Array[Point2D]): Array[Double] = { measureColor(tri, toRoi(outline.toSeq)) } + def measureColor[T <: ImageProcessor](tri: IndexedSeq[T], outline: IndexedSeq[Point2D]): IndexedSeq[Double] = + measureColor(tri, toRoi(outline)) + /** - * Measure color within ROI. - * - * @param tri - * three bands of an image, may represent only color space. - * @param roi - * region of interest. - * @return - * average color in the ROI. - * @see - * #measureColorXY(ij.process.ImageProcessor[], ij.gui.Roi) - */ + * Measure color within ROI. + * + * @param tri + * three bands of an image, may represent only color space. + * @param roi + * region of interest. + * @return + * average color in the ROI. + * @see + * #measureColorXY(ij.process.ImageProcessor[], ij.gui.Roi) + */ def measureColor[T <: ImageProcessor](tri: Array[T], roi: Roi): Array[Double] = { val color: Array[Double] = new Array[Double](tri.length) for (i <- tri.indices) { @@ -174,16 +177,23 @@ object ImageJUtils { color } + def measureColor[T <: ImageProcessor](tri: IndexedSeq[T], roi: Roi): IndexedSeq[Double] = { + tri.map { ip => + ip.setRoi(roi) + ip.getStatistics.mean + } + } + /** - * @param src - * images to validate - * @param length - * expected number of images - * @tparam T - * image processor type - * @throws java.lang.IllegalArgumentException - * if the images in the array are not of the same dimension. - */ + * @param src + * images to validate + * @param length + * expected number of images + * @tparam T + * image processor type + * @throws java.lang.IllegalArgumentException + * if the images in the array are not of the same dimension. + */ @inline def validateSameDimensions[T <: ImageProcessor](src: Array[T], length: Int): Unit = { require(src != null, "Input cannot be null.") diff --git a/ijp-color/src/main/scala/ij_plugins/color/util/Utils.scala b/ijp-color/src/main/scala/ij_plugins/color/util/Utils.scala index cf90343..a6d9147 100644 --- a/ijp-color/src/main/scala/ij_plugins/color/util/Utils.scala +++ b/ijp-color/src/main/scala/ij_plugins/color/util/Utils.scala @@ -1,6 +1,6 @@ /* * Image/J Plugins - * Copyright (C) 2002-2021 Jarek Sacha + * Copyright (C) 2002-2022 Jarek Sacha * Author's email: jpsacha at gmail dot com * * This library is free software; you can redistribute it and/or @@ -46,12 +46,15 @@ object Utils { math.sqrt(sum) } + def delta(a: IndexedSeq[Double], b: IndexedSeq[Double]): Double = delta(a.toArray, b.toArray) + + /** - * Clip all elements of the input array to range suitable for an 8-bit unsigned integer: 0 to 255. - * - * Values in the range will be rounded to the closest integer. Values less than 0 will be set to 0. Values greater - * than 255 will be set to 255. - */ + * Clip all elements of the input array to range suitable for an 8-bit unsigned integer: 0 to 255. + * + * Values in the range will be rounded to the closest integer. Values less than 0 will be set to 0. Values greater + * than 255 will be set to 255. + */ def clipUInt8(a: Array[Double]): Array[Int] = { assert(a != null) val r = new Array[Int](a.length) diff --git a/ijp-color/src/test/scala/ij_plugins/color/calibration/ColorCalibratorSpec.scala b/ijp-color/src/test/scala/ij_plugins/color/calibration/ColorCalibratorSpec.scala index b6a5811..9922896 100644 --- a/ijp-color/src/test/scala/ij_plugins/color/calibration/ColorCalibratorSpec.scala +++ b/ijp-color/src/test/scala/ij_plugins/color/calibration/ColorCalibratorSpec.scala @@ -1,6 +1,6 @@ /* * Image/J Plugins - * Copyright (C) 2002-2021 Jarek Sacha + * Copyright (C) 2002-2022 Jarek Sacha * Author's email: jpsacha at gmail dot com * * This library is free software; you can redistribute it and/or @@ -41,31 +41,31 @@ class ColorCalibratorSpec extends AnyFlatSpec { "ColorCalibrator" should "perform color calibration in XYZ" in { // Chip values extracted from image "test/data/Passport-linear.tif" - val observed = Array( - Array(3851.893490574798, 1820.0958925310304, 1081.198749497013), - Array(16410.104982096556, 10655.920854426473, 7690.646283491789), - Array(3472.768551673046, 8135.125138906038, 12503.371373009013), - Array(2684.0717372515123, 4054.299049265341, 1411.8277256451413), - Array(7477.835998271392, 8336.07775651315, 15226.610291394), - Array(5201.1832443052745, 19058.847700620125, 16467.021796524838), - Array(20038.040115145326, 4672.813291237193, 78.92818893738199), - Array(2221.6156006914434, 4260.434930238301, 14121.624367205828), - Array(15785.138535621682, 2236.879583899247, 2232.0833744906777), - Array(2970.8610322262007, 1290.8258735646375, 4063.300253117669), - Array(8753.748147919496, 17177.93335596987, 2280.2255525373503), - Array(19219.849174188003, 9679.008565113281, 266.20740467857803), - Array(414.4198337846671, 2596.390045755906, 9125.597099013292), - Array(1107.6494937646623, 10670.602697863933, 3125.2439807383626), - Array(11850.008704778367, 241.4327694777133, 334.81911347079887), - Array(23971.986603284357, 19444.33176935424, 353.3155945178417), - Array(14612.416440301271, 3170.6015866156317, 8516.639276453883), - Array(856.160496101185, 9154.455516669737, 13771.1440412599), - Array(29347.484639088616, 35452.884178416905, 34019.23145640738), - Array(17745.21410220695, 22728.341535890057, 22223.123595505618), - Array(10271.672644770959, 13308.654494382023, 13039.64915421657), - Array(4647.7389183849855, 5868.471663168292, 5809.715520434622), - Array(1399.1883257192246, 1857.1423632547228, 1683.8465242622547), - Array(376.3981937853972, 466.053053727231, 423.635542629726) + val observed = IndexedSeq( + IndexedSeq(3851.893490574798, 1820.0958925310304, 1081.198749497013), + IndexedSeq(16410.104982096556, 10655.920854426473, 7690.646283491789), + IndexedSeq(3472.768551673046, 8135.125138906038, 12503.371373009013), + IndexedSeq(2684.0717372515123, 4054.299049265341, 1411.8277256451413), + IndexedSeq(7477.835998271392, 8336.07775651315, 15226.610291394), + IndexedSeq(5201.1832443052745, 19058.847700620125, 16467.021796524838), + IndexedSeq(20038.040115145326, 4672.813291237193, 78.92818893738199), + IndexedSeq(2221.6156006914434, 4260.434930238301, 14121.624367205828), + IndexedSeq(15785.138535621682, 2236.879583899247, 2232.0833744906777), + IndexedSeq(2970.8610322262007, 1290.8258735646375, 4063.300253117669), + IndexedSeq(8753.748147919496, 17177.93335596987, 2280.2255525373503), + IndexedSeq(19219.849174188003, 9679.008565113281, 266.20740467857803), + IndexedSeq(414.4198337846671, 2596.390045755906, 9125.597099013292), + IndexedSeq(1107.6494937646623, 10670.602697863933, 3125.2439807383626), + IndexedSeq(11850.008704778367, 241.4327694777133, 334.81911347079887), + IndexedSeq(23971.986603284357, 19444.33176935424, 353.3155945178417), + IndexedSeq(14612.416440301271, 3170.6015866156317, 8516.639276453883), + IndexedSeq(856.160496101185, 9154.455516669737, 13771.1440412599), + IndexedSeq(29347.484639088616, 35452.884178416905, 34019.23145640738), + IndexedSeq(17745.21410220695, 22728.341535890057, 22223.123595505618), + IndexedSeq(10271.672644770959, 13308.654494382023, 13039.64915421657), + IndexedSeq(4647.7389183849855, 5868.471663168292, 5809.715520434622), + IndexedSeq(1399.1883257192246, 1857.1423632547228, 1683.8465242622547), + IndexedSeq(376.3981937853972, 466.053053727231, 423.635542629726) ) // Parameters diff --git a/ijp-color/src/test/scala/ij_plugins/color/calibration/LOOCrossValidation1Demo.scala b/ijp-color/src/test/scala/ij_plugins/color/calibration/LOOCrossValidation1Demo.scala index a4a4f12..d1e1627 100644 --- a/ijp-color/src/test/scala/ij_plugins/color/calibration/LOOCrossValidation1Demo.scala +++ b/ijp-color/src/test/scala/ij_plugins/color/calibration/LOOCrossValidation1Demo.scala @@ -1,6 +1,6 @@ /* * Image/J Plugins - * Copyright (C) 2002-2021 Jarek Sacha + * Copyright (C) 2002-2022 Jarek Sacha * Author's email: jpsacha at gmail dot com * * This library is free software; you can redistribute it and/or @@ -79,8 +79,7 @@ object LOOCrossValidation1Demo { printBestInfo(crossValidations) // LOO-CV with invalid-reference-chips disabled - val enabled2 = chart.enabled.toArray - enabled2(6) = false + val enabled2 = chart.enabled.updated(6, false) val chart2 = chart.copyWithEnabled(enabled2) val crossValidations2 = LOOCrossValidation.crossValidationStatsAll(chart2, imp, ReferenceColorSpace.values, MappingMethod.values) diff --git a/ijp-color/src/test/scala/ij_plugins/color/calibration/LOOCrossValidation2Demo.scala b/ijp-color/src/test/scala/ij_plugins/color/calibration/LOOCrossValidation2Demo.scala index 85c3b65..4c6fba4 100644 --- a/ijp-color/src/test/scala/ij_plugins/color/calibration/LOOCrossValidation2Demo.scala +++ b/ijp-color/src/test/scala/ij_plugins/color/calibration/LOOCrossValidation2Demo.scala @@ -1,6 +1,6 @@ /* * Image/J Plugins - * Copyright (C) 2002-2021 Jarek Sacha + * Copyright (C) 2002-2022 Jarek Sacha * Author's email: jpsacha at gmail dot com * * This library is free software; you can redistribute it and/or @@ -38,13 +38,14 @@ object LOOCrossValidation2Demo extends App { val srcRT = ResultsTable.open(tableFile) // Extract chart values as an array - val observed = new Array[Array[Double]](srcRT.getCounter) - for (i <- observed.indices) { - observed(i) = new Array[Double](3) - observed(i)(0) = srcRT.getValue("Observed X", i) - observed(i)(1) = srcRT.getValue("Observed Y", i) - observed(i)(2) = srcRT.getValue("Observed Z", i) - } + val observed = + (0 until srcRT.getCounter).map { i => + IndexedSeq( + srcRT.getValue("Observed X", i), + srcRT.getValue("Observed Y", i), + srcRT.getValue("Observed Z", i) + ) + } // Reference chart val chart = ColorCharts.XRitePassportColorChecker diff --git a/ijp-color/src/test/scala/ij_plugins/color/calibration/LOOCrossValidationSpec.scala b/ijp-color/src/test/scala/ij_plugins/color/calibration/LOOCrossValidationSpec.scala index 73baf2f..e0a9cc1 100644 --- a/ijp-color/src/test/scala/ij_plugins/color/calibration/LOOCrossValidationSpec.scala +++ b/ijp-color/src/test/scala/ij_plugins/color/calibration/LOOCrossValidationSpec.scala @@ -1,6 +1,6 @@ /* * Image/J Plugins - * Copyright (C) 2002-2021 Jarek Sacha + * Copyright (C) 2002-2022 Jarek Sacha * Author's email: jpsacha at gmail dot com * * This library is free software; you can redistribute it and/or @@ -45,20 +45,21 @@ class LOOCrossValidationSpec extends AnyFlatSpec { assert(tableFile.exists(), "Input table file should exist: " + tableFile) val srcRT = ResultsTable.open(tableFile.getCanonicalPath) // Extract chart values as an array - val observed = new Array[Array[Double]](srcRT.getCounter) - for (i <- observed.indices) { - observed(i) = new Array[Double](3) - observed(i)(0) = srcRT.getValue("Observed X", i) - observed(i)(1) = srcRT.getValue("Observed Y", i) - observed(i)(2) = srcRT.getValue("Observed Z", i) - } + val observed = + (0 until srcRT.getCounter).map { i => + IndexedSeq( + srcRT.getValue("Observed X", i), + srcRT.getValue("Observed Y", i), + srcRT.getValue("Observed Z", i) + ) + } // Reference chart val chart = ColorCharts.XRitePassportColorChecker // Do LOO validation val referenceColorSpaces = ReferenceColorSpace.values - val mappingMethods = MappingMethod.values + val mappingMethods = MappingMethod.values val crossValidations = LOOCrossValidation.crossValidationStatsAll(chart, observed, referenceColorSpaces, mappingMethods) @@ -100,8 +101,7 @@ class LOOCrossValidationSpec extends AnyFlatSpec { val (imp, chart) = loadImageChart() // LOO-CV with invalid-reference-chips disabled - val enabled = chart.enabled.toArray - enabled(6) = false + val enabled = chart.enabled.updated(6, false) val chart2 = chart.copyWithEnabled(enabled) val crossValidations = LOOCrossValidation.crossValidationStatsAll(chart2, imp, ReferenceColorSpace.values, MappingMethod.values) diff --git a/ijp-color/src/test/scala/ij_plugins/color/calibration/PolynomialCorrectorDemo.scala b/ijp-color/src/test/scala/ij_plugins/color/calibration/PolynomialCorrectorDemo.scala index 0285d72..485a272 100644 --- a/ijp-color/src/test/scala/ij_plugins/color/calibration/PolynomialCorrectorDemo.scala +++ b/ijp-color/src/test/scala/ij_plugins/color/calibration/PolynomialCorrectorDemo.scala @@ -1,6 +1,6 @@ /* * Image/J Plugins - * Copyright (C) 2002-2021 Jarek Sacha + * Copyright (C) 2002-2022 Jarek Sacha * Author's email: jpsacha at gmail dot com * * This library is free software; you can redistribute it and/or @@ -35,13 +35,14 @@ object PolynomialCorrectorDemo extends App { val srcRT = ResultsTable.open(tableFile) // Extract chart values as an array - val observed = new Array[Array[Double]](srcRT.getCounter) - for (i <- observed.indices) { - observed(i) = new Array[Double](3) - observed(i)(0) = srcRT.getValue("Observed X", i) - observed(i)(1) = srcRT.getValue("Observed Y", i) - observed(i)(2) = srcRT.getValue("Observed Z", i) - } + val observed = + (0 until srcRT.getCounter).map { i => + IndexedSeq( + srcRT.getValue("Observed X", i), + srcRT.getValue("Observed Y", i), + srcRT.getValue("Observed Z", i) + ) + } // Reference chart // val chart = ColorCharts.XRitePassportColorChecker diff --git a/ijp-color/src/test/scala/ij_plugins/color/calibration/regression/MappingFactoryTest.scala b/ijp-color/src/test/scala/ij_plugins/color/calibration/regression/MappingFactoryTest.scala index 8065b2d..df21b7f 100644 --- a/ijp-color/src/test/scala/ij_plugins/color/calibration/regression/MappingFactoryTest.scala +++ b/ijp-color/src/test/scala/ij_plugins/color/calibration/regression/MappingFactoryTest.scala @@ -1,6 +1,6 @@ /* * Image/J Plugins - * Copyright (C) 2002-2021 Jarek Sacha + * Copyright (C) 2002-2022 Jarek Sacha * Author's email: jpsacha at gmail dot com * * This library is free software; you can redistribute it and/or @@ -78,34 +78,34 @@ class MappingFactoryTest extends AnyFlatSpec { 177.94611517624423, 252.25703548744028, 83.31632229886839, 204.2661490485881, 265.5540885367791, 204.39823118845072, 248.2796227241613, 201.91675592164282, 160.56106457681673, 117.76955679703373, 83.66445997737526, 49.37241240933231) - val observed = Array( - Array(50.0181598062954, 20.692493946731236, 6.2941888619854724), - Array(123.94199243379572, 60.84741488020177, 43.90668348045397), - Array(52.774739583333336, 48.147135416666664, 55.24609375), - Array(49.07827260458839, 45.76923076923077, 3.4116059379217276), - Array(80.11018131101812, 56.83682008368201, 76.83124128312413), - Array(70.71551724137932, 104.76867816091954, 77.96120689655173), - Array(129.9434832756632, 48.10957324106113, 6.1280276816609), - Array(122.0730198019802, 31.084158415841586, 27.407178217821784), - Array(34.896153846153844, 13.28974358974359, 25.8), - Array(111.19521912350598, 106.04780876494024, 0.0), - Array(159.51440329218107, 89.1673525377229, 0.0), - Array(42.25541619156214, 64.54161915621437, 4.850627137970354), - Array(101.95985832349469, 20.65289256198347, 14.334120425029516), - Array(166.38536585365853, 109.20487804878049, 0.0), - Array(118.84702907711757, 41.25031605562579, 65.6826801517067), - Array(160.03979057591624, 124.83769633507853, 102.07748691099476), - Array(130.071661237785, 99.51031487513572, 81.14332247557003), - Array(101.88004484304933, 76.65807174887892, 62.26569506726457), - Array(71.38675958188153, 50.54239256678281, 37.92566782810685), - Array(42.70169082125604, 29.83816425120773, 21.544685990338163), - Array(15.848180677540778, 5.183186951066499, 2.986198243412798) + val observed = IndexedSeq( + IndexedSeq(50.0181598062954, 20.692493946731236, 6.2941888619854724), + IndexedSeq(123.94199243379572, 60.84741488020177, 43.90668348045397), + IndexedSeq(52.774739583333336, 48.147135416666664, 55.24609375), + IndexedSeq(49.07827260458839, 45.76923076923077, 3.4116059379217276), + IndexedSeq(80.11018131101812, 56.83682008368201, 76.83124128312413), + IndexedSeq(70.71551724137932, 104.76867816091954, 77.96120689655173), + IndexedSeq(129.9434832756632, 48.10957324106113, 6.1280276816609), + IndexedSeq(122.0730198019802, 31.084158415841586, 27.407178217821784), + IndexedSeq(34.896153846153844, 13.28974358974359, 25.8), + IndexedSeq(111.19521912350598, 106.04780876494024, 0.0), + IndexedSeq(159.51440329218107, 89.1673525377229, 0.0), + IndexedSeq(42.25541619156214, 64.54161915621437, 4.850627137970354), + IndexedSeq(101.95985832349469, 20.65289256198347, 14.334120425029516), + IndexedSeq(166.38536585365853, 109.20487804878049, 0.0), + IndexedSeq(118.84702907711757, 41.25031605562579, 65.6826801517067), + IndexedSeq(160.03979057591624, 124.83769633507853, 102.07748691099476), + IndexedSeq(130.071661237785, 99.51031487513572, 81.14332247557003), + IndexedSeq(101.88004484304933, 76.65807174887892, 62.26569506726457), + IndexedSeq(71.38675958188153, 50.54239256678281, 37.92566782810685), + IndexedSeq(42.70169082125604, 29.83816425120773, 21.544685990338163), + IndexedSeq(15.848180677540778, 5.183186951066499, 2.986198243412798) ) val expectedResult = Array(0.0, 1.9724784162678572, -0.426914583530878, -0.1296547005685931, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) - val cp = MappingFactory.createLinearNoInterceptXBand(standard, observed) + val cp = MappingFactory.createLinearNoInterceptXBand(standard, observed) val cpArray = cp.toArray cpArray should be(expectedResult) diff --git a/ijp-color/src/test/scala/ij_plugins/color/converter/ColorConverterTest.scala b/ijp-color/src/test/scala/ij_plugins/color/converter/ColorConverterTest.scala index 389ea35..1011f4c 100644 --- a/ijp-color/src/test/scala/ij_plugins/color/converter/ColorConverterTest.scala +++ b/ijp-color/src/test/scala/ij_plugins/color/converter/ColorConverterTest.scala @@ -1,6 +1,6 @@ /* * Image/J Plugins - * Copyright (C) 2002-2021 Jarek Sacha + * Copyright (C) 2002-2022 Jarek Sacha * Author's email: jpsacha at gmail dot com * * This library is free software; you can redistribute it and/or @@ -136,7 +136,7 @@ class ColorConverterTest extends AnyFlatSpec { it should "convert RGB to XYZ with scaled RGB" in { val converter = new ColorConverter(ReferenceWhite.D50, AdobeRGB1998, None, rgbScale = 255, xyzScale = 1) - val rgb = RGB(178, 217, 18) + val rgb = RGB(Array[Double](178, 217, 18)) val xyz = converter.rgbToXYZ(rgb.r, rgb.g, rgb.b) xyz.x should be(0.392179 +- 0.00001) xyz.y should be(0.574946 +- 0.00001)