From 9869c38530d1154957cc7e432742f460a3d5d069 Mon Sep 17 00:00:00 2001
From: Tamas Gal <tgal@km3net.de>
Date: Mon, 3 Feb 2025 21:51:46 +0000
Subject: [PATCH] Add JSON I/O

---
 Project.toml     |  1 +
 src/KM3io.jl     |  2 ++
 src/exports.jl   |  3 +++
 src/hardware.jl  |  1 +
 src/json.jl      | 56 +++++++++++++++++++++++++++++++++++++++++++++++
 test/json.jl     | 57 ++++++++++++++++++++++++++++++++++++++++++++++++
 test/runtests.jl |  1 +
 7 files changed, 121 insertions(+)
 create mode 100644 src/json.jl
 create mode 100644 test/json.jl

diff --git a/Project.toml b/Project.toml
index 8492bec2..d6002054 100644
--- a/Project.toml
+++ b/Project.toml
@@ -7,6 +7,7 @@ version = "0.17.9"
 Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
 DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
 HDF5 = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f"
+JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
 LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
 Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
 Requires = "ae029012-a4dd-5104-9daa-d747884805df"
diff --git a/src/KM3io.jl b/src/KM3io.jl
index c3f8394f..3b6a91b8 100644
--- a/src/KM3io.jl
+++ b/src/KM3io.jl
@@ -8,6 +8,7 @@ using Dates: DateTime, datetime2unix, unix2datetime
 using Sockets
 using UUIDs
 using TOML
+using JSON
 
 if !isdefined(Base, :get_extension)
     using Requires
@@ -69,6 +70,7 @@ include("daq.jl")
 include("acoustics.jl")
 include("calibration.jl")
 include("controlhost.jl")
+include("json.jl")
 
 include("tools/general.jl")
 include("tools/daq.jl")
diff --git a/src/exports.jl b/src/exports.jl
index 984ddd05..643187f4 100644
--- a/src/exports.jl
+++ b/src/exports.jl
@@ -80,6 +80,9 @@ Trk,
 XCalibratedHit,
 FitInformation,
 
+# Misc I/O
+tojson,
+
 # Calibration
 AbstractCalibratedHit,
 K40Rates,
diff --git a/src/hardware.jl b/src/hardware.jl
index 54fc88cf..9f8b75cd 100644
--- a/src/hardware.jl
+++ b/src/hardware.jl
@@ -282,6 +282,7 @@ struct Detector
     comments::Vector{String}
     _pmt_id_module_map::Dict{Int, DetectorModule}
 end
+
 """
 Return a vector of all modules of a given detector.
 """
