Skip to content

Commit 8fb4e4d

Browse files
authored
Merge pull request #2 from vikulin/NPESv3
NPESv3 - multiple input spectra support
2 parents 153e12d + 22f1435 commit 8fb4e4d

File tree

4 files changed

+172
-111
lines changed

4 files changed

+172
-111
lines changed

app/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ android {
1212
applicationId = "io.github.vikulin.opengammakit"
1313
minSdk = 24
1414
targetSdk = 35
15-
versionCode = 17
16-
versionName = "1.1.7"
15+
versionCode = 18
16+
versionName = "1.1.8"
1717
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
1818
setProperty("archivesBaseName", "ogk-inspector-$versionName")
1919
}

app/src/main/kotlin/io/github/vikulin/opengammakit/SpectrumFragment.kt

Lines changed: 138 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ import androidx.core.net.toUri
6262
import com.github.mikephil.charting.components.Legend
6363
import io.github.vikulin.opengammakit.math.SpectrumModifier
6464
import io.github.vikulin.opengammakit.math.SpectrumModifier.smartPeakDetect
65-
import io.github.vikulin.opengammakit.model.EnergySpectrum
65+
import io.github.vikulin.opengammakit.model.DerivedSpectrumEntry
66+
import io.github.vikulin.opengammakit.model.ModifierInfo
6667
import io.github.vikulin.opengammakit.model.GammaKitEntry
6768
import io.github.vikulin.opengammakit.view.FwhmSpectrumSelectionDialogFragment
6869
import io.github.vikulin.opengammakit.view.SaveSelectedSpectrumDialogFragment
@@ -355,9 +356,9 @@ class SpectrumFragment : SerialConnectionFragment(),
355356
xAxis.limitLines.removeIf { it.label.startsWith("P@") }
356357

357358
// Assume you are working with spectrum index = 0
358-
val energySpectrum = spectrumDataSet.data[0].resultData.energySpectrum
359-
val peaks = energySpectrum.peaks
360-
if (peaks.isEmpty()) return
359+
val energySpectrum = spectrumDataSet.derivedSpectra[0]
360+
val peaks = energySpectrum?.peaks
361+
if (peaks.isNullOrEmpty()) return
361362

362363
val sortedCalibrationList = verticalCalibrationLineList.sortedBy { it.second.first }
363364

