diff --git a/numass-core/src/main/kotlin/inr/numass/data/storage/NumassDataLoader.kt b/numass-core/src/main/kotlin/inr/numass/data/storage/NumassDataLoader.kt index 82b29fe7..40656487 100644 --- a/numass-core/src/main/kotlin/inr/numass/data/storage/NumassDataLoader.kt +++ b/numass-core/src/main/kotlin/inr/numass/data/storage/NumassDataLoader.kt @@ -15,6 +15,7 @@ */ package inr.numass.data.storage +import hep.dataforge.context.Context import hep.dataforge.exceptions.StorageException import hep.dataforge.io.ColumnedDataReader import hep.dataforge.io.envelopes.Envelope @@ -23,6 +24,7 @@ import hep.dataforge.meta.MetaBuilder import hep.dataforge.providers.Provider import hep.dataforge.storage.api.ObjectLoader import hep.dataforge.storage.api.Storage +import hep.dataforge.storage.commons.DummyStorage import hep.dataforge.storage.filestorage.FileStorage import hep.dataforge.storage.loaders.AbstractLoader import hep.dataforge.tables.Table @@ -30,7 +32,6 @@ import inr.numass.data.api.NumassPoint import inr.numass.data.api.NumassSet import inr.numass.data.legacy.NumassFileEnvelope import org.slf4j.LoggerFactory - import java.io.IOException import java.nio.file.Files import java.nio.file.Path @@ -170,6 +171,10 @@ class NumassDataLoader( return NumassDataLoader(storage, name, annotation, items) } + fun fromDir(context: Context, directory: Path, name: String = FileStorage.entryName(directory)): NumassDataLoader { + return fromDir(DummyStorage(context), directory, name) + } + /** * "start_time": "2016-04-20T04:08:50", * diff --git a/numass-viewer/src/main/kotlin/inr/numass/viewer/AmplitudeView.kt b/numass-viewer/src/main/kotlin/inr/numass/viewer/AmplitudeView.kt index 16ac0f3e..0ccac565 100644 --- a/numass-viewer/src/main/kotlin/inr/numass/viewer/AmplitudeView.kt +++ b/numass-viewer/src/main/kotlin/inr/numass/viewer/AmplitudeView.kt @@ -7,16 +7,13 @@ import hep.dataforge.fx.ui import hep.dataforge.goals.Goal import hep.dataforge.kodex.configure import hep.dataforge.kodex.toList -import hep.dataforge.meta.Meta import hep.dataforge.plots.PlotFrame import hep.dataforge.plots.PlotGroup import hep.dataforge.plots.Plottable import hep.dataforge.plots.data.DataPlot import hep.dataforge.plots.jfreechart.JFreeChartFrame import hep.dataforge.tables.Adapters -import hep.dataforge.tables.Table import inr.numass.data.analyzers.NumassAnalyzer -import inr.numass.data.analyzers.SimpleAnalyzer import inr.numass.data.analyzers.withBinning import inr.numass.data.api.MetaBlock import inr.numass.data.api.NumassBlock @@ -32,12 +29,8 @@ import javafx.scene.control.CheckBox import javafx.scene.control.ChoiceBox import javafx.scene.image.ImageView import tornadofx.* -import java.util.concurrent.ConcurrentHashMap -class AmplitudeView( - private val analyzer: NumassAnalyzer = SimpleAnalyzer(), - private val cache: MutableMap = ConcurrentHashMap() -) : View(title = "Numass amplitude spectrum plot", icon = ImageView(dfIcon)) { +class AmplitudeView : View(title = "Numass amplitude spectrum plot", icon = ImageView(dfIcon)) { private val frame: PlotFrame = JFreeChartFrame().configure { "title" to "Detector response plot" @@ -116,13 +109,6 @@ class AmplitudeView( center = container.root } - /** - * Calculate or get spectrum from the immutable - */ - private fun getSpectrum(point: NumassBlock): Table { - return cache.computeIfAbsent(point) { analyzer.getAmplitudeSpectrum(point, Meta.empty()) } - } - /** * Put or replace current plot with name `key` */ @@ -138,7 +124,7 @@ class AmplitudeView( * Distinct map of channel number to corresponding grouping block */ private fun NumassPoint.getChannels(): Map { - return blocks.toList().groupBy { it.channel ?: 0 }.mapValues { entry -> + return blocks.toList().groupBy { it.channel }.mapValues { entry -> if (entry.value.size == 1) { entry.value.first() } else { @@ -164,7 +150,7 @@ class AmplitudeView( DataPlot.plot( key, adapter, - getSpectrum(point).withBinning(binning) + PointCache[point].withBinning(binning) ) } else { val group = PlotGroup.typed(key) @@ -172,7 +158,7 @@ class AmplitudeView( val plot = DataPlot.plot( key.toString(), adapter, - getSpectrum(block).withBinning(binning) + PointCache[point].withBinning(binning) ) group.add(plot) } diff --git a/numass-viewer/src/main/kotlin/inr/numass/viewer/MainView.kt b/numass-viewer/src/main/kotlin/inr/numass/viewer/MainView.kt new file mode 100644 index 00000000..d18606b1 --- /dev/null +++ b/numass-viewer/src/main/kotlin/inr/numass/viewer/MainView.kt @@ -0,0 +1,200 @@ +package inr.numass.viewer + +import hep.dataforge.context.Context +import hep.dataforge.context.Global +import hep.dataforge.fx.* +import hep.dataforge.fx.fragments.LogFragment +import hep.dataforge.storage.commons.StorageManager +import inr.numass.NumassProperties +import inr.numass.data.api.NumassPoint +import inr.numass.data.legacy.NumassFileEnvelope +import inr.numass.data.storage.NumassDataLoader +import inr.numass.data.storage.NumassStorageFactory +import javafx.beans.property.SimpleObjectProperty +import javafx.geometry.Insets +import javafx.scene.control.Alert +import javafx.scene.layout.Priority +import javafx.scene.text.Font +import javafx.stage.DirectoryChooser +import javafx.stage.FileChooser +import kotlinx.coroutines.experimental.async +import org.controlsfx.control.StatusBar +import tornadofx.* +import java.io.File +import java.nio.file.Files +import java.nio.file.Path + +class MainView(val context: Context = Global.getContext("viewer")) : View(title = "Numass viewer", icon = dfIconView) { + + private val statusBar = StatusBar(); + private val logFragment = LogFragment().apply { + addLogHandler(context.logger) + } + + private val pathProperty = SimpleObjectProperty() + private var path: Path by pathProperty + + val contentViewProperty = SimpleObjectProperty() + var contentView: UIComponent? by contentViewProperty + + + override val root = borderpane { + prefHeight = 600.0 + prefWidth = 800.0 + top { + toolbar { + prefHeight = 40.0 + button("Load directory") { + action { + val chooser = DirectoryChooser() + chooser.title = "Select directory to view" + val homeDir = NumassProperties.getNumassProperty("numass.viewer.lastPath") + try { + if (homeDir == null) { + chooser.initialDirectory = File(".").absoluteFile + } else { + chooser.initialDirectory = File(homeDir) + } + } catch (ex: Exception) { + NumassProperties.setNumassProperty("numass.viewer.lastPath", null) + } + + val rootDir = chooser.showDialog(primaryStage.scene.window) + + if (rootDir != null) { + NumassProperties.setNumassProperty("numass.viewer.lastPath", rootDir.absolutePath) + async { + runLater { + path = rootDir.toPath() + } + load(rootDir.toPath()) + } + } + } + } + button("Load file") { + action { + val chooser = FileChooser() + chooser.title = "Select file to view" + val homeDir = NumassProperties.getNumassProperty("numass.viewer.lastPath") + try { + if (homeDir == null) { + chooser.initialDirectory = File(".").absoluteFile + } else { + chooser.initialDirectory = File(homeDir) + } + } catch (ex: Exception) { + NumassProperties.setNumassProperty("numass.viewer.lastPath", null) + } + + val file = chooser.showOpenDialog(primaryStage.scene.window) + if (file != null) { + NumassProperties.setNumassProperty("numass.viewer.lastPath", file.parentFile.absolutePath) + async { + runLater { + path = file.toPath() + } + load(file.toPath()) + } + } + } + } + + label(pathProperty.asString()) { + padding = Insets(0.0, 0.0, 0.0, 10.0); + font = Font.font("System Bold", 13.0); + } + pane { + hgrow = Priority.ALWAYS + } + togglebutton("Console") { + isSelected = false + logFragment.bindWindow(this@togglebutton) + } + } + } + bottom = statusBar + } + + init { + contentViewProperty.onChange { + root.center = it?.root + } + } + + private suspend fun load(path: Path) { + runLater { + contentView = null + } + if (Files.isDirectory(path)) { + if (Files.exists(path.resolve(NumassDataLoader.META_FRAGMENT_NAME))) { + //build set view + runGoal("viewer.load.set[$path]") { + title = "Load set ($path)" + message = "Building numass set..." + NumassDataLoader.fromDir(context, path) + } ui { + contentView = SpectrumView().apply { + add(it.name, it) + } + } except { + alert( + type = Alert.AlertType.ERROR, + header = "Error during set loading", + content = it.toString() + ).show() + } + } else { + //build storage + runGoal("viewer.load.storage[$path]") { + title = "Load storage ($path)" + message = "Building numass storage tree..." + StorageManager.buildStorage( + context, + NumassStorageFactory.buildStorageMeta(path.toUri(), true, false) + ) + } ui { + contentView = StorageView(it) + } except { + alert( + type = Alert.AlertType.ERROR, + header = "Error during storage loading", + content = it.toString() + ).show() + } + } + } else { + //Reading individual file + val envelope = try { + NumassFileEnvelope.open(path,true) + } catch (ex: Exception){ + runLater { + alert( + type = Alert.AlertType.ERROR, + header = "Can't load DF envelope from file $path", + content = ex.toString() + ).show() + } + null + } + + envelope?.let { + if(it.meta.hasMeta("external_meta")){ + //try to read as point + val point = NumassPoint.read(it) + runLater { + contentView = AmplitudeView().apply { + add(path.toString(), point) + } + } + } else { + alert( + type = Alert.AlertType.ERROR, + header = "Unknown envelope content: $path" + ).show() + } + } + } + } + +} diff --git a/numass-viewer/src/main/kotlin/inr/numass/viewer/PointInfoView.kt b/numass-viewer/src/main/kotlin/inr/numass/viewer/PointInfoView.kt new file mode 100644 index 00000000..9c6680fd --- /dev/null +++ b/numass-viewer/src/main/kotlin/inr/numass/viewer/PointInfoView.kt @@ -0,0 +1,35 @@ +package inr.numass.viewer + +import hep.dataforge.fx.meta.MetaViewer +import inr.numass.data.analyzers.NumassAnalyzer +import inr.numass.data.api.NumassPoint +import tornadofx.* +import tornadofx.controlsfx.borders + +class PointInfoView(val point: NumassPoint) : MetaViewer(point.meta) { + private val count: Int by lazy { + PointCache[point].sumBy { it.getValue(NumassAnalyzer.COUNT_KEY).int } + } + + override val root = super.root.apply { + top { + gridpane { + borders { + lineBorder().build() + } + row { + hbox { + label("Total number of events: ") + label("$count") + } + } + row { + hbox { + label("Total count rate: ") + label(String.format("%.2f", count.toDouble() / point.length.toMillis() * 1000)) + } + } + } + } + } +} diff --git a/numass-viewer/src/main/kotlin/inr/numass/viewer/SpectrumView.kt b/numass-viewer/src/main/kotlin/inr/numass/viewer/SpectrumView.kt index ec6dae3a..1383f470 100644 --- a/numass-viewer/src/main/kotlin/inr/numass/viewer/SpectrumView.kt +++ b/numass-viewer/src/main/kotlin/inr/numass/viewer/SpectrumView.kt @@ -5,16 +5,11 @@ import hep.dataforge.fx.plots.PlotContainer import hep.dataforge.fx.runGoal import hep.dataforge.fx.ui import hep.dataforge.kodex.configure -import hep.dataforge.meta.Meta import hep.dataforge.plots.PlotFrame import hep.dataforge.plots.data.DataPlot import hep.dataforge.plots.jfreechart.JFreeChartFrame import hep.dataforge.tables.Adapters -import hep.dataforge.tables.Table -import inr.numass.data.analyzers.NumassAnalyzer -import inr.numass.data.analyzers.SimpleAnalyzer import inr.numass.data.analyzers.countInWindow -import inr.numass.data.api.NumassPoint import inr.numass.data.api.NumassSet import javafx.beans.property.SimpleIntegerProperty import javafx.collections.FXCollections @@ -26,7 +21,6 @@ import javafx.scene.image.ImageView import javafx.util.converter.NumberStringConverter import org.controlsfx.control.RangeSlider import tornadofx.* -import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.AtomicInteger import java.util.stream.Collectors @@ -35,10 +29,7 @@ import java.util.stream.Collectors * @param analyzer * @param cache - optional global point immutable */ -class SpectrumView( - val analyzer: NumassAnalyzer = SimpleAnalyzer(), - val cache: MutableMap = ConcurrentHashMap() -) : View(title = "Numass spectrum plot", icon = ImageView(dfIcon)) { +class SpectrumView : View(title = "Numass spectrum plot", icon = ImageView(dfIcon)) { private val frame: PlotFrame = JFreeChartFrame().configure { "xAxis.title" to "U" @@ -119,11 +110,6 @@ class SpectrumView( } } - private fun getSpectrum(point: NumassPoint): Table { - return cache.computeIfAbsent(point) { analyzer.getAmplitudeSpectrum(point, Meta.empty()) } - - } - private fun updateView() { runLater { container.progress = 0.0 } val progress = AtomicInteger(0) @@ -134,7 +120,7 @@ class SpectrumView( runGoal("spectrumData[$name]") { set.points.map { point -> - val count = getSpectrum(point).countInWindow(loChannel.toShort(), upChannel.toShort()); + val count = PointCache[point].countInWindow(loChannel.toShort(), upChannel.toShort()); val seconds = point.length.toMillis() / 1000.0; runLater { container.progress = progress.incrementAndGet().toDouble() / totalProgress diff --git a/numass-viewer/src/main/kotlin/inr/numass/viewer/StorageView.kt b/numass-viewer/src/main/kotlin/inr/numass/viewer/StorageView.kt index 55807750..e9fa1555 100644 --- a/numass-viewer/src/main/kotlin/inr/numass/viewer/StorageView.kt +++ b/numass-viewer/src/main/kotlin/inr/numass/viewer/StorageView.kt @@ -1,55 +1,25 @@ package inr.numass.viewer -import hep.dataforge.context.Context -import hep.dataforge.context.Global -import hep.dataforge.fx.* -import hep.dataforge.fx.fragments.LogFragment +import hep.dataforge.fx.dfIconView import hep.dataforge.fx.meta.MetaViewer +import hep.dataforge.fx.runGoal import hep.dataforge.meta.Metoid import hep.dataforge.storage.api.Loader import hep.dataforge.storage.api.Storage import hep.dataforge.storage.api.TableLoader -import hep.dataforge.storage.commons.StorageManager -import hep.dataforge.tables.Table -import inr.numass.NumassProperties import inr.numass.data.api.NumassPoint import inr.numass.data.api.NumassSet import inr.numass.data.storage.NumassDataLoader -import inr.numass.data.storage.NumassStorageFactory import javafx.beans.property.SimpleBooleanProperty -import javafx.beans.property.SimpleObjectProperty -import javafx.beans.property.SimpleStringProperty -import javafx.geometry.Insets -import javafx.scene.control.Alert import javafx.scene.control.ContextMenu import javafx.scene.control.TreeItem -import javafx.scene.image.ImageView -import javafx.scene.layout.Priority -import javafx.scene.text.Font -import javafx.stage.DirectoryChooser -import org.controlsfx.control.StatusBar import tornadofx.* -import java.io.File -import java.net.URI -import java.util.concurrent.ConcurrentHashMap import kotlin.streams.toList -class StorageView(private val context: Context = Global) : View(title = "Numass storage", icon = ImageView(dfIcon)) { +class StorageView(val storage: Storage) : View(title = "Numass storage", icon = dfIconView) { - - val storageProperty = SimpleObjectProperty() - var storage by storageProperty - - - private val storageNameProperty = SimpleStringProperty("") - private var storageName by storageNameProperty - - private val statusBar = StatusBar(); - - private val cache: MutableMap = ConcurrentHashMap() - - private val ampView: AmplitudeView by inject(params = mapOf("cache" to cache)); - private val spectrumView: SpectrumView by inject(params = mapOf("cache" to cache)); + private val ampView: AmplitudeView by inject(); + private val spectrumView: SpectrumView by inject(); private val hvView: HVView by inject(); private val scView: SlowControlView by inject(); @@ -57,6 +27,14 @@ class StorageView(private val context: Context = Global) : View(title = "Numass val checkedProperty = SimpleBooleanProperty(false) var checked by checkedProperty + val infoView: UIComponent? by lazy { + when (content) { + is NumassPoint -> PointInfoView(content) + is Metoid -> MetaViewer(content.meta, title = "Meta view: $id") + else -> null + } + } + init { checkedProperty.onChange { selected -> when (content) { @@ -90,7 +68,10 @@ class StorageView(private val context: Context = Global) : View(title = "Numass val children: List? by lazy { when (content) { is Storage -> (content.shelves().sorted() + content.loaders().sorted()).map { buildContainer(it, this) } - is NumassSet -> content.points.map { buildContainer(it, this) }.toList().sortedBy { it.id } + is NumassSet -> content.points + .sorted(compareBy { it.index }) + .map { buildContainer(it, this) } + .toList() else -> null } } @@ -100,158 +81,87 @@ class StorageView(private val context: Context = Global) : View(title = "Numass } - override val root = borderpane { - top { - toolbar { - prefHeight = 40.0 - button("Load") { - action { - val chooser = DirectoryChooser() - chooser.title = "Select numass storage root" - val storageRoot = NumassProperties.getNumassProperty("numass.storage.root") - try { - if (storageRoot == null) { - chooser.initialDirectory = File(".").absoluteFile - } else { - chooser.initialDirectory = File(storageRoot) - } - } catch (ex: Exception) { - NumassProperties.setNumassProperty("numass.storage.root", null) - } - val rootDir = chooser.showDialog(primaryStage.scene.window) - - if (rootDir != null) { - NumassProperties.setNumassProperty("numass.storage.root", rootDir.absolutePath) - loadDirectory(rootDir.toURI()) - } - } - } - label(storageNameProperty) { - padding = Insets(0.0, 0.0, 0.0, 10.0); - font = Font.font("System Bold", 13.0); - } - pane { - hgrow = Priority.ALWAYS - } - togglebutton("Console") { - isSelected = false - LogFragment().apply { - addLogHandler(context.logger) - bindWindow(this@togglebutton) - } - } + override val root = splitpane { + treeview { + //isShowRoot = false + root = TreeItem(Container(storage.name, storage)) + root.isExpanded = true + runGoal("viewer.storage.populateTree") { + populate { parent -> parent.value.children } } - - } - center { - splitpane { - treeview { - //isShowRoot = false - storageProperty.onChange { - if (it != null) { - root = TreeItem(Container(it.name, it)) - root.isExpanded = true - runGoal("populateTree") { - runLater { statusBar.progress = -1.0 } - populate { parent -> - val value = parent.value.content - when (value) { - is Storage -> (value.shelves().sorted() + value.loaders().sorted()).map { buildContainer(it, parent.value) } - is NumassSet -> value.points.map { buildContainer(it, parent.value) }.toList().sortedBy { it.id } - else -> null - } - } - runLater { statusBar.progress = 0.0 } - } - - /* - lazyPopulate( leafCheck = { it.value.hasChildren }) { - runLater { statusBar.progress = -1.0 } - it.value.children.also { - runLater { statusBar.progress = 0.0 } - } - } - */ + cellFormat { value -> + when (value.content) { + is Storage -> { + text = value.content.name + graphic = null + } + is NumassSet -> { + text = null + graphic = checkbox(value.content.name).apply { + selectedProperty().bindBidirectional(value.checkedProperty) } } - cellFormat { value -> - when (value.content) { - is Storage -> { - text = value.content.name - graphic = null - } - is NumassSet -> { - text = null - graphic = checkbox(value.content.name).apply { - selectedProperty().bindBidirectional(value.checkedProperty) - } - } - is NumassPoint -> { - text = null - graphic = checkbox("${value.content.voltage}[${value.content.index}]").apply { - selectedProperty().bindBidirectional(value.checkedProperty) - } - } - is TableLoader -> { - text = null - graphic = checkbox(value.content.name).apply { - selectedProperty().bindBidirectional(value.checkedProperty) - } - } - else -> { - text = value.id - graphic = null - } + is NumassPoint -> { + text = null + graphic = checkbox("${value.content.voltage}[${value.content.index}]").apply { + selectedProperty().bindBidirectional(value.checkedProperty) } - contextMenu = ContextMenu() - contextMenu.item("Clear all") { + } + is TableLoader -> { + text = null + graphic = checkbox(value.content.name).apply { + selectedProperty().bindBidirectional(value.checkedProperty) + } + } + else -> { + text = value.id + graphic = null + } + } + contextMenu = ContextMenu().apply { + item("Clear all") { + action { + this@cellFormat.treeItem.uncheckAll() + } + } + value.infoView?.let { + item("Info") { action { - this@cellFormat.treeItem.uncheckAll() + it.openModal(escapeClosesWindow = true) } } - if (value.content is Metoid) { - contextMenu.item("Meta") { - action { - openInternalWindow(MetaViewer(value.content.meta), escapeClosesWindow = true) - } - } - - } } } - - tabpane { - tab("Amplitude spectra") { - content = ampView.root - isClosable = false - //visibleWhen(ampView.isEmpty.not()) - } - tab("HV") { - content = hvView.root - isClosable = false - //visibleWhen(hvView.isEmpty.not()) - } - tab("Numass spectra") { - content = spectrumView.root - isClosable = false - //visibleWhen(spectrumView.isEmpty.not()) - } - tab("Slow control") { - content = scView.root - isClosable = false - //visibleWhen(scView.isEmpty.not()) - } - } - setDividerPosition(0, 0.3); } } - - bottom = statusBar; - + tabpane { + tab("Amplitude spectra") { + content = ampView.root + isClosable = false + //visibleWhen(ampView.isEmpty.not()) + } + tab("HV") { + content = hvView.root + isClosable = false + //visibleWhen(hvView.isEmpty.not()) + } + tab("Numass spectra") { + content = spectrumView.root + isClosable = false + //visibleWhen(spectrumView.isEmpty.not()) + } + tab("Slow control") { + content = scView.root + isClosable = false + //visibleWhen(scView.isEmpty.not()) + } + } + setDividerPosition(0, 0.3); } + private fun TreeItem.uncheckAll() { this.value.checked = false this.children.forEach { it.uncheckAll() } @@ -280,21 +190,4 @@ class StorageView(private val context: Context = Global) : View(title = "Numass else -> throw IllegalArgumentException("Unknown content type: ${content::class.java}"); } - private fun loadDirectory(path: URI) { - statusBar.text = "Loading storage: $path" - runGoal("loadDirectory[$path]") { - title = "Load storage ($path)" - message = "Building numass storage tree..." - StorageManager.buildStorage(context, NumassStorageFactory.buildStorageMeta(path, true, false)) - } ui { - storage = it - storageName = "Storage: $path" - - statusBar.text = "OK" - } except { - alert(type = Alert.AlertType.ERROR, header = "Error during storage loading", content = it.toString()).show() - it.printStackTrace() - } - } - } diff --git a/numass-viewer/src/main/kotlin/inr/numass/viewer/Viewer.kt b/numass-viewer/src/main/kotlin/inr/numass/viewer/Viewer.kt index 19dedb9a..bf186cd1 100644 --- a/numass-viewer/src/main/kotlin/inr/numass/viewer/Viewer.kt +++ b/numass-viewer/src/main/kotlin/inr/numass/viewer/Viewer.kt @@ -4,6 +4,11 @@ import ch.qos.logback.classic.Level import ch.qos.logback.classic.Logger import hep.dataforge.context.Global import hep.dataforge.fx.dfIcon +import hep.dataforge.meta.Meta +import hep.dataforge.tables.Table +import hep.dataforge.utils.Misc +import inr.numass.data.analyzers.SimpleAnalyzer +import inr.numass.data.api.NumassPoint import javafx.stage.Stage import org.slf4j.LoggerFactory import tornadofx.* @@ -11,7 +16,7 @@ import tornadofx.* /** * Created by darksnake on 14-Apr-17. */ -class Viewer : App(StorageView::class) { +class Viewer : App(MainView::class) { init{ (LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME) as Logger).level = Level.INFO } @@ -25,4 +30,16 @@ class Viewer : App(StorageView::class) { super.stop() Global.terminate(); } +} + +/** + * Global point cache + */ +object PointCache{ + private val analyzer = SimpleAnalyzer() + private val cache: MutableMap = Misc.getLRUCache(1000) + + operator fun get(point: NumassPoint): Table { + return cache.computeIfAbsent(point) { analyzer.getAmplitudeSpectrum(point, Meta.empty()) } + } } \ No newline at end of file