diff --git a/src/json.jl b/src/json.jl
new file mode 100644
index 00000000..d1250c57
--- /dev/null
+++ b/src/json.jl
@@ -0,0 +1,56 @@
+"""
+    function tojson(filename::AbstractString, e::Evt)
+
+Writes an offline event ([`Evt`]@ref) as a JSON string to a file.
+"""
+function tojson(filename::AbstractString, event::Evt, detector::Detector)
+    open(filename, "w") do io
+        tojson(io, event, detector)
+    end
+end
+
+function tojson(io::IO, event::Evt, detector::Detector)
+    bt = bestjppmuon(event)
+    tâ‚€ = first(event.hits).t
+
+    hits = [
+        (
+            dom_id = h.dom_id,
+            channel_id = h.channel_id,
+            floor = detector[h.dom_id].location.floor,
+            detection_unit = detector[h.dom_id].location.string,
+	    tdc = h.tdc,
+            t = h.t - tâ‚€,
+            tot = h.tot,
+            pos_x = h.pos.x, pos_y = h.pos.y, pos_z = h.pos.z,
+            dir_x = h.dir.x, dir_y = h.dir.y, dir_z = h.dir.z,
+            triggered = h.trigger_mask > 0
+        ) for h in event.hits
+    ]
+    if !ismissing(bt)
+        bt = (
+            pos_x = bt.pos.x, pos_y = bt.pos.y, pos_z = bt.pos.z,
+            dir_x = bt.dir.x, dir_y = bt.dir.y, dir_z = bt.dir.z,
+            t = bt.t - tâ‚€
+        )
+    end
+
+    JSON.print(io, (utc_timestamp=event.t.s + (event.t.ns + tâ‚€)/1e9, hits=hits, reconstructed_track=bt))
+end
+
+function tojson(filename::AbstractString, detector::Detector)
+    open(filename, "w") do io
+        tojson(io, detector)
+    end
+end
+
+function tojson(io::IO, detector::Detector)
+    modules = [
+        (
+            id=m.id, detection_unit=m.location.string, floor=m.location.floor, pos_x = m.pos.x, pos_y = m.pos.y, pos_z = m.pos.z,
+            pmts=[(id=channel_id - 1, pos_x=p.pos.x, pos_y=p.pos.y, pos_z=p.pos.z, dir_x=p.dir.x, dir_y=p.dir.y, dir_z=p.dir.z) for (channel_id, p) in enumerate(m)]
+        )
+        for m in detector
+    ]
+    JSON.print(io, modules)
+end
diff --git a/test/json.jl b/test/json.jl
new file mode 100644
index 00000000..be8be391
--- /dev/null
+++ b/test/json.jl
@@ -0,0 +1,57 @@
+using KM3io
+using JSON
+using KM3NeTTestData
+using Test
+
+
+const OFFLINEFILE = datapath("offline", "km3net_offline.root")
+
+
+@testset "JSON output" begin
+    f = ROOTFile(OFFLINEFILE)
+    e = f.offline[1]
+    d = Detector(datapath("detx", "km3net_offline.detx"))
+
+    outfile = tempname()
+    tojson(outfile, e, d)
+
+    json_evt = JSON.parsefile(outfile)
+    json_hits = json_evt["hits"]
+    @test 1.567036818270104e9 == json_evt["utc_timestamp"]
+
+    tâ‚€ = first(e.hits).t
+
+    bt = bestjppmuon(e)
+    json_bt = json_evt["reconstructed_track"]
+    @test bt.pos.x == json_bt["pos_x"]
+    @test bt.pos.y == json_bt["pos_y"]
+    @test bt.pos.z == json_bt["pos_z"]
+    @test bt.dir.x == json_bt["dir_x"]
+    @test bt.dir.y == json_bt["dir_y"]
+    @test bt.dir.z == json_bt["dir_z"]
+    @test bt.t == json_bt["t"] + tâ‚€
+
+    @test 176 == length(json_hits)
+    for idx in 1:length(json_hits)
+        orig_hit = e.hits[idx]
+        json_hit = json_hits[idx]
+        @test orig_hit.t == json_hit["t"] + tâ‚€
+        @test orig_hit.pos.x == json_hit["pos_x"]
+        @test orig_hit.pos.y == json_hit["pos_y"]
+        @test orig_hit.pos.z == json_hit["pos_z"]
+        @test orig_hit.dir.x == json_hit["dir_x"]
+        @test orig_hit.dir.y == json_hit["dir_y"]
+        @test orig_hit.dir.z == json_hit["dir_z"]
+        @test orig_hit.channel_id == json_hit["channel_id"]
+        @test (orig_hit.trigger_mask > 0) == json_hit["triggered"]
+
+        orig_dom_id = orig_hit.dom_id
+        dom_id = json_hit["dom_id"]
+        @test dom_id == orig_dom_id
+        m = d[dom_id]
+        @test json_hit["floor"] == m.location.floor
+        @test json_hit["detection_unit"] == m.location.string
+    end
+
+
+end
diff --git a/test/runtests.jl b/test/runtests.jl
index 10c19d7f..8a277144 100644
--- a/test/runtests.jl
+++ b/test/runtests.jl
@@ -2,6 +2,7 @@ using Test
 
 include("root.jl")
 include("hdf5.jl")
+include("json.jl")
 include("daq.jl")
 include("tools.jl")
 include("hardware.jl")
-- 
GitLab