@@ -540,24 +541,22 @@ class SpectrumFragment : SerialConnectionFragment(),
540541
private fun setupChart() {
541542
val primaryColor = resources.getColor(R.color.colorPrimaryText, null)
542543
//copy data to outputSpectrum
543-
spectrumDataSet.data.mapIndexed { index, entry ->
544-
if(entry.resultData.energySpectrum.filters.isEmpty()){
545-
resetSpectrumData(entry.resultData.energySpectrum)
546-
}
544+
if(spectrumDataSet.derivedSpectra.isEmpty()){
545+
resetSpectrumData(spectrumDataSet)
547546
}
548547
// Create LineDataSets from each GammaKitEntry in spectrumDataSet
549-
val dataSets = spectrumDataSet.data.mapIndexed { index, entry ->
550-
val spectrum = entry.resultData.energySpectrum.outputSpectrum
548+
val dataSets = spectrumDataSet.derivedSpectra.map { entry ->
549+
val spectrum = entry.value.resultSpectrum
551550
val entries = spectrum.mapIndexed { ch, count ->
552551
Entry(ch.toFloat(), count.toFloat())
553552
}
554-
val label = getSpectrumLabel(index, entry)
553+
val label = getSpectrumLabel(entry.key, spectrumDataSet.data[entry.key])
555554
LineDataSet(entries, label).apply {
556555
mode = LineDataSet.Mode.CUBIC_BEZIER
557556
lineWidth = 1.5f
558557
setDrawCircles(false)
559558
setDrawValues(false)
560-
color = getLineColor(requireContext(), index)
559+
color = getLineColor(requireContext(), entry.key)
561560
}
562561
}
563562

@@ -609,18 +608,18 @@ class SpectrumFragment : SerialConnectionFragment(),
609608

610609
private fun updateChartSpectrumData() {
611610

612-
val dataSets = spectrumDataSet.data.mapIndexed { index, entry ->
613-
val spectrum = entry.resultData.energySpectrum.outputSpectrum
611+
val dataSets = spectrumDataSet.derivedSpectra.map { entry ->
612+
val spectrum = entry.value.resultSpectrum
614613
val entries = spectrum.mapIndexed { ch, count ->
615614
Entry(ch.toFloat(), count.toFloat())
616615
}
617-
val label = getSpectrumLabel(index, entry)
616+
val label = getSpectrumLabel(entry.key, spectrumDataSet.data[entry.key])
618617
LineDataSet(entries, label).apply {
619618
mode = LineDataSet.Mode.CUBIC_BEZIER
620619
lineWidth = 1.5f
621620
setDrawCircles(false)
622621
setDrawValues(false)
623-
color = getLineColor(requireContext(), index)
622+
color = getLineColor(requireContext(), entry.key)
624623
}
625624
}
626625

@@ -820,8 +819,8 @@ class SpectrumFragment : SerialConnectionFragment(),
820819
energySpectrum.numberOfChannels = spectrum.size
821820
energySpectrum.measurementTime =
822821
(SystemClock.elapsedRealtime() - measureTimer.base)/1000
823-
// copy spectrum. TODO apply filters and peaks detection
824-
resetSpectrumData(energySpectrum)
822+
// copy spectrum. TODO apply modifiers and peaks detection
823+
resetSpectrumData(spectrumDataSet)
825824

826825
updateChartSpectrumData()
827826
} catch (e: Exception) {
@@ -860,10 +859,8 @@ class SpectrumFragment : SerialConnectionFragment(),
860859
"Parsed scheduled spectrum number: ${openGammaKitData.data.size}"
861860
)
862861
spectrumDataSet.data[deviceSpectrumIndex] = openGammaKitData.data[deviceSpectrumIndex]
863-
var energySpectrum = spectrumDataSet.data[deviceSpectrumIndex].
864-
resultData.energySpectrum
865-
// copy spectrum. TODO apply filters and peaks detection
866-
resetSpectrumData(energySpectrum)
862+
// copy spectrum. TODO apply modifiers and peaks detection
863+
resetSpectrumData(spectrumDataSet)
867864
updateChartSpectrumData()
868865
} catch (e: Exception) {
869866
Log.e("Test", "Failed to parse data: ${e.message}")
@@ -1517,96 +1514,149 @@ class SpectrumFragment : SerialConnectionFragment(),
15171514
index in selectedIndexes
15181515
}.toMutableList()
15191516

1520-
// Create a new OpenGammaKitData with the same schema version and filtered data
1521-
val filteredData = OpenGammaKitData(
1517+
// Create a new OpenGammaKitData with the same schema version and modified data
1518+
val modifiedData = OpenGammaKitData(
15221519
schemaVersion = spectrumDataSet.schemaVersion,
15231520
data = selectedEntries
15241521
)
15251522

1526-
// Call save method with the filtered data
1527-
saveGammaKitDataAsJson(requireContext(), filteredData)
1523+
// Call save method with the modified data
1524+
saveGammaKitDataAsJson(requireContext(), modifiedData)
15281525
}
15291526

1530-
private fun toggleSavitzkyGolayFilter(){
1531-
for (entry in spectrumDataSet.data) {
1532-
val energy = entry.resultData.energySpectrum
1533-
if (!energy.filters.contains("SavitzkyGolay")) {
1534-
// Apply filter and add tag
1535-
val inputSpectrum = if(energy.filters.isNotEmpty()) {
1536-
entry.resultData.energySpectrum.outputSpectrum
1537-
} else {
1538-
entry.resultData.energySpectrum.spectrum.map { it.toDouble() }
1539-
}
1540-
SpectrumModifier.applySavitzkyGolayFilter(inputSpectrum, entry)
1541-
entry.resultData.energySpectrum.filters.add("SavitzkyGolay")
1542-
} else {
1543-
energy.filters.clear()
1544-
resetSpectrumData(energy)
1527+
private fun toggleSavitzkyGolayFilter() {
1528+
val alreadyModified = alreadyModified("SavitzkyGolay")
1529+
1530+
if (!alreadyModified) {
1531+
// Apply Savitzky-Golay modifier for each original spectrum
1532+
spectrumDataSet.data.forEachIndexed { index, entry ->
1533+
val inputSpectrum = entry.resultData.energySpectrum.spectrum.map { it.toDouble() }
1534+
1535+
val modified = SpectrumModifier.applySavitzkyGolayFilter(inputSpectrum)
1536+
1537+
val derivedEntry = DerivedSpectrumEntry(
1538+
name = "${entry.deviceData.deviceName} - SavitzkyGolay",
1539+
resultSpectrum = modified,
1540+
modifiers = mutableListOf(
1541+
ModifierInfo(
1542+
modifierName = "SavitzkyGolay",
1543+
inputIndexes = listOf(index)
1544+
)
1545+
)
1546+
)
1547+
1548+
spectrumDataSet.derivedSpectra[index] = derivedEntry
15451549
}
1550+
} else {
1551+
// Reset: clear all derived spectra and add raw versions only
1552+
resetSpectrumData(spectrumDataSet)
15461553
}
15471554
}
15481555

1549-
private fun applySavitzkyGolayFilter(apply: Boolean) {
1550-
for (entry in spectrumDataSet.data) {
1551-
val energy = entry.resultData.energySpectrum
1552-
if (apply) {
1553-
if (!energy.filters.contains("SavitzkyGolay")) {
1554-
// Apply filter and add tag
1555-
val inputSpectrum = if(energy.filters.isNotEmpty()) {
1556-
entry.resultData.energySpectrum.outputSpectrum
1557-
} else {
1558-
entry.resultData.energySpectrum.spectrum.map { it.toDouble() }
1559-
}
1560-
SpectrumModifier.applySavitzkyGolayFilter(inputSpectrum, entry)
1561-
entry.resultData.energySpectrum.filters.add("SavitzkyGolay")
1562-
}
1563-
} else {
1564-
energy.filters.clear()
1565-
resetSpectrumData(energy)
1566-
}
1556+
private fun alreadyModified(modifierName: String): Boolean {
1557+
val alreadyModified = spectrumDataSet.derivedSpectra.any { derived ->
1558+
derived.value.modifiers.any { it.modifierName == modifierName}
15671559
}
1560+
return alreadyModified
15681561
}
15691562

1570-
private fun resetSpectrumData(energySpectrum: EnergySpectrum){
1571-
energySpectrum.outputSpectrum =
1572-
energySpectrum.spectrum.map { count ->
1573-
count.toDouble()
1574-
}.toMutableList()
1563+
private fun alreadyModified(modifierName: String, index: Int): Boolean {
1564+
val alreadyModified = spectrumDataSet.derivedSpectra.any { derived ->
1565+
derived.value.modifiers.any { it.modifierName == modifierName && it.inputIndexes == listOf(index)}
1566+
}
1567+
return alreadyModified
15751568
}
15761569

1577-
private fun toggleLogScaleFilter(){
1578-
for (entry in spectrumDataSet.data) {
1579-
val energy = entry.resultData.energySpectrum
1580-
if (!energy.filters.contains("LogScale")) {
1581-
// Apply filter and add tag
1582-
applyLogScale(entry, true)
1583-
} else {
1584-
applyLogScale(entry, false)
1570+
private fun applySavitzkyGolayFilter(apply: Boolean) {
1571+
if (apply) {
1572+
spectrumDataSet.data.forEachIndexed { index, entry ->
1573+
// Skip if a SavitzkyGolay-derived spectrum already exists for this input
1574+
val alreadyModified = alreadyModified("SavitzkyGolay", index)
1575+
1576+
if (alreadyModified) return@forEachIndexed
1577+
1578+
val inputSpectrum = entry.resultData.energySpectrum.spectrum.map { it.toDouble() }
1579+
1580+
val modified = SpectrumModifier.applySavitzkyGolayFilter(inputSpectrum)
1581+
1582+
val derivedEntry = DerivedSpectrumEntry(
1583+
name = "${entry.deviceData.deviceName} - SavitzkyGolay",
1584+
resultSpectrum = modified,
1585+
modifiers = mutableListOf(
1586+
ModifierInfo(
1587+
modifierName = "SavitzkyGolay",
1588+
inputIndexes = listOf(index)
1589+
)
1590+
)
1591+
)
1592+
1593+
spectrumDataSet.derivedSpectra[index] = derivedEntry
15851594
}
1595+
} else {
1596+
// Remove all derived spectra and replace with raw versions
1597+
resetSpectrumData(spectrumDataSet)
1598+
}
1599+
}
1600+
1601+
private fun resetSpectrumData(dataSet: OpenGammaKitData){
1602+
// Remove all derived spectra and replace with raw versions
1603+
dataSet.derivedSpectra.clear()
1604+
dataSet.data.forEachIndexed { index, entry ->
1605+
val raw = entry.resultData.energySpectrum.spectrum.map { it.toDouble() }
1606+
1607+
val baseEntry = DerivedSpectrumEntry(
1608+
name = "Raw",
1609+
resultSpectrum = raw,
1610+
modifiers = mutableListOf() // Now empty
1611+
)
1612+
1613+
spectrumDataSet.derivedSpectra[index] = baseEntry
15861614
}
15871615
}
15881616

1589-
fun applyLogScale(entry: GammaKitEntry, apply: Boolean) {
1590-
entry.resultData.energySpectrum.applyLogScale(apply)
1617+
private fun toggleLogScaleFilter() {
1618+
val alreadyModified = alreadyModified("LogScale")
1619+
1620+
if (!alreadyModified) {
1621+
spectrumDataSet.data.forEachIndexed { index, entry ->
1622+
applyLogScale(spectrumDataSet, entry, index, apply = true)
1623+
}
1624+
} else {
1625+
// Reset: clear all derived spectra and add raw versions only
1626+
resetSpectrumData(spectrumDataSet)
1627+
}
15911628
}
15921629

1593-
fun EnergySpectrum.applyLogScale(apply: Boolean) {
1630+
fun applyLogScale(
1631+
dataSet: OpenGammaKitData,
1632+
entry: GammaKitEntry,
1633+
index: Int,
1634+
apply: Boolean
1635+
) {
15941636
if (apply) {
1595-
if (!filters.contains("LogScale")) {
1596-
val inputSpectrum = if(filters.isNotEmpty()) {
1597-
outputSpectrum
1598-
} else {
1599-
spectrum.map { it.toDouble() }
1600-
}
1601-
outputSpectrum = inputSpectrum.map { count ->
1602-
val adjusted = if (count > 1L) count.toDouble() else 1.0
1637+
1638+
val alreadyModified = alreadyModified("LogScale", index)
1639+
1640+
if (!alreadyModified) {
1641+
val inputSpectrum = entry.resultData.energySpectrum.spectrum.map { it.toDouble() }
1642+
val logScaled = inputSpectrum.map { count ->
1643+
val adjusted = if (count > 1.0) count else 1.0
16031644
log10(adjusted)
1604-
}.toMutableList()
1605-
filters.add("LogScale")
1645+
}
1646+
1647+
val derivedEntry = DerivedSpectrumEntry(
1648+
name = "${entry.deviceData.deviceName} - LogScale",
1649+
resultSpectrum = logScaled,
1650+
modifiers = mutableListOf(
1651+
ModifierInfo(
1652+
modifierName = "LogScale",
1653+
inputIndexes = listOf(index)
1654+
)
1655+
)
1656+
)
1657+
1658+
spectrumDataSet.derivedSpectra[index] = derivedEntry
16061659
}
1607-
} else {
1608-
filters.clear()
1609-
resetSpectrumData(this)
16101660
}
16111661
}
16121662

app/src/main/kotlin/io/github/vikulin/opengammakit/math/SpectrumModifier.kt

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package io.github.vikulin.opengammakit.math
22

3-
import io.github.vikulin.opengammakit.model.GammaKitEntry
43
import io.github.vikulin.opengammakit.model.OpenGammaKitData
54
import io.github.vikulin.opengammakit.model.PeakInfo
65
import org.apache.commons.math3.linear.Array2DRowRealMatrix
@@ -58,11 +57,10 @@ object SpectrumModifier {
5857
return result
5958
}
6059

61-
fun applySavitzkyGolayFilter(spectrum: List<Double>, entry: GammaKitEntry) {
62-
63-
if (spectrum.size < 31) return // Too small for filtering
64-
val smoothed = savitzkyGolay(spectrum.toDoubleArray(), windowSize = 31, polyOrder = 3)
65-
entry.resultData.energySpectrum.outputSpectrum = smoothed.map { it }.toMutableList()
60+
fun applySavitzkyGolayFilter(spectrum: List<Double>): List<Double> {
61+
if (spectrum.size < 31) return spectrum // Too small for filtering, return unchanged
62+
val smoothed = savitzkyGolay(spectrum.toDoubleArray(), windowSize = 31, polyOrder = 3)
63+
return smoothed.toList()
6664
}
6765

6866
// Public method to detect peaks in selected spectrums of a dataset
@@ -77,7 +75,8 @@ object SpectrumModifier {
7775
val detectedPeaks = mutableListOf<PeakInfo>()
7876

7977
for (spectrumIndex in indexesToAnalyze) {
80-
val spectrum = dataSet.data[spectrumIndex].resultData.energySpectrum.outputSpectrum.map { it.toDouble() }
78+
val spectrum = dataSet.derivedSpectra[spectrumIndex]?.resultSpectrum
79+
if(spectrum.isNullOrEmpty()) return detectedPeaks
8180
val correctedSpectrum = if (estimateBaseline) removeBaseline(spectrum) else spectrum
8281

8382
val waveletWidthList = waveletWidths.toList()
@@ -117,7 +116,7 @@ object SpectrumModifier {
117116
val groupedPeaks = groupPeaksByProximity(localMaxima, peakProximityThreshold)
118117

119118
detectedPeaks.addAll(groupedPeaks)
120-
dataSet.data[spectrumIndex].resultData.energySpectrum.peaks.addAll(groupedPeaks)
119+
dataSet.derivedSpectra[spectrumIndex]?.peaks?.addAll(groupedPeaks)
121120
}
122121

123122
return detectedPeaks

0 commit comments

Comments
 (0)