Add renderers

This commit is contained in:
Alexander Nozik 2021-12-21 13:29:48 +03:00
parent d1ddf89c6e
commit 7d053d4fa9
No known key found for this signature in database
GPG Key ID: F7FCF2DD25C71357
18 changed files with 309 additions and 97 deletions

2
.gitignore vendored
View File

@ -4,6 +4,6 @@
out/
.gradle
build/
/notebooks/.ipynb_checkpoints
!gradle-wrapper.jar

View File

@ -9,15 +9,15 @@ allprojects {
}
group = "ru.inr.mass"
version = "0.1.0-dev-1"
version = "0.1.0"
}
val dataforgeVersion by extra("0.5.2")
val tablesVersion: String by extra("0.1.1")
val tablesVersion: String by extra("0.1.2")
val kmathVersion by extra("0.3.0-dev-17")
val plotlyVersion: String by extra("0.5.0")
ksciencePublish{
git("https://mipt-npm.jetbrains.space/p/numass/code/numass/")
github("numass")
space("https://maven.pkg.jetbrains.space/mipt-npm/p/numass/maven")
}

View File

@ -0,0 +1,140 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "2a640dcc-4696-408f-b1f0-0cdf917e4719",
"metadata": {},
"outputs": [],
"source": [
"@file:Repository(\"https://repo.kotlin.link\")\n",
"@file:Repository(\"*mavenLocal\")\n",
"@file:DependsOn(\"ru.inr.mass:numass-workspace:0.1.0\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2d193fb2-6c18-4c64-b5c3-3e9ed4099d5b",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"val repo: DataTree<NumassDirectorySet> = Numass.readNumassRepository(\"D:\\\\Work\\\\Numass\\\\data\\\\test\")\n",
"repo"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "373de92b-2533-4cfc-820b-ca90b9d028fc",
"metadata": {},
"outputs": [],
"source": [
"val numassSet = repo[\"set_7\"]\n",
"numassSet"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d0845a15-2928-4e6e-a891-fee130ef5326",
"metadata": {},
"outputs": [],
"source": [
"val point = numassSet.points.first{it.voltage == 14000.0}"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "842dfbcb-17f0-4df9-b5d4-2835f15f3a50",
"metadata": {},
"outputs": [],
"source": [
"point"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "50df6925-82f5-4330-a1c2-3e43fb9cd17d",
"metadata": {},
"outputs": [],
"source": [
"Plotly.plotNumassBlock(point, eventExtractor = NumassEventExtractor.TQDC)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "196f94ca-0439-4190-bda7-8d692c37b2db",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"val frames = point.listFrames()\n",
"Plotly.page {\n",
" p { +\"${frames.size} frames\" }\n",
" h2 { +\"Random frames\" }\n",
" plot {\n",
" val random = kotlin.random.Random(1234)\n",
"\n",
" repeat(10) {\n",
" val frame = frames.random(random)\n",
" scatter {\n",
" y.numbers = frame.signal.map { (it.toUShort().toInt() - Short.MAX_VALUE).toShort() }\n",
" }\n",
" }\n",
" }\n",
" h2 { +\"Analysis\" }\n",
" plot {\n",
" histogram {\n",
" name = \"max\"\n",
" x.numbers = frames.map { frame -> frame.signal.maxOf { (it.toUShort().toInt() - Short.MAX_VALUE).toShort() } }\n",
" }\n",
"\n",
" histogram {\n",
" name = \"max-min\"\n",
" xbins {\n",
" size = 2.0\n",
" }\n",
" x.numbers = frames.map { frame ->\n",
" frame.signal.maxOf { it.toUShort().toInt() - Short.MAX_VALUE } -\n",
" frame.signal.minOf { it.toUShort().toInt() - Short.MAX_VALUE }\n",
" }\n",
" }\n",
" }\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5320d9d5-eae3-469b-a1f2-5d33d3db286c",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Kotlin",
"language": "kotlin",
"name": "kotlin"
},
"language_info": {
"codemirror_mode": "text/x-kotlin",
"file_extension": ".kt",
"mimetype": "text/x-kotlin",
"name": "kotlin",
"nbconvert_exporter": "",
"pygments_lexer": "kotlin",
"version": "1.6.20-dev-6372"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -1,35 +0,0 @@
{
"imports": [
"kscience.plotly.*",
"kscience.plotly.models.*",
"kscience.plotly.JupyterPlotly",
"space.kscience.dataforge.meta.*",
"kotlinx.html.*",
"ru.inr.mass.workspace.*"
],
"repositories": [
"*mavenLocal",
"https://dl.bintray.com/mipt-npm/dataforge",
"https://dl.bintray.com/mipt-npm/kscience",
"https://dl.bintray.com/mipt-npm/dev",
"https://kotlin.bintray.com/kotlinx"
],
"properties": {
"v": "0.1.0-SNAPSHOT"
},
"link": "https://mipt-npm.jetbrains.space/p/numass/code/numass",
"dependencies": [
"ru.inr.mass:numass-workspace:$v"
],
"init": [
"DISPLAY(HTML(JupyterPlotly.loadJs().toString()))",
"DISPLAY(HTML(\"<p>Plotly.kt jupyter integration is in the development phase. Expect glitches!</p>\"))"
],
"renderers": {
"kscience.plotly.HtmlFragment": "HTML($it.toString())",
"kscience.plotly.Plot": "HTML(JupyterPlotly.renderPlot($it))",
"kscience.plotly.PlotlyFragment": "HTML(JupyterPlotly.renderFragment($it))",
"kscience.plotly.PlotlyPage": "HTML(JupyterPlotly.renderPage($it), true)",
"ru.inr.mass.data.proto.NumassDirectorySet": "HTML(JupyterPlotly.renderPage(${it.plotlyPage()}), true)"
}
}

View File

@ -12,7 +12,9 @@ public class NumassAmplitudeSpectrum(public val amplitudes: Map<UShort, ULong>)
public val minChannel: UShort by lazy { amplitudes.keys.minOf { it } }
public val maxChannel: UShort by lazy { amplitudes.keys.maxOf { it } }
public fun binned(binSize: UInt, range: UIntRange = minChannel..maxChannel): Map<UIntRange, Double> {
public val channels: UIntRange by lazy { minChannel..maxChannel }
public fun binned(binSize: UInt, range: UIntRange = channels): Map<UIntRange, Double> {
val keys = sequence {
var left = range.first
do {
@ -24,6 +26,9 @@ public class NumassAmplitudeSpectrum(public val amplitudes: Map<UShort, ULong>)
return keys.associateWith { bin -> amplitudes.filter { it.key in bin }.values.sum().toDouble() }
}
public fun sum(range: UIntRange = channels): ULong =
amplitudes.filter { it.key in range }.values.sum()
}
/**

View File

@ -15,9 +15,9 @@ public class TimeAnalyzerParameters : Scheme() {
/**
* The relative fraction of events that should be removed by time cut
*/
public var crFraction by double()
public var min by double(0.0)
public var crMin by double(0.0)
public var crFraction: Double? by double()
public var min: Double by double(0.0)
public var crMin: Double by double(0.0)
/**
* The number of events in chunk to split the chain into. If null, no chunks are used

View File

@ -1,6 +1,7 @@
package ru.inr.mass.data.analysis
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import ru.inr.mass.data.api.NumassBlock
import ru.inr.mass.data.api.NumassEvent
@ -12,6 +13,30 @@ public fun interface NumassEventExtractor {
* A default event extractor that ignores frames
*/
public val EVENTS_ONLY: NumassEventExtractor = NumassEventExtractor { it.events }
public val TQDC: NumassEventExtractor = NumassEventExtractor { block ->
block.frames.map { frame ->
var max = 0
var min = 0
var indexOfMax = 0
frame.signal.forEachIndexed { index, sh ->
val corrected = sh.toUShort().toInt() - Short.MAX_VALUE
if (corrected >= max) {
max = corrected
indexOfMax = index
}
if (corrected <= min) {
min = corrected
}
}
NumassEvent(
(max - min).toShort().toUShort(),
frame.timeOffset + frame.tickSize.inWholeNanoseconds * indexOfMax,
block
)
}
}
}
}

View File

@ -8,6 +8,7 @@ import ru.inr.mass.data.api.NumassBlock
import ru.inr.mass.data.api.getTime
import space.kscience.kmath.histogram.UnivariateHistogram
import kotlin.math.max
import kotlin.time.DurationUnit
public fun <T, R> Flow<T>.zipWithNext(block: (l: T, r: T) -> R): Flow<R> {
var current: T? = null
@ -26,7 +27,7 @@ public fun NumassBlock.timeHistogram(
runBlocking {
extractor.extract(this@timeHistogram).zipWithNext { l, r ->
if(l.owner == r.owner) {
max((r.getTime() - l.getTime()).inWholeMicroseconds,0L)
max((r.getTime() - l.getTime()).toDouble(DurationUnit.SECONDS),0.0)
} else {
0
}

View File

@ -1,17 +1,17 @@
package ru.inr.mass.data.api
import kotlinx.datetime.Instant
import kotlin.time.Duration
/**
* The continuous frame of digital detector data
* Created by darksnake on 06-Jul-17.
* @param time The absolute start time of the frame
* @param timeOffset The time offset relative to block start in nanos
* @param tickSize The time interval per tick
* @param signal The buffered signal shape in ticks
*/
public class NumassFrame(
public val time: Instant,
public val timeOffset: Long,
public val tickSize: Duration,
public val signal: ShortArray,
) {

View File

@ -89,6 +89,8 @@ public interface NumassPoint : ParentBlock {
}
}
public val NumassPoint.title: String get() = "p$index(HV=$voltage)"
/**
* Get the first block if it exists. Throw runtime exception otherwise.
*

View File

@ -19,9 +19,7 @@ package ru.inr.mass.data.proto
import io.ktor.utils.io.core.readBytes
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.runBlocking
import kotlinx.datetime.DateTimeUnit
import kotlinx.datetime.Instant
import kotlinx.datetime.plus
import okio.ByteString
import org.slf4j.LoggerFactory
import ru.inr.mass.data.api.*
@ -202,9 +200,9 @@ public class ProtoNumassBlock(
get() {
val tickSize = block.bin_size.nanoseconds
return block.frames.asFlow().map { frame ->
val time = startTime.plus(frame.time, DateTimeUnit.NANOSECOND)
//val time = startTime.plus(frame.time, DateTimeUnit.NANOSECOND)
val frameData = frame.data_
NumassFrame(time, tickSize, frameData.toShortArray())
NumassFrame(frame.time, tickSize, frameData.toShortArray())
}
}

View File

@ -33,7 +33,7 @@ class TestNumassDirectory {
fun testTQDCRead() = runBlocking {
val pointPath = Path.of("src/test/resources", "testData/tqdc")
val set: NumassSet = context.readNumassDirectory(pointPath)
val point = set.first { it.voltage == 16000.0 }
val point = set.first { it.voltage == 18200.0 }
point.getChannels().forEach { (channel, block) ->
println("$channel: $block")
if(block is ParentBlock){

View File

@ -1,6 +1,6 @@
plugins {
kotlin("jvm")
id("ru.mipt.npm.gradle.common")
id("ru.mipt.npm.gradle.jvm")
id("com.github.johnrengelman.shadow") version "7.1.1"
`maven-publish`
}
@ -11,6 +11,7 @@ kotlin {
val dataforgeVersion: String by rootProject.extra
val plotlyVersion: String by rootProject.extra
val kmathVersion: String by rootProject.extra
val tablesVersion: String by rootProject.extra
dependencies {
implementation(projects.numassDataProto)
@ -19,6 +20,7 @@ dependencies {
implementation("space.kscience:dataforge-workspace:$dataforgeVersion")
implementation("space.kscience:plotlykt-jupyter:$plotlyVersion")
implementation("space.kscience:kmath-jupyter:$kmathVersion")
implementation("space.kscience:tables-kt:$tablesVersion")
}
kscience{

View File

@ -4,10 +4,17 @@ package ru.inr.mass.notebook
import org.jetbrains.kotlinx.jupyter.api.HTML
import org.jetbrains.kotlinx.jupyter.api.libraries.JupyterIntegration
import ru.inr.mass.data.api.NumassBlock
import ru.inr.mass.data.api.NumassFrame
import ru.inr.mass.data.api.NumassSet
import ru.inr.mass.data.proto.NumassDirectorySet
import ru.inr.mass.workspace.Numass
import ru.inr.mass.workspace.numassSet
import ru.inr.mass.workspace.plotNumassBlock
import ru.inr.mass.workspace.plotNumassSet
import space.kscience.dataforge.data.DataTree
import space.kscience.plotly.Plotly
import space.kscience.plotly.scatter
import space.kscience.plotly.toHTML
import space.kscience.plotly.toPage
internal class NumassJupyter : JupyterIntegration() {
override fun Builder.onLoaded() {
@ -30,11 +37,26 @@ internal class NumassJupyter : JupyterIntegration() {
render<NumassBlock> {
HTML(Plotly.plotNumassBlock(it).toPage().render())
}
render<NumassFrame> { numassFrame ->
HTML(
Plotly.plot {
scatter {
x.numbers = numassFrame.signal.indices.map { numassFrame.tickSize.times(it).inWholeNanoseconds }
y.numbers = numassFrame.signal.toList()
}
}.toHTML()
)
}
render<NumassSet> { numassSet ->
HTML(Plotly.numassSet(numassSet).render(), true)
HTML(Plotly.plotNumassSet(numassSet).toPage().render())
}
render<DataTree<NumassDirectorySet>> { tree ->
HTML("TODO: render repository tree")
}
}
}

View File

@ -2,7 +2,7 @@ package ru.inr.mass.scripts
import ru.inr.mass.data.proto.NumassDirectorySet
import ru.inr.mass.workspace.Numass.readNumassRepository
import ru.inr.mass.workspace.numassSet
import ru.inr.mass.workspace.plotNumassSet
import space.kscience.dataforge.data.DataTree
import space.kscience.dataforge.data.await
import space.kscience.dataforge.data.getData
@ -14,5 +14,5 @@ suspend fun main() {
//val dataPath = Path.of("D:\\Work\\Numass\\data\\2018_04\\Adiabacity_19\\set_4\\")
//val testSet = NUMASS.context.readNumassDirectory(dataPath)
val testSet = repo.getData("Adiabacity_19.set_3")?.await() ?: error("Not found")
Plotly.numassSet(testSet).makeFile()
Plotly.plotNumassSet(testSet).makeFile()
}

View File

@ -1,19 +1,19 @@
package ru.inr.mass.scripts
import kotlinx.coroutines.flow.toList
import kotlinx.html.h2
import kotlinx.html.p
import kotlinx.serialization.json.Json
import ru.inr.mass.workspace.Numass.readNumassDirectory
import ru.inr.mass.workspace.listFrames
import space.kscience.dataforge.meta.MetaSerializer
import space.kscience.plotly.*
suspend fun main() {
//val repo: DataTree<NumassDirectorySet> = readNumassRepository("D:\\Work\\numass-data\\")
val directory = readNumassDirectory("D:\\Work\\numass-data\\set_3\\")
val directory = readNumassDirectory("D:\\Work\\Numass\\data\\test\\set_7")
val point = directory.points.first()
val frames = point.frames.toList()
val frames = point.listFrames()
Plotly.page {
p { +"${frames.size} frames" }
h2 { +"Random frames" }
@ -23,7 +23,7 @@ suspend fun main() {
repeat(10) {
val frame = frames.random(random)
scatter {
y.numbers = frame.signal.map { it.toUShort().toInt() - Short.MAX_VALUE }
y.numbers = frame.signal.map { (it.toUShort().toInt() - Short.MAX_VALUE).toShort() }
}
}
}
@ -31,7 +31,7 @@ suspend fun main() {
plot {
histogram {
name = "max"
x.numbers = frames.map { frame -> frame.signal.maxOf { it.toUShort().toInt() - Short.MAX_VALUE } }
x.numbers = frames.map { frame -> frame.signal.maxOf { (it.toUShort().toInt() - Short.MAX_VALUE).toShort() } }
}
histogram {

View File

@ -1,8 +1,10 @@
package ru.inr.mass.workspace
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import ru.inr.mass.data.api.NumassBlock
import ru.inr.mass.data.api.NumassSet
import ru.inr.mass.data.proto.NumassDirectorySet
import ru.inr.mass.data.proto.readNumassDirectory
@ -45,3 +47,7 @@ object Numass {
operator fun DataSet<NumassSet>.get(name: String): NumassSet? = runBlocking {
getData(Name.parse(name))?.await()
}
fun NumassBlock.listFrames() = runBlocking { frames.toList() }
fun NumassBlock.listEvents() = runBlocking { events.toList() }

View File

@ -7,7 +7,10 @@ import ru.inr.mass.data.analysis.NumassAmplitudeSpectrum
import ru.inr.mass.data.analysis.NumassEventExtractor
import ru.inr.mass.data.analysis.amplitudeSpectrum
import ru.inr.mass.data.analysis.timeHistogram
import ru.inr.mass.data.api.NumassBlock
import ru.inr.mass.data.api.NumassPoint
import ru.inr.mass.data.api.NumassSet
import ru.inr.mass.data.api.title
import ru.inr.mass.data.proto.HVData
import ru.inr.mass.data.proto.NumassDirectorySet
import space.kscience.dataforge.values.asValue
@ -20,6 +23,7 @@ import space.kscience.kmath.structures.Buffer
import space.kscience.kmath.structures.DoubleBuffer
import space.kscience.plotly.*
import space.kscience.plotly.models.*
import kotlin.time.DurationUnit
/**
* Plot a kmath histogram
@ -52,47 +56,89 @@ fun Plot.hvData(data: HVData): Trace = scatter {
y.numbers = data.map { it.value }
}
fun Plotly.numassSet(
set: NumassSet,
fun Plotly.plotNumassBlock(
block: NumassBlock,
amplitudeBinSize: UInt = 20U,
eventExtractor: NumassEventExtractor = NumassEventExtractor.EVENTS_ONLY,
): PlotlyPage =
Plotly.page {
h1 {
+"Numass point set ${ShapeType.path}"
}
h2 {
+"Amplitude spectrum"
}
plot {
runBlocking {
set.points.sortedBy { it.index }.forEach {
histogram(it.amplitudeSpectrum(eventExtractor), amplitudeBinSize)
splitChannels: Boolean = true
): PlotlyFragment = Plotly.fragment {
plot {
runBlocking {
if (splitChannels && block is NumassPoint) {
block.getChannels().forEach { (channel, channelBlock) ->
val spectrum = channelBlock.amplitudeSpectrum(eventExtractor)
histogram(spectrum, amplitudeBinSize) {
name = block.title + "[$channel]"
}
}
}
}
h2 {
+"Time spectra"
}
plot {
set.points.sortedBy { it.index }.forEach {
histogram(it.timeHistogram(1e3))
}
layout.yaxis.type = AxisType.log
}
if (set is NumassDirectorySet) {
set.getHvData()?.let { entries ->
h2 {
+"HV"
}
plot {
hvData(entries)
} else {
scatter {
val spectrum = block.amplitudeSpectrum(eventExtractor)
histogram(spectrum, amplitudeBinSize)
}
}
}
}
}
fun Plotly.plotNumassSet(
set: NumassSet,
amplitudeBinSize: UInt = 20U,
eventExtractor: NumassEventExtractor = NumassEventExtractor.EVENTS_ONLY,
): PlotlyFragment = Plotly.fragment {
h1 { +"Numass point set ${(set as? NumassDirectorySet)?.path ?: ""}" }
//TODO do in parallel
val spectra = runBlocking {
set.points.sortedBy { it.index }.map { it to it.amplitudeSpectrum(eventExtractor) }
}
h2 { +"Amplitude spectrum" }
plot {
spectra.forEach { (point, spectrum) ->
histogram(spectrum, amplitudeBinSize) {
name = point.title
}
}
}
h2 { +"Time spectra" }
plot {
spectra.forEach { (point,spectrum) ->
val countRate = runBlocking {
spectrum.sum().toDouble() / point.getLength().toDouble(DurationUnit.SECONDS)
}
val binSize = 1.0 / countRate / 10.0
histogram(point.timeHistogram(binSize)) {
name = point.title
}
}
layout.yaxis.type = AxisType.log
}
h2 { +"Integral spectrum" }
plot {
scatter {
mode = ScatterMode.markers
x.numbers = spectra.map { it.first.voltage }
y.numbers = spectra.map { it.second.sum().toLong() }
}
}
if (set is NumassDirectorySet) {
set.getHvData()?.let { entries ->
h2 { +"HV" }
plot {
hvData(entries)
}
}
}
}
/**
* Add a number buffer accessor for Plotly trace values