Skip to content

Commit

Permalink
Allow for calibration with some chips disabled #39
Browse files Browse the repository at this point in the history
  • Loading branch information
jpsacha committed Sep 2, 2021
1 parent 5f07a33 commit cbda651
Show file tree
Hide file tree
Showing 17 changed files with 730 additions and 204 deletions.
4 changes: 2 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import scala.xml.{Node => XmlNode, NodeSeq => XmlNodeSeq, _}

name := "ijp-color-project"

val _version = "0.10.2.1-SNAPSHOT"
val _version = "0.10.2.2-SNAPSHOT"
val _scalaVersions = Seq("2.13.6", "2.12.14")
val _scalaVersion = _scalaVersions.head

Expand Down Expand Up @@ -151,7 +151,7 @@ lazy val ijp_color_ui = (project in file("ijp-color-ui"))
libraryDependencies ++= Seq(
"org.jfree" % "jfreechart-fx" % "1.0.1",
"org.jfree" % "fxgraphics2d" % "1.8",
"org.scalafx" %% "scalafx" % "16.0.0-R24",
"org.scalafx" %% "scalafx" % "16.0.0-R25-SNAPSHOT",
"org.scalafx" %% "scalafx-extras" % "0.3.6",
"org.scalafx" %% "scalafxml-core-sfx8" % "0.5",
// Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,31 +90,31 @@
</padding>
</HBox>
<Label id="ijp-label" alignment="CENTER_RIGHT" text="Reference" GridPane.halignment="RIGHT"
GridPane.rowIndex="8" GridPane.valignment="CENTER"/>
GridPane.rowIndex="9"/>
<ChoiceBox fx:id="referenceColorSpaceChoiceBox" maxWidth="1.7976931348623157E308" prefWidth="150.0"
GridPane.columnIndex="1" GridPane.rowIndex="8"/>
<Label id="ijp-label" text="Mapping method" GridPane.rowIndex="9"/>
<ChoiceBox fx:id="mappingMethodChoiceBox" maxWidth="1.7976931348623157E308" prefWidth="150.0"
GridPane.columnIndex="1" GridPane.rowIndex="9"/>
<Button fx:id="suggestCalibrationOptionsButton" mnemonicParsing="false" text="Sugget Options"
GridPane.columnIndex="2" GridPane.rowIndex="8"/>
<Label id="ijp-label" text="Mapping method" GridPane.rowIndex="10"/>
<ChoiceBox fx:id="mappingMethodChoiceBox" maxWidth="1.7976931348623157E308" prefWidth="150.0"
GridPane.columnIndex="1" GridPane.rowIndex="10"/>
<Button fx:id="suggestCalibrationOptionsButton" maxWidth="1.7976931348623157E308"
mnemonicParsing="false" text="Sugget Options" GridPane.columnIndex="2" GridPane.rowIndex="9"/>
<Button id="ijp-button" fx:id="calibrateButton" contentDisplay="CENTER"
maxWidth="1.7976931348623157E308" mnemonicParsing="false" text="Calibrate"
GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.rowIndex="11">
GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.rowIndex="12">
<GridPane.margin>
<Insets bottom="5.0" top="5.0"/>
</GridPane.margin>
</Button>
<Button fx:id="applyToCurrentImageButton" maxWidth="1.7976931348623157E308" mnemonicParsing="false"
text="Apply" GridPane.columnIndex="1" GridPane.rowIndex="13">
text="Apply" GridPane.columnIndex="1" GridPane.rowIndex="14">
<padding>
<Insets bottom="5.0" top="5.0"/>
</padding>
<GridPane.margin>
<Insets bottom="5.0" top="5.0"/>
</GridPane.margin>
</Button>
<HBox prefWidth="200.0" GridPane.columnSpan="2147483647" GridPane.rowIndex="12">
<HBox prefWidth="200.0" GridPane.columnSpan="2147483647" GridPane.rowIndex="13">
<children>
<Label id="ijp-separator" text="Apply to Another Image"/>
<Separator id="ijp-separator" valignment="TOP" HBox.hgrow="ALWAYS">
Expand All @@ -128,15 +128,20 @@
</padding>
</HBox>
<Button fx:id="helpButton" maxWidth="1.7976931348623157E308" mnemonicParsing="false" text="Help"
GridPane.columnIndex="2" GridPane.rowIndex="13"/>
GridPane.columnIndex="2" GridPane.rowIndex="14"/>
<Label id="ijp-label" text="Info" GridPane.halignment="RIGHT" GridPane.rowIndex="4"
GridPane.valignment="CENTER"/>
<Label id="ijp-label" fx:id="chartInfoLabel" text="???" GridPane.columnIndex="1" GridPane.rowIndex="4"/>
<Button fx:id="editChartButton" disable="true" maxWidth="1.7976931348623157E308" mnemonicParsing="false"
text="Edit Chart" GridPane.columnIndex="2" GridPane.rowIndex="3"/>
<Button fx:id="selectOutputsButton" maxWidth="1.7976931348623157E308" mnemonicParsing="false"
text="Select Outputs..." GridPane.columnIndex="1" GridPane.halignment="CENTER"
GridPane.rowIndex="10"/>
GridPane.rowIndex="11"/>
<Label alignment="CENTER_RIGHT" text="Enabled Chips" GridPane.halignment="RIGHT" GridPane.rowIndex="8"/>
<ChoiceBox fx:id="enabledChipsChoiceBox" maxWidth="1.7976931348623157E308" prefWidth="150.0"
GridPane.columnIndex="1" GridPane.rowIndex="8"/>
<Button fx:id="selectChipsButton" maxWidth="1.7976931348623157E308" mnemonicParsing="false"
text="Select Chips" GridPane.columnIndex="2" GridPane.rowIndex="8"/>
</children>
<rowConstraints>
<RowConstraints minHeight="10.0"/>
Expand All @@ -147,6 +152,7 @@
<RowConstraints minHeight="10.0"/>
<RowConstraints minHeight="10.0"/>
<RowConstraints minHeight="10.0"/>
<RowConstraints minHeight="10.0" prefHeight="30.0"/>
<RowConstraints minHeight="10.0"/>
<RowConstraints minHeight="10.0"/>
<RowConstraints minHeight="10.0"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* 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-color/
*/

package ij_plugins.color.ui.calibration

import enumeratum.{Enum, EnumEntry}

import scala.collection.immutable

sealed abstract class ChipsEnabledType(override val entryName: String) extends EnumEntry {
override def toString: String = entryName
}

case object ChipsEnabledType extends Enum[ChipsEnabledType] {

case object All extends ChipsEnabledType("All")

case object Auto extends ChipsEnabledType("Auto")

case object Custom extends ChipsEnabledType("Custom")

override val values: immutable.IndexedSeq[ChipsEnabledType] = findValues
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ class ColorCalibratorUIController(
private val chartInfoLabel: Label,
private val editChartButton: Button,
private val marginsSpinner: Spinner[java.lang.Integer],
private val enabledChipsChoiceBox: ChoiceBox[ChipsEnabledType],
private val selectChipsButton: Button,
private val referenceColorSpaceChoiceBox: ChoiceBox[ReferenceColorSpace],
private val mappingMethodChoiceBox: ChoiceBox[MappingMethod],
private val suggestCalibrationOptionsButton: Button,
Expand Down Expand Up @@ -86,6 +88,9 @@ class ColorCalibratorUIController(
chartTypeChoiceBox.selectionModel().select(newValue)
}

editChartButton.onAction = _ => model.onEditChart()
editChartButton.disable <== model.referenceChartType =!= ColorChartType.Custom

renderReferenceChartSplitButton.onAction = _ => model.onRenderReferenceChart()
renderReferenceChartSplitButton.items = List(
new MenuItem("Reference Colors") {
Expand All @@ -95,9 +100,6 @@ class ColorCalibratorUIController(

renderReferenceChartSplitButton.disable <== !model.referenceChartDefined

editChartButton.onAction = _ => model.onEditChart()
editChartButton.disable <== !model.referenceChartEditEnabled

chartInfoLabel.text <== model.chartInfoText

// Actual chart
Expand All @@ -106,6 +108,27 @@ class ColorCalibratorUIController(
value <==> model.chipMarginPercent
}

// Enabled chips type choice
enabledChipsChoiceBox.items = ObservableBuffer.from(ChipsEnabledType.values)
// Update model when UI changed
private val enabledChipsIsChanging = new AtomicBoolean(false)
enabledChipsChoiceBox.selectionModel().selectedItem.onChange { (_, oldValue, newValue) =>
enabledChipsIsChanging.synchronized {
if (!enabledChipsIsChanging.getAndSet(true)) {
model.enabledChipsType.value = newValue
}
enabledChipsIsChanging.set(false)
}
}
enabledChipsChoiceBox.selectionModel().selectFirst()
// Update UI when enabledChipsType changed
model.enabledChipsType.onChange { (_, _, newValue) =>
enabledChipsChoiceBox.selectionModel().select(newValue)
}

selectChipsButton.onAction = _ => model.onSelectEnabledChips()
selectChipsButton.disable <== (model.enabledChipsType =!= ChipsEnabledType.Custom) or !model.referenceChartDefined

selectOutputsButton.onAction = _ => model.onSelectOutputs()

// Calibration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import ij_plugins.color.calibration.regression.MappingMethod
import ij_plugins.color.calibration.{CorrectionRecipe, renderReferenceChart}
import ij_plugins.color.ui.calibration.tasks.CalibrateTask.OutputConfig
import ij_plugins.color.ui.calibration.tasks._
import ij_plugins.color.ui.util.LiveChartROI
import ij_plugins.color.ui.util.{IJPrefs, LiveChartROI}
import javafx.beans.property.ReadOnlyBooleanProperty
import org.scalafx.extras.mvcfx.ModelFX
import org.scalafx.extras.{BusyWorker, ShowMessage, onFX}
Expand All @@ -51,20 +51,14 @@ object ColorCalibratorUIModel {
// We will use `null` to indicate missing values from Java API

for {
referenceColorSpaceName <-
Option(Prefs.get(ReferencePrefix + ".referenceColorSpace", null.asInstanceOf[String]))
referenceColorSpaceName <- IJPrefs.getStringOption(ReferencePrefix + ".referenceColorSpace")
referenceColorSpace <- ReferenceColorSpace.withNameOption(referenceColorSpaceName)

mappingMethodName <- Option(Prefs.get(ReferencePrefix + ".mappingMethod", null.asInstanceOf[String]))
mappingMethodName <- IJPrefs.getStringOption(ReferencePrefix + ".mappingMethod")
mappingMethod <- MappingMethod.withNameOption(mappingMethodName)

colorChartTypeName <- Option(Prefs.get(ReferencePrefix + ".colorChartType", null.asInstanceOf[String]))
colorChartTypeName <- IJPrefs.getStringOption(ReferencePrefix + ".colorChartType")
colorChartType <- ColorChartType.withNameOption(colorChartTypeName)

chipMargin <- Option(Prefs.get(ReferencePrefix + ".chipMargin", null.asInstanceOf[Double]))
// TODO: Implement reading of outputConfig
// outputConfig <- Option(Prefs.get(ReferencePrefix + ".showExtraInfo", null.asInstanceOf[Boolean]))
outputConfig <- Option(OutputConfig())
chipMargin <- IJPrefs.getDoubleOption(ReferencePrefix + ".chipMargin")
outputConfig <- OutputConfig.loadFromIJPref()
} yield Config(
referenceColorSpace,
mappingMethod,
Expand All @@ -90,8 +84,7 @@ object ColorCalibratorUIModel {
Prefs.set(ReferencePrefix + ".mappingMethod", mappingMethod.entryName)
Prefs.set(ReferencePrefix + ".colorChartType", colorChartType.entryName)
Prefs.set(ReferencePrefix + ".chipMargin", s"$chipMargin")
// TODO: Implement writing of outputConfig
// Prefs.set(ReferencePrefix + ".showExtraInfo", s"$showExtraInfo")
outputConfig.saveToIJPref()
}

}
Expand All @@ -113,6 +106,10 @@ class ColorCalibratorUIModel(val image: ImagePlus, parentWindow: Window) extends
// Parameters defining chart, beside ROI that will be handled by `liveChartROI`
val referenceChartType = new ObjectProperty[ColorChartType](this, "chart", ColorChartType.XRitePassportColorChecker)
referenceChartType.onChange { (_, _, _) =>
if (enabledChipsType.value == ChipsEnabledType.Custom) {
// Chart changed, so custom chip selection got invalid, assume that "All" are selected
enabledChipsType.value = ChipsEnabledType.All
}
recreateReferenceChart()
}

Expand All @@ -124,9 +121,6 @@ class ColorCalibratorUIModel(val image: ImagePlus, parentWindow: Window) extends
private val referenceChartDefinedWrapper = new ReadOnlyBooleanWrapper()
val referenceChartDefined: ReadOnlyBooleanProperty = referenceChartDefinedWrapper.readOnlyProperty

private val referenceChartEditEnabledWrapper = new ReadOnlyBooleanWrapper()
val referenceChartEditEnabled: ReadOnlyBooleanProperty = referenceChartEditEnabledWrapper.readOnlyProperty

private var customChartOption: Option[GridColorChart] = None

val chartInfoText = new StringProperty("???")
Expand All @@ -136,6 +130,12 @@ class ColorCalibratorUIModel(val image: ImagePlus, parentWindow: Window) extends
recreateReferenceChart()
}

// Parameters defining chart, beside ROI that will be handled by `liveChartROI`
val enabledChipsType = new ObjectProperty[ChipsEnabledType](this, "chipsEnabledType", ChipsEnabledType.All)
enabledChipsType.onChange { (_, _, _) =>
recreateReferenceChart()
}

private val outputConfigWrapper = new ReadOnlyObjectWrapper[OutputConfig](this, "outputConfig", OutputConfig())

def outputConfig: OutputConfig = outputConfigWrapper.value
Expand Down Expand Up @@ -164,20 +164,32 @@ class ColorCalibratorUIModel(val image: ImagePlus, parentWindow: Window) extends
recreateReferenceChart()

def recreateReferenceChart(): Unit = {
val chartOpt =
val newChartOpt =
(
if (referenceChartType.value != ColorChartType.Custom) {
ColorCharts.withColorChartType(referenceChartType.value)
} else {
if (referenceChartType.value == ColorChartType.Custom) {
customChartOption
} else {
ColorCharts.withColorChartType(referenceChartType.value)
}
).map(c => c.copyWithNewChipMargin(chipMarginPercent.value / 100d))

referenceChartOptionWrapper.value = chartOpt
// Transfer enabled chips if chart are defines and we have custom chip enabled
val newChartOpt2 =
for {
currentChart <- referenceChartOptionWrapper.value
newChart <- newChartOpt
if enabledChipsType.value == ChipsEnabledType.Custom
} yield {
newChart.copyWithEnableChips(currentChart.enabled.toArray)
}

referenceChartOptionWrapper.value = if (newChartOpt2.isDefined) newChartOpt2 else newChartOpt

referenceChartDefinedWrapper.value = referenceChartOptionWrapper.value.isDefined

referenceChartEditEnabledWrapper.value = referenceChartType.value == ColorChartType.Custom
if (referenceChartType.value == ColorChartType.Custom) {
customChartOption = if (newChartOpt2.isDefined) newChartOpt2 else newChartOpt
}

chartInfoText.value =
referenceChartOptionWrapper.value
Expand All @@ -187,16 +199,15 @@ class ColorCalibratorUIModel(val image: ImagePlus, parentWindow: Window) extends

private def currentChart: GridColorChart = {
liveChartROI.locatedChart.value match {
case Some(c) => c match {
case gcc: GridColorChart =>
gcc
case x =>
throw new IllegalStateException(
s"Internal error. Unexpected class type. Expecting ${classOf[GridColorChart]}, got ${x.getClass}"
)
}
case None => throw new IllegalStateException(s"Internal error. Option is empty.")

case Some(c) =>
referenceChartOption.value match {
case Some(rc) =>
rc.copyWith(c.alignmentTransform)
case None =>
throw new IllegalStateException(s"Internal error. referenceChartOptiont Option is empty.")
}
case None =>
throw new IllegalStateException(s"Internal error. liveChartROI.locatedChart Option is empty.")
}
}

Expand Down Expand Up @@ -261,6 +272,31 @@ class ColorCalibratorUIModel(val image: ImagePlus, parentWindow: Window) extends
}
}

def onSelectEnabledChips(): Unit =
referenceChartOption.value match {
case Some(chart) =>
busyWorker.doTask("onSelectEnabledChips") {
new SelectEnabledChipsTask(chart, Option(parentWindow)) {
override def onFinish(result: Future[Option[GridColorChart]], successful: Boolean): Unit = {
if (successful) onFX {
// Update charts
val newChartOpt = result.get()
if (newChartOpt.isDefined) {
if (referenceChartType.value == ColorChartType.Custom) {
customChartOption = newChartOpt
}
referenceChartOptionWrapper.value = newChartOpt
recreateReferenceChart()
}
}
}
}
}

case None =>
showError(Title, "Cannot select which chips are enabled.", "Chart is not defined.")
}

def onSuggestCalibrationOptions(): Unit = busyWorker.doTask("onSuggestCalibrationOptions") {
new SuggestCalibrationOptionsTask(currentChart, image, Option(parentWindow))
}
Expand Down
Loading

0 comments on commit cbda651

Please sign in to comment.