Fix viewer cache

This commit is contained in:
Alexander Nozik 2021-11-14 16:46:32 +03:00
parent d3689638e0
commit 15d17f2cc4
28 changed files with 456 additions and 393 deletions

View File

@ -1,6 +1,6 @@
plugins {
kotlin("jvm") version "1.5.31"
id("org.openjfx.javafxplugin") version "0.0.9" apply false
id("org.openjfx.javafxplugin") version "0.0.10" apply false
id("com.github.johnrengelman.shadow") version "7.1.0" apply false
}
@ -12,7 +12,7 @@ allprojects {
repositories {
mavenCentral()
jcenter()
maven("https://oss.sonatype.org/content/repositories/snapshots")
}
dependencies {

View File

@ -1,126 +0,0 @@
/*
* The Alphanum Algorithm is an improved sorting algorithm for strings
* containing numbers. Instead of sorting numbers in ASCII order like
* a standard sort, this algorithm sorts numbers in numeric order.
*
* The Alphanum Algorithm is discussed at http://www.DaveKoelle.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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
package hep.dataforge.names;
import java.util.Comparator;
/**
* This is an updated version with enhancements made by Daniel Migowski,
* Andre Bogus, and David Koelle
* To use this class:
* Use the static "sort" method from the java.util.Collections class:
* Collections.sort(your list, new AlphanumComparator());
*/
public class AlphanumComparator implements Comparator<String> {
public static final AlphanumComparator INSTANCE = new AlphanumComparator();
private Comparator<String> comparator = new NaturalComparator();
public AlphanumComparator(Comparator<String> comparator) {
this.comparator = comparator;
}
public AlphanumComparator() {
}
private final boolean isDigit(char ch) {
return ch >= 48 && ch <= 57;
}
/**
* Length of string is passed in for improved efficiency (only need to calculate it once)
**/
private final String getChunk(String s, int slength, int marker) {
StringBuilder chunk = new StringBuilder();
char c = s.charAt(marker);
chunk.append(c);
marker++;
if (isDigit(c)) {
while (marker < slength) {
c = s.charAt(marker);
if (!isDigit(c))
break;
chunk.append(c);
marker++;
}
} else {
while (marker < slength) {
c = s.charAt(marker);
if (isDigit(c))
break;
chunk.append(c);
marker++;
}
}
return chunk.toString();
}
public int compare(String s1, String s2) {
int thisMarker = 0;
int thatMarker = 0;
int s1Length = s1.length();
int s2Length = s2.length();
while (thisMarker < s1Length && thatMarker < s2Length) {
String thisChunk = getChunk(s1, s1Length, thisMarker);
thisMarker += thisChunk.length();
String thatChunk = getChunk(s2, s2Length, thatMarker);
thatMarker += thatChunk.length();
// If both chunks contain numeric characters, sort them numerically
int result = 0;
if (isDigit(thisChunk.charAt(0)) && isDigit(thatChunk.charAt(0))) {
// Simple chunk comparison by length.
int thisChunkLength = thisChunk.length();
result = thisChunkLength - thatChunk.length();
// If equal, the first different number counts
if (result == 0) {
for (int i = 0; i < thisChunkLength; i++) {
result = thisChunk.charAt(i) - thatChunk.charAt(i);
if (result != 0) {
return result;
}
}
}
} else {
result = comparator.compare(thisChunk, thatChunk);
}
if (result != 0)
return result;
}
return s1Length - s2Length;
}
private static class NaturalComparator implements Comparator<String> {
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
}
}

View File

@ -6,6 +6,7 @@
package hep.dataforge.utils;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
@ -15,7 +16,7 @@ import java.util.concurrent.CancellationException;
* @author Alexander Nozik
*/
public class Misc {
public static final Charset UTF = Charset.forName("UTF-8");
public static final Charset UTF = StandardCharsets.UTF_8;
/**
* A synchronized lru cache

View File

@ -36,6 +36,7 @@ import hep.dataforge.values.Value
import hep.dataforge.values.ValueProvider
import hep.dataforge.values.ValueProvider.Companion.VALUE_TARGET
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.io.File
@ -47,7 +48,6 @@ import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.stream.Stream
import kotlin.collections.HashMap
import kotlin.collections.set
import kotlin.coroutines.CoroutineContext
import kotlin.streams.asSequence
@ -308,10 +308,12 @@ open class Context(
*/
@Throws(Exception::class)
override fun close() {
logger.info("Closing context: $name")
//detach all plugins
plugins.close()
if (started) {
coroutineContext.cancel()
dispatcher.shutdown()
}
}

View File

@ -133,15 +133,13 @@ object Global : Context("GLOBAL", null, Thread.currentThread().contextClassLoade
* @return
*/
@Synchronized
fun getContext(name: String): Context {
return contextRegistry
.findFirst { ctx -> ctx.name == name }
.orElseGet {
val ctx = Context(name)
contextRegistry.add(ctx)
ctx
}
}
fun getContext(name: String): Context = contextRegistry
.findFirst { ctx -> ctx.name == name }
.orElseGet {
val ctx = Context(name)
contextRegistry.add(ctx)
ctx
}
/**
* Close all contexts and terminate framework

View File

@ -0,0 +1,106 @@
/*
* The Alphanum Algorithm is an improved sorting algorithm for strings
* containing numbers. Instead of sorting numbers in ASCII order like
* a standard sort, this algorithm sorts numbers in numeric order.
*
* The Alphanum Algorithm is discussed at http://www.DaveKoelle.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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
package hep.dataforge.names
/**
* This is an updated version with enhancements made by Daniel Migowski,
* Andre Bogus, and David Koelle
* To use this class:
* Use the static "sort" method from the java.util.Collections class:
* Collections.sort(your list, new AlphanumComparator());
*/
object AlphanumComparator : Comparator<String> {
private val comparator: Comparator<String> = NaturalComparator()
private fun isDigit(ch: Char): Boolean {
return ch.code in 48..57
}
/**
* Length of string is passed in for improved efficiency (only need to calculate it once)
*/
private fun getChunk(s: String, slength: Int, marker: Int): String {
var modMarker = marker
val chunk = StringBuilder()
var c = s[modMarker]
chunk.append(c)
modMarker++
if (isDigit(c)) {
while (modMarker < slength) {
c = s[modMarker]
if (!isDigit(c)) break
chunk.append(c)
modMarker++
}
} else {
while (modMarker < slength) {
c = s[modMarker]
if (isDigit(c)) break
chunk.append(c)
modMarker++
}
}
return chunk.toString()
}
override fun compare(s1: String, s2: String): Int {
var thisMarker = 0
var thatMarker = 0
val s1Length = s1.length
val s2Length = s2.length
while (thisMarker < s1Length && thatMarker < s2Length) {
val thisChunk = getChunk(s1, s1Length, thisMarker)
thisMarker += thisChunk.length
val thatChunk = getChunk(s2, s2Length, thatMarker)
thatMarker += thatChunk.length
// If both chunks contain numeric characters, sort them numerically
var result = 0
if (isDigit(thisChunk[0]) && isDigit(thatChunk[0])) {
// Simple chunk comparison by length.
val thisChunkLength = thisChunk.length
result = thisChunkLength - thatChunk.length
// If equal, the first different number counts
if (result == 0) {
for (i in 0 until thisChunkLength) {
result = thisChunk[i] - thatChunk[i]
if (result != 0) {
return result
}
}
}
} else {
result = comparator.compare(thisChunk, thatChunk)
}
if (result != 0) return result
}
return s1Length - s2Length
}
private class NaturalComparator : Comparator<String> {
override fun compare(o1: String, o2: String): Int {
return o1.compareTo(o2)
}
}
}

View File

@ -126,7 +126,7 @@ interface Value : Serializable, Comparable<Value> {
return when (type) {
ValueType.NUMBER -> ValueUtils.NUMBER_COMPARATOR.compare(number, other.number)
ValueType.BOOLEAN -> boolean.compareTo(other.boolean)
ValueType.STRING -> AlphanumComparator.INSTANCE.compare(this.string, other.string)
ValueType.STRING -> AlphanumComparator.compare(this.string, other.string)
ValueType.TIME -> time.compareTo(other.time)
ValueType.NULL -> if (other.type == ValueType.NULL) 0 else -1
ValueType.BINARY -> binary.compareTo(other.binary)

View File

@ -70,6 +70,7 @@ class WorkspaceTest {
@BeforeClass
@JvmStatic
fun setup() {
counter.set(0)
val context = Global.getContext("TEST").apply {
load(CachePlugin::class.java, MetaBuilder().setValue("fileCache.enabled", false))
}

View File

@ -1,23 +0,0 @@
//apply plugin: 'org.openjfx.javafxplugin'
//
//javafx {
// modules = [ 'javafx.controls', 'javafx.web' ]
//}
description = "A tornadofx based kotlin library"
dependencies {
api project(':dataforge-plots')
api project(':dataforge-gui:dataforge-html')
api 'org.controlsfx:controlsfx:8.40.14'
api "no.tornado:tornadofx:1.7.19"
api 'no.tornado:tornadofx-controlsfx:0.1.1'
api group: 'org.fxmisc.richtext', name: 'richtextfx', version: '0.10.2'
api 'org.jetbrains.kotlinx:kotlinx-coroutines-javafx:1.5.0'
// optional dependency for JFreeChart
//compileOnly project(":dataforge-plots:plots-jfc")
}

View File

@ -0,0 +1,25 @@
plugins {
id("org.openjfx.javafxplugin")
}
javafx {
modules = listOf("javafx.controls", "javafx.web")
version = "11"
}
description = "A tornadofx based kotlin library"
dependencies {
api(project(":dataforge-plots"))
api(project(":dataforge-gui:dataforge-html"))
api("no.tornado:tornadofx:1.7.20")
api("org.controlsfx:controlsfx:11.1.0")
api("no.tornado:tornadofx-controlsfx:0.1.1")
api("org.fxmisc.richtext:richtextfx:0.10.7")
api("org.jetbrains.kotlinx:kotlinx-coroutines-javafx:1.5.2")
// optional dependency for JFreeChart
//compileOnly project(":dataforge-plots:plots-jfc")
}

View File

@ -2,5 +2,6 @@ description = "An html rendering core and HTML output"
dependencies {
api project(':dataforge-core')
api 'org.jetbrains.kotlinx:kotlinx-html-jvm:0.6.11'
// https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-html-jvm
api 'org.jetbrains.kotlinx:kotlinx-html-jvm:0.7.3'
}

View File

@ -1,13 +0,0 @@
//allprojects {
// apply plugin: 'org.openjfx.javafxplugin'
//
// javafx {
// modules = ['javafx.controls']
// }
//}
description = 'dataforge-plots'
dependencies {
api project(':dataforge-core')
}

View File

@ -0,0 +1,14 @@
plugins {
id("org.openjfx.javafxplugin")
}
javafx {
modules = listOf("javafx.controls")
version = "11"
}
description = "dataforge-plots"
dependencies {
api(project(":dataforge-core"))
}

View File

@ -33,11 +33,11 @@ import hep.dataforge.storage.MutableStorage
import hep.dataforge.storage.StorageElement
import hep.dataforge.storage.StorageElementType
import hep.dataforge.storage.StorageManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.runBlocking
import java.nio.file.*
import kotlin.streams.asSequence
import kotlin.streams.toList
/**
* An element of file storage with fixed path
@ -62,12 +62,12 @@ interface FileStorageElementType : StorageElementType, Named {
}
class FileStorage(
override val context: Context,
override val name: String,
override val meta: Meta,
override val path: Path,
override val parent: StorageElement? = null,
val type: FileStorageElementType
override val context: Context,
override val name: String,
override val meta: Meta,
override val path: Path,
override val parent: StorageElement? = null,
val type: FileStorageElementType,
) : MutableStorage, FileStorageElement {
private val _connectionHelper by lazy { ConnectionHelper(this) }
@ -79,15 +79,14 @@ class FileStorage(
override val children: Collection<StorageElement>
get() = synchronized(this) {
runBlocking {
if (!isInitialized) {
refresh()
}
get() = runBlocking(Dispatchers.IO) {
if (!isInitialized) {
refresh()
}
_children.values
}
override fun resolveType(meta: Meta): StorageElementType? {
val type = meta.optString(StorageManager.STORAGE_META_TYPE_KEY).nullable
return if (type == null) {
@ -104,14 +103,14 @@ class FileStorage(
(parent as? FileStorage)?.watchService ?: path.fileSystem.newWatchService()
}
//TODO actually watch for file change
//TODO actually watch for file change
override fun create(meta: Meta): StorageElement {
val path = path.resolve(meta.getString("path", meta.getString("name")))
return _children.getOrPut(path) {
resolveType(meta)
?.create(this, meta)
?: error("Can't resolve storage element type.")
?.create(this, meta)
?: error("Can't resolve storage element type.")
}
}
@ -127,7 +126,7 @@ class FileStorage(
launch {
if (!_children.contains(path)) {
type.read(context, path, this@FileStorage)?.let { _children[path] = it }
?: logger.debug("Could not resolve type for $path in $this")
?: logger.debug("Could not resolve type for $path in $this")
}
}
}.toList().joinAll()
@ -141,11 +140,14 @@ class FileStorage(
/**
* Resolve meta for given path if it is available. If directory search for file called meta or meta.df inside
*/
fun resolveMeta(path: Path, metaReader: (Path) -> Meta? = { EnvelopeType.infer(it)?.reader?.read(it)?.meta }): Meta? {
fun resolveMeta(
path: Path,
metaReader: (Path) -> Meta? = { EnvelopeType.infer(it)?.reader?.read(it)?.meta },
): Meta? {
return if (Files.isDirectory(path)) {
Files.list(path).asSequence()
.find { it.fileName.toString() == "meta.df" || it.fileName.toString() == "meta" }
?.let(metaReader)
.find { it.fileName.toString() == "meta.df" || it.fileName.toString() == "meta" }
?.let(metaReader)
} else {
metaReader(path)
}
@ -166,8 +168,10 @@ class FileStorage(
override val name: String = "hep.dataforge.storage.directory"
@ValueDefs(
ValueDef(key = "path", info = "The relative path to the shelf inside parent storage or absolute path"),
ValueDef(key = "name", required = true, info = "The name of the new storage. By default use last segment shelf name")
ValueDef(key = "path", info = "The relative path to the shelf inside parent storage or absolute path"),
ValueDef(key = "name",
required = true,
info = "The name of the new storage. By default use last segment shelf name")
)
override fun create(context: Context, meta: Meta, parent: StorageElement?): FileStorageElement {
val shelfName = meta.getString("name")
@ -192,7 +196,9 @@ class FileStorage(
override suspend fun read(context: Context, path: Path, parent: StorageElement?): FileStorageElement? {
val meta = resolveMeta(path)
val name = meta?.optString("name").nullable ?: path.fileName.toString()
val type = meta?.optString("type").nullable?.let { context.load<StorageManager>().getType(it) } as? FileStorageElementType
val type = meta?.optString("type").nullable?.let {
context.load<StorageManager>().getType(it)
} as? FileStorageElementType
return if (type == null || type is Directory) {
// Read path as directory if type not found and path is directory
if (Files.isDirectory(path)) {

View File

@ -1,11 +1,7 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
idea
kotlin("jvm")
}
repositories {
mavenCentral()
}
@ -13,7 +9,3 @@ repositories {
dependencies {
api(project(":dataforge-core"))
}
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "1.8"
}

View File

@ -28,7 +28,6 @@ import java.util.stream.Stream
*/
interface NumassPoint : Metoid, ParentBlock, Provider {
override val blocks: List<NumassBlock>
/**

View File

@ -1,30 +0,0 @@
apply plugin: 'application'
apply plugin: 'kotlin'
repositories {
mavenCentral()
}
//apply plugin: 'org.openjfx.javafxplugin'
//
//javafx {
// modules = [ 'javafx.controls' ]
//}
if (!hasProperty('mainClass')) {
ext.mainClass = 'inr.numass.viewer.Viewer'//"inr.numass.viewer.test.TestApp"
}
mainClassName = mainClass
version = "0.5.6"
description = "The viewer for numass data"
dependencies {
api project(':numass-core')
api project(':dataforge-plots:plots-jfc')
api project(':dataforge-gui')
}

View File

@ -0,0 +1,42 @@
plugins {
kotlin("jvm")
id("org.openjfx.javafxplugin")
application
}
javafx {
modules = listOf("javafx.controls", "javafx.web")
version = "11"
}
repositories {
mavenCentral()
}
application {
mainClass.set("inr.numass.viewer.Viewer")
}
version = "0.6.0"
description = "The viewer for numass data"
dependencies {
api(project(":numass-core"))
api(project(":dataforge-plots:plots-jfc"))
api(project(":dataforge-gui"))
}
application {
applicationDefaultJvmArgs = listOf(
"--add-exports=javafx.graphics/com.sun.glass.ui=ALL-UNNAMED",
"--add-opens=javafx.graphics/com.sun.javafx.css=ALL-UNNAMED",
"--add-opens=javafx.graphics/com.sun.javafx.scene=ALL-UNNAMED",
"--add-opens=javafx.graphics/com.sun.javafx.scene.traversal=ALL-UNNAMED",
"--add-opens=javafx.graphics/javafx.scene=ALL-UNNAMED",
"--add-opens=javafx.controls/com.sun.javafx.scene.control=ALL-UNNAMED",
"--add-opens=javafx.controls/com.sun.javafx.scene.control.behavior=ALL-UNNAMED",
"--add-opens=javafx.controls/javafx.scene.control.skin=ALL-UNNAMED",
"--add-exports=javafx.controls/com.sun.javafx.scene.control.inputmap=ALL-UNNAMED",
)
}

View File

@ -15,6 +15,7 @@ import hep.dataforge.plots.jfreechart.JFreeChartFrame
import hep.dataforge.tables.Adapters
import inr.numass.data.analyzers.NumassAnalyzer
import inr.numass.data.analyzers.withBinning
import inr.numass.data.api.NumassPoint
import javafx.beans.Observable
import javafx.beans.binding.DoubleBinding
import javafx.beans.property.SimpleBooleanProperty
@ -28,6 +29,8 @@ import tornadofx.*
class AmplitudeView : View(title = "Numass amplitude spectrum plot", icon = ImageView(dfIcon)) {
private val pointCache by inject<PointCache>()
private val frame = JFreeChartFrame().configure {
"title" to "Detector response plot"
node("xAxis") {
@ -70,7 +73,7 @@ class AmplitudeView : View(title = "Numass amplitude spectrum plot", icon = Imag
addToSideBar(0, binningSelector, normalizeSwitch)
}
private val data: ObservableMap<String, CachedPoint> = FXCollections.observableHashMap()
private val data: ObservableMap<String, NumassPoint> = FXCollections.observableHashMap()
private val plots: ObservableMap<String, Goal<Plottable>> = FXCollections.observableHashMap()
val isEmpty = booleanBinding(data) { isEmpty() }
@ -113,16 +116,16 @@ class AmplitudeView : View(title = "Numass amplitude spectrum plot", icon = Imag
/**
* Put or replace current plot with name `key`
*/
operator fun set(key: String, point: CachedPoint) {
operator fun set(key: String, point: NumassPoint) {
data[key] = point
}
fun addAll(data: Map<String, CachedPoint>) {
fun addAll(data: Map<String, NumassPoint>) {
this.data.putAll(data);
}
private fun invalidate() {
data.forEach { key, point ->
data.forEach { (key, point) ->
plots.getOrPut(key) {
runGoal<Plottable>("loadAmplitudeSpectrum_$key") {
val valueAxis = if (normalize) {
@ -132,7 +135,7 @@ class AmplitudeView : View(title = "Numass amplitude spectrum plot", icon = Imag
}
val adapter = Adapters.buildXYAdapter(NumassAnalyzer.CHANNEL_KEY, valueAxis)
val channels = point.channelSpectra.await()
val channels = pointCache.getChannelSpectra(key, point)
return@runGoal if (channels.size == 1) {
DataPlot.plot(
@ -186,7 +189,7 @@ class AmplitudeView : View(title = "Numass amplitude spectrum plot", icon = Imag
/**
* Set frame content to the given map. All keys not in the map are removed.
*/
fun setAll(map: Map<String, CachedPoint>) {
fun setAll(map: Map<String, NumassPoint>) {
plots.clear();
//Remove obsolete keys
data.keys.filter { !map.containsKey(it) }.forEach {

View File

@ -1,48 +0,0 @@
/*
* Copyright 2018 Alexander Nozik.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package inr.numass.viewer
import hep.dataforge.meta.Meta
import hep.dataforge.tables.Table
import inr.numass.data.analyzers.SimpleAnalyzer
import inr.numass.data.api.NumassBlock
import inr.numass.data.api.NumassPoint
import inr.numass.data.api.NumassSet
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
private val analyzer = SimpleAnalyzer()
class CachedPoint(val point: NumassPoint) : NumassPoint by point {
override val blocks: List<NumassBlock> by lazy { point.blocks }
override val meta: Meta = point.meta
val channelSpectra: Deferred<Map<Int, Table>> = GlobalScope.async(start = CoroutineStart.LAZY) {
point.channels.mapValues { (_, value) -> analyzer.getAmplitudeSpectrum(value) }
}
val spectrum: Deferred<Table> = GlobalScope.async(start = CoroutineStart.LAZY) { analyzer.getAmplitudeSpectrum(point) }
}
class CachedSet(set: NumassSet) : NumassSet by set {
override val points: List<CachedPoint> by lazy { set.points.map { CachedPoint(it) } }
}

View File

@ -1,7 +1,5 @@
package inr.numass.viewer
import hep.dataforge.context.Context
import hep.dataforge.context.Global
import hep.dataforge.fx.dfIconView
import hep.dataforge.fx.except
import hep.dataforge.fx.runGoal
@ -19,7 +17,6 @@ import javafx.scene.layout.Priority
import javafx.scene.text.Font
import javafx.stage.DirectoryChooser
import javafx.stage.FileChooser
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.controlsfx.control.StatusBar
import tornadofx.*
@ -27,9 +24,11 @@ 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) {
class MainView : View(title = "Numass viewer", icon = dfIconView) {
private val statusBar = StatusBar();
private val pointCache by inject<PointCache>()
private val statusBar = StatusBar()
// private val logFragment = LogFragment().apply {
// addLogHandler(context.logger)
// }
@ -67,7 +66,7 @@ class MainView(val context: Context = Global.getContext("viewer")) : View(title
if (rootDir != null) {
NumassProperties.setNumassProperty("numass.viewer.lastPath", rootDir.absolutePath)
GlobalScope.launch {
app.context.launch {
runLater {
path = rootDir.toPath()
}
@ -95,8 +94,9 @@ class MainView(val context: Context = Global.getContext("viewer")) : View(title
val file = chooser.showOpenDialog(primaryStage.scene.window)
if (file != null) {
NumassProperties.setNumassProperty("numass.viewer.lastPath", file.parentFile.absolutePath)
GlobalScope.launch {
NumassProperties.setNumassProperty("numass.viewer.lastPath",
file.parentFile.absolutePath)
app.context.launch {
runLater {
path = file.toPath()
}
@ -110,9 +110,9 @@ class MainView(val context: Context = Global.getContext("viewer")) : View(title
}
}
label(pathProperty.stringBinding{it?.toString() ?: "NOT LOADED"}) {
padding = Insets(0.0, 0.0, 0.0, 10.0);
font = Font.font("System Bold", 13.0);
label(pathProperty.stringBinding { it?.toString() ?: "NOT LOADED" }) {
padding = Insets(0.0, 0.0, 0.0, 10.0)
font = Font.font("System Bold", 13.0)
}
pane {
hgrow = Priority.ALWAYS
@ -136,23 +136,24 @@ class MainView(val context: Context = Global.getContext("viewer")) : View(title
runLater {
contentView = null
}
pointCache.clear()
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(context, null, path.fileName.toString(), path)
} ui {
NumassDataLoader(app.context, null, path.fileName.toString(), path)
} ui { loader: NumassDataLoader ->
contentView = SpectrumView().apply {
clear()
set(it.name, CachedSet(it))
set(loader.name, loader)
}
} except {
alert(
type = Alert.AlertType.ERROR,
header = "Error during set loading",
content = it.toString()
type = Alert.AlertType.ERROR,
header = "Error during set loading",
content = it.toString()
).show()
}
} else {
@ -160,7 +161,7 @@ class MainView(val context: Context = Global.getContext("viewer")) : View(title
runGoal("viewer.load.storage[$path]") {
title = "Load storage ($path)"
message = "Building numass storage tree..."
NumassDirectory.INSTANCE.read(context, path)
NumassDirectory.INSTANCE.read(app.context, path)
} ui {
contentView = StorageView(it as Storage)
}
@ -172,9 +173,9 @@ class MainView(val context: Context = Global.getContext("viewer")) : View(title
} catch (ex: Exception) {
runLater {
alert(
type = Alert.AlertType.ERROR,
header = "Can't load DF envelope from file $path",
content = ex.toString()
type = Alert.AlertType.ERROR,
header = "Can't load DF envelope from file $path",
content = ex.toString()
).show()
}
null
@ -186,13 +187,13 @@ class MainView(val context: Context = Global.getContext("viewer")) : View(title
val point = NumassDataUtils.read(it)
runLater {
contentView = AmplitudeView().apply {
set(path.fileName.toString(), CachedPoint(point))
set(path.fileName.toString(), point)
}
}
} else {
alert(
type = Alert.AlertType.ERROR,
header = "Unknown envelope content: $path"
type = Alert.AlertType.ERROR,
header = "Unknown envelope content: $path"
).show()
}
}

View File

@ -0,0 +1,98 @@
/*
* Copyright 2018 Alexander Nozik.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package inr.numass.viewer
import hep.dataforge.tables.Table
import hep.dataforge.utils.Misc
import inr.numass.data.analyzers.SimpleAnalyzer
import inr.numass.data.api.NumassPoint
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import tornadofx.*
private val analyzer = SimpleAnalyzer()
class PointCache : Controller() {
private val context = app.context
inner class CachedPoint(point: NumassPoint) {
val length = point.length
val voltage = point.voltage
val meta = point.meta
val channelSpectra: Deferred<Map<Int, Table>> = context.async(Dispatchers.IO) {
point.channels.mapValues { (_, value) -> analyzer.getAmplitudeSpectrum(value) }
}
val spectrum: Deferred<Table> = context.async(Dispatchers.IO) {
analyzer.getAmplitudeSpectrum(point)
}
}
private val cache = Misc.getLRUCache<String, CachedPoint>(1000)
fun getCachedPoint(id: String,point: NumassPoint): CachedPoint = cache.getOrPut(id) { CachedPoint(point) }
fun getSpectrumAsync(id: String, point: NumassPoint): Deferred<Table> =
getCachedPoint(id, point).spectrum
suspend fun getChannelSpectra(id: String, point: NumassPoint): Map<Int, Table> =
getCachedPoint(id, point).channelSpectra.await()
fun clear(){
cache.clear()
}
}
//class CachedSet(set: NumassSet, context: Context) {
// override val points: ObservableList<CachedPoint> by lazy {
// set.points.map { CachedPoint(it, context) }.toObservable()
// }
// init {
// var watcher: WatchService? = null
//
// if (set is NumassDataLoader) {
// context.launch(Dispatchers.IO) {
// watcher = set.path.fileSystem.newWatchService()
// try {
// val key: WatchKey = set.path.register(watcher!!, ENTRY_CREATE)
// while (true) {
// key.pollEvents().forEach { event ->
// if (event.kind() == ENTRY_CREATE) {
// val path: Path = event.context() as Path
// if (path.fileName.toString().startsWith(NumassDataLoader.POINT_FRAGMENT_NAME)) {
// val envelope: Envelope = NumassEnvelopeType.infer(path)?.reader?.read(path)
// ?: kotlin.error("Can't read point file")
// val point = NumassDataUtils.read(envelope)
// points.add(CachedPoint(point, context))
// }
// }
// }
// }
// } catch (x: IOException) {
// x.printStackTrace()
// }
// }
// }
// }
//}

View File

@ -3,15 +3,13 @@ package inr.numass.viewer
import hep.dataforge.fx.meta.MetaViewer
import inr.numass.data.analyzers.NumassAnalyzer
import javafx.beans.property.SimpleIntegerProperty
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.controlsfx.glyphfont.FontAwesome
import tornadofx.*
import tornadofx.controlsfx.borders
import tornadofx.controlsfx.toGlyph
class PointInfoView(val point: CachedPoint) : MetaViewer(point.meta) {
class PointInfoView(val cachedPoint: PointCache.CachedPoint) : MetaViewer(cachedPoint.meta) {
val countProperty = SimpleIntegerProperty(0)
var count by countProperty
@ -25,8 +23,10 @@ class PointInfoView(val point: CachedPoint) : MetaViewer(point.meta) {
row {
button(graphic = FontAwesome.Glyph.REFRESH.toGlyph()) {
action {
GlobalScope.launch {
val res = point.spectrum.await().sumOf { it.getValue(NumassAnalyzer.COUNT_KEY).int }
app.context.launch {
val res = cachedPoint.spectrum.await().sumOf {
it.getValue(NumassAnalyzer.COUNT_KEY).int
}
runLater { count = res }
}
}
@ -44,7 +44,10 @@ class PointInfoView(val point: CachedPoint) : MetaViewer(point.meta) {
hbox {
label("Total count rate: ")
label {
textProperty().bind(countProperty.stringBinding { String.format("%.2f", it!!.toDouble() / point.length.toMillis() * 1000) })
textProperty().bind(countProperty.stringBinding {
String.format("%.2f",
it!!.toDouble() / cachedPoint.length.toMillis() * 1000)
})
}
}
}

View File

@ -9,6 +9,7 @@ import hep.dataforge.names.Name
import hep.dataforge.plots.data.DataPlot
import hep.dataforge.plots.jfreechart.JFreeChartFrame
import hep.dataforge.tables.Adapters
import hep.dataforge.values.Values
import inr.numass.data.analyzers.countInWindow
import inr.numass.data.api.NumassSet
import javafx.beans.property.SimpleIntegerProperty
@ -22,6 +23,7 @@ import javafx.util.converter.NumberStringConverter
import org.controlsfx.control.RangeSlider
import tornadofx.*
import java.util.concurrent.atomic.AtomicInteger
import kotlin.math.sqrt
/**
* View for energy spectrum
@ -30,6 +32,8 @@ import java.util.concurrent.atomic.AtomicInteger
*/
class SpectrumView : View(title = "Numass spectrum plot", icon = ImageView(dfIcon)) {
private val pointCache by inject<PointCache>()
private val frame = JFreeChartFrame().configure {
"xAxis.title" to "U"
"xAxis.units" to "V"
@ -37,7 +41,7 @@ class SpectrumView : View(title = "Numass spectrum plot", icon = ImageView(dfIco
"yAxis.units" to "Hz"
//"legend.show" to false
}
private val container = PlotContainer(frame);
private val container = PlotContainer(frame)
private val loChannelProperty = SimpleIntegerProperty(500).apply {
@ -51,7 +55,7 @@ class SpectrumView : View(title = "Numass spectrum plot", icon = ImageView(dfIco
private var upChannel by upChannelProperty
private val data: ObservableMap<String, CachedSet> = FXCollections.observableHashMap();
private val data: ObservableMap<String, NumassSet> = FXCollections.observableHashMap()
val isEmpty = booleanBinding(data) { data.isEmpty() }
override val root = borderpane {
@ -86,7 +90,7 @@ class SpectrumView : View(title = "Numass spectrum plot", icon = ImageView(dfIco
vbox {
label("Up channel")
textfield {
isEditable = true;
isEditable = true
prefWidth = 60.0
textProperty().bindBidirectional(upChannelProperty, NumberStringConverter())
}
@ -100,7 +104,7 @@ class SpectrumView : View(title = "Numass spectrum plot", icon = ImageView(dfIco
init {
data.addListener { change: MapChangeListener.Change<out String, out NumassSet> ->
if (change.wasRemoved()) {
frame.plots.remove(Name.ofSingle(change.key));
frame.plots.remove(Name.ofSingle(change.key))
}
if (change.wasAdded()) {
@ -115,24 +119,26 @@ class SpectrumView : View(title = "Numass spectrum plot", icon = ImageView(dfIco
val progress = AtomicInteger(0)
val totalProgress = data.values.stream().mapToInt { it.points.size }.sum()
data.forEach { name, set ->
val plot: DataPlot = frame.plots[Name.ofSingle(name)] as DataPlot? ?: DataPlot(name).apply { frame.add(this) }
data.forEach { (name, set) ->
val plot: DataPlot =
frame.plots[Name.ofSingle(name)] as DataPlot? ?: DataPlot(name).apply { frame.add(this) }
runGoal("spectrumData[$name]") {
set.points.forEach { it.spectrum.start() }
set.points.map { point ->
val count = point.spectrum.await().countInWindow(loChannel.toShort(), upChannel.toShort());
val seconds = point.length.toMillis() / 1000.0;
set.points.map {
pointCache.getCachedPoint("$name/${it.voltage}[${it.index}]", it)
}.map { cachedPoint ->
val count = cachedPoint.spectrum.await().countInWindow(loChannel.toShort(), upChannel.toShort())
val seconds = cachedPoint.length.toMillis() / 1000.0
runLater {
container.progress = progress.incrementAndGet().toDouble() / totalProgress
}
Adapters.buildXYDataPoint(
point.voltage,
(count / seconds),
Math.sqrt(count.toDouble()) / seconds
cachedPoint.voltage,
(count / seconds),
sqrt(count.toDouble()) / seconds
)
}
} ui { points ->
} ui { points: List<Values> ->
plot.fillData(points)
container.progress = 1.0
//spectrumExportButton.isDisable = false
@ -140,7 +146,7 @@ class SpectrumView : View(title = "Numass spectrum plot", icon = ImageView(dfIco
}
}
operator fun set(key: String, value: CachedSet) {
operator fun set(key: String, value: NumassSet) {
data[key] = value
}

View File

@ -12,13 +12,16 @@ import inr.numass.data.api.NumassPoint
import inr.numass.data.api.NumassSet
import inr.numass.data.storage.NumassDataLoader
import javafx.beans.property.SimpleBooleanProperty
import javafx.collections.ObservableList
import javafx.scene.control.ContextMenu
import javafx.scene.control.TreeItem
import kotlinx.coroutines.runBlocking
import tornadofx.*
class StorageView(val storage: Storage) : View(title = "Numass storage", icon = dfIconView) {
private val pointCache by inject<PointCache>()
private val ampView: AmplitudeView by inject()
private val timeView: TimeView by inject()
private val spectrumView: SpectrumView by inject()
@ -39,7 +42,7 @@ class StorageView(val storage: Storage) : View(title = "Numass storage", icon =
val infoView: UIComponent by lazy {
when (content) {
is CachedPoint -> PointInfoView(content)
is NumassPoint -> PointInfoView(pointCache.getCachedPoint(id, content))
is Metoid -> MetaViewer(content.meta, title = "Meta view: $id")
else -> MetaViewer(Meta.empty(), title = "Meta view: $id")
}
@ -48,7 +51,7 @@ class StorageView(val storage: Storage) : View(title = "Numass storage", icon =
init {
checkedProperty.onChange { selected ->
when (content) {
is CachedPoint -> {
is NumassPoint -> {
if (selected) {
ampView[id] = content
timeView[id] = content
@ -57,7 +60,7 @@ class StorageView(val storage: Storage) : View(title = "Numass storage", icon =
timeView.remove(id)
}
}
is CachedSet -> {
is NumassSet -> {
if (selected) {
spectrumView[id] = content
hvView[id] = content
@ -77,22 +80,18 @@ class StorageView(val storage: Storage) : View(title = "Numass storage", icon =
}
}
val children: List<Container>? by lazy {
when (content) {
is Storage -> runBlocking { content.children }.map { buildContainer(it, this) }.sortedWith(
object : Comparator<Container> {
private val alphanumComparator = AlphanumComparator()
override fun compare(o1: Container, o2: Container): Int = alphanumComparator.compare(o1.id, o2.id)
}
)
is NumassSet -> content.points
.sortedBy { it.index }
.map { buildContainer(it, this) }
.toList()
else -> null
}
fun getChildren(): ObservableList<Container>? = when (content) {
is Storage -> content.children.map {
buildContainer(it, this)
}.sortedWith(Comparator.comparing({ it.id }, AlphanumComparator)).asObservable()
is NumassSet -> content.points
.sortedBy { it.index }
.map { buildContainer(it, this) }
.asObservable()
else -> null
}
val hasChildren: Boolean = (content is Storage) || (content is NumassSet)
}
@ -102,10 +101,12 @@ class StorageView(val storage: Storage) : View(title = "Numass storage", icon =
//isShowRoot = false
root = TreeItem(Container(storage.name, storage))
root.isExpanded = true
lazyPopulate(leafCheck = { !it.value.hasChildren }) {
it.value.children
lazyPopulate(leafCheck = {
!it.value.hasChildren
}) {
it.value.getChildren()
}
cellFormat { value ->
cellFormat { value: Container ->
when (value.content) {
is Storage -> {
text = value.content.name
@ -187,25 +188,20 @@ class StorageView(val storage: Storage) : View(title = "Numass storage", icon =
private fun buildContainer(content: Any, parent: Container): Container =
when (content) {
is Storage -> {
Container(content.fullName.toString(), content)
when (content) {
is Storage -> Container(content.fullName.toString(), content)
is NumassSet -> {
val id: String = if (content is NumassDataLoader) {
content.fullName.unescaped
} else {
content.name
}
is NumassSet -> {
val id: String = if (content is NumassDataLoader) {
content.fullName.unescaped
} else {
content.name
}
Container(id, content as? CachedSet ?: CachedSet(content))
}
is NumassPoint -> {
Container("${parent.id}/${content.voltage}[${content.index}]", content as? CachedPoint
?: CachedPoint(content))
}
is FileTableLoader -> {
Container(content.path.toString(), content);
}
else -> throw IllegalArgumentException("Unknown content type: ${content::class.java}");
Container(id, content)
}
is NumassPoint -> {
Container("${parent.id}/${content.voltage}[${content.index}]", content)
}
is FileTableLoader -> Container(content.path.toString(), content)
else -> throw IllegalArgumentException("Unknown content type: ${content::class.java}");
}
}

View File

@ -15,6 +15,7 @@ import hep.dataforge.plots.jfreechart.JFreeChartFrame
import hep.dataforge.tables.Adapters
import hep.dataforge.values.ValueMap
import inr.numass.data.analyzers.TimeAnalyzer
import inr.numass.data.api.NumassPoint
import javafx.beans.Observable
import javafx.beans.binding.DoubleBinding
import javafx.collections.FXCollections
@ -59,7 +60,7 @@ class TimeView : View(title = "Numass time spectrum plot", icon = ImageView(dfIc
private val container = PlotContainer(frame)
private val data: ObservableMap<String, CachedPoint> = FXCollections.observableHashMap()
private val data: ObservableMap<String, NumassPoint> = FXCollections.observableHashMap()
private val plots: ObservableMap<String, Goal<Plottable>> = FXCollections.observableHashMap()
val isEmpty = booleanBinding(data) { isEmpty() }
@ -88,11 +89,11 @@ class TimeView : View(title = "Numass time spectrum plot", icon = ImageView(dfIc
/**
* Put or replace current plot with name `key`
*/
operator fun set(key: String, point: CachedPoint) {
operator fun set(key: String, point: NumassPoint) {
data[key] = point
}
fun addAll(data: Map<String, CachedPoint>) {
fun addAll(data: Map<String, NumassPoint>) {
this.data.putAll(data);
}
@ -160,7 +161,7 @@ class TimeView : View(title = "Numass time spectrum plot", icon = ImageView(dfIc
/**
* Set frame content to the given map. All keys not in the map are removed.
*/
fun setAll(map: Map<String, CachedPoint>) {
fun setAll(map: Map<String, NumassPoint>) {
plots.clear();
//Remove obsolete keys
data.keys.filter { !map.containsKey(it) }.forEach {

View File

@ -2,6 +2,7 @@ package inr.numass.viewer
import ch.qos.logback.classic.Level
import ch.qos.logback.classic.Logger
import hep.dataforge.context.Context
import hep.dataforge.context.Global
import hep.dataforge.fx.dfIcon
import javafx.stage.Stage
@ -16,13 +17,18 @@ class Viewer : App(MainView::class) {
(LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME) as Logger).level = Level.INFO
}
val context: Context = Global.getContext("numass-viewer")
override fun start(stage: Stage) {
stage.icons += dfIcon
super.start(stage)
stage.icons += dfIcon
}
override fun stop() {
super.stop()
context.close()
Global.terminate();
super.stop()
}
}
}
internal val App.context get() = (this as Viewer).context

View File

@ -7,10 +7,11 @@ import hep.dataforge.tables.Table
import inr.numass.data.api.NumassPoint
import inr.numass.data.api.NumassSet
import inr.numass.data.storage.NumassDirectory
import inr.numass.viewer.*
import inr.numass.viewer.AmplitudeView
import inr.numass.viewer.HVView
import inr.numass.viewer.SpectrumView
import javafx.application.Application
import javafx.scene.image.ImageView
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import tornadofx.*
import java.io.File
@ -25,7 +26,8 @@ class ViewerComponentsTest : View(title = "Numass viewer test", icon = ImageView
//val set: NumassSet = NumassStorageFactory.buildLocal(rootDir).provide("loader::set_8", NumassSet::class.java).orElseThrow { RuntimeException("err") }
private val cache: MutableMap<NumassPoint, Table> = ConcurrentHashMap();
private val cache: MutableMap<NumassPoint, Table> = ConcurrentHashMap()
val context = Global
val amp: AmplitudeView by inject(params = mapOf("cache" to cache))//= AmplitudeView(immutable = immutable)
val sp: SpectrumView by inject(params = mapOf("cache" to cache))
@ -35,11 +37,11 @@ class ViewerComponentsTest : View(title = "Numass viewer test", icon = ImageView
top {
button("Click me!") {
action {
GlobalScope.launch {
context.launch {
val set: NumassSet = NumassDirectory.INSTANCE.read(Global, File("D:\\Work\\Numass\\data\\2017_05\\Fill_2").toPath())
?.provide("loader::set_2", NumassSet::class.java).nullable
?: kotlin.error("Error")
update(set);
update(set)
}
}
}
@ -54,13 +56,13 @@ class ViewerComponentsTest : View(title = "Numass viewer test", icon = ImageView
}
fun update(set: NumassSet) {
amp.setAll(set.points.filter { it.voltage != 16000.0 }.associateBy({ "point_${it.voltage}" }) { CachedPoint(it) });
sp.set("test", CachedSet(set));
hv.set(set.name, set)
amp.setAll(set.points.filter { it.voltage != 16000.0 }.associateBy { "point_${it.voltage}" })
sp["test"] = set
hv[set.name] = set
}
}
fun main(args: Array<String>) {
Application.launch(ViewerComponentsTestApp::class.java, *args);
Application.launch(ViewerComponentsTestApp::class.java, *args)
}