diff --git a/build-coatjava.sh b/build-coatjava.sh
index 1586b1fa05..8cc3a57168 100755
--- a/build-coatjava.sh
+++ b/build-coatjava.sh
@@ -395,6 +395,8 @@ done
for pom in $(find common-tools -name pom.xml); do
if [[ "$pom" =~ coat-libs ]]; then
install_jars $pom $prefix_dir/lib/clas 'coat-libs-*.jar'
+ elif [[ "$pom" =~ clas-qcddat ]]; then
+ install_jars $pom $prefix_dir/lib/services
# else # FIXME, consumers may be need these after https://github.com/JeffersonLab/coatjava/pull/632 ; alternatively add needed deps to `coat-libs`
# install_jars $pom $prefix_dir/lib/services
fi
diff --git a/common-tools/clas-qcddat/README.md b/common-tools/clas-qcddat/README.md
new file mode 100644
index 0000000000..59fb897b2f
--- /dev/null
+++ b/common-tools/clas-qcddat/README.md
@@ -0,0 +1,17 @@
+# QCDDAT Tools
+
+> [!NOTE]
+> See [documentation from Veronique](https://clasweb.jlab.org/wiki/images/d/d0/CVT_QCDDAT_Subpackage_Documentation.pdf), the original
+> developer; see also [Veronique's wikipage for further documentation](https://clasweb.jlab.org/wiki/index.php/Veronique_Ziegler_Documentation).
+
+## `CVT::QCDDATHit` Bank Validation
+
+Run reconstruction, _e.g._,
+```bash
+run-clara -y $COATJAVA/etc/services/mc-qcddat.yaml -t 4 -n 500 -c ./clara -o ./clarout raw.evio
+```
+
+Run the CVT event display:
+```bash
+run-coatjava org.jlab.qcddat.CVTBrowser clarout/rec_raw.evio.hipo
+```
diff --git a/common-tools/clas-qcddat/pom.xml b/common-tools/clas-qcddat/pom.xml
new file mode 100644
index 0000000000..b3401fb771
--- /dev/null
+++ b/common-tools/clas-qcddat/pom.xml
@@ -0,0 +1,39 @@
+
+
+ 4.0.0
+
+ org.jlab.clas
+ clas-qcddat
+ 14.2.0-SNAPSHOT
+ jar
+
+
+ org.jlab.clas
+ common-tools
+ 14.2.0-SNAPSHOT
+
+
+
+
+ org.jlab.clas
+ clas-io
+ 14.2.0-SNAPSHOT
+
+
+ org.openjfx
+ javafx-base
+ linux
+
+
+ org.openjfx
+ javafx-graphics
+ linux
+
+
+ org.openjfx
+ javafx-controls
+ linux
+
+
+
+
diff --git a/common-tools/clas-qcddat/src/main/java/org/jlab/qcddat/CVTBrowser.java b/common-tools/clas-qcddat/src/main/java/org/jlab/qcddat/CVTBrowser.java
new file mode 100644
index 0000000000..843dfe4fd9
--- /dev/null
+++ b/common-tools/clas-qcddat/src/main/java/org/jlab/qcddat/CVTBrowser.java
@@ -0,0 +1,22 @@
+package org.jlab.qcddat;
+
+import javafx.application.Application;
+/**
+ *
+ * @author veronique
+ */
+public class CVTBrowser {
+
+ public static void main(String[] args) {
+ if (args.length < 1) {
+ System.err.println("Usage: java Viewer ");
+ System.err.println("Example: java Viewer file.hipo");
+ System.exit(1);
+ }
+
+ String inputFile = args[0];
+
+ CVTViewer.configure(inputFile);
+ Application.launch(CVTViewer.class);
+ }
+}
diff --git a/common-tools/clas-qcddat/src/main/java/org/jlab/qcddat/CVTViewer.java b/common-tools/clas-qcddat/src/main/java/org/jlab/qcddat/CVTViewer.java
new file mode 100644
index 0000000000..25b735956a
--- /dev/null
+++ b/common-tools/clas-qcddat/src/main/java/org/jlab/qcddat/CVTViewer.java
@@ -0,0 +1,682 @@
+package org.jlab.qcddat;
+
+import javafx.application.Application;
+import javafx.geometry.Insets;
+import javafx.scene.*;
+import javafx.scene.control.Button;
+import javafx.scene.control.CheckBox;
+import javafx.scene.control.Label;
+import javafx.scene.control.Separator;
+import javafx.scene.input.KeyCode;
+import javafx.scene.input.MouseEvent;
+import javafx.scene.input.ScrollEvent;
+import javafx.scene.layout.*;
+import javafx.scene.paint.Color;
+import javafx.scene.paint.PhongMaterial;
+import javafx.scene.shape.Box;
+import javafx.scene.shape.Sphere;
+import javafx.scene.transform.Rotate;
+import javafx.stage.Stage;
+import org.jlab.io.base.DataBank;
+import org.jlab.io.base.DataEvent;
+import org.jlab.io.hipo.HipoDataSource;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ *
+ * @author veronique
+ */
+
+public class CVTViewer extends Application {
+
+ // ---------- config ----------
+ private static String inputFile;
+ private static String bankName;
+
+ public static void configure(String input) {
+ inputFile = input;
+ bankName = "CVT::QCDDATHit";
+ }
+
+ public static void main(String[] args) {
+ if (args.length < 1) {
+ System.err.println("Usage: java org.jlab.qcddat.CVTViewer ");
+ System.exit(1);
+ }
+ configure(args[0]);
+ launch(args);
+ }
+
+ // ---------- bounded cache ----------
+ private static final int MAX_CACHE_SIZE = 200;
+ private static final int MAX_LAYER = 12;
+ private static final int MAX_SECTOR = 18;
+
+ private HipoDataSource reader;
+ private final List> eventPointCache = new ArrayList<>();
+ private final List eventRowCountCache = new ArrayList<>();
+
+ // global event number of first cached event
+ private int cacheStartEventNumber = 0;
+
+ // current global event number
+ private int currentEventNumber = -1;
+
+ // next global event number expected from persistent reader
+ private int nextUnreadEventNumber = 0;
+
+ private int totalEvents = 0;
+
+ // ---------- gui ----------
+ private CheckBox showLoc1;
+ private CheckBox showLoc2;
+ private CheckBox showLoc3;
+ private CheckBox[] showLayer = new CheckBox[MAX_LAYER];
+ private CheckBox[] showSector = new CheckBox[MAX_SECTOR];
+ private CheckBox showSVT;
+ private CheckBox showBMTC;
+ private CheckBox showBMTZ;
+ private CheckBox showPersistence;
+
+ private Label hoverLabel;
+ private Label infoLabel;
+ private Label countsLabel;
+
+ // ---------- 3D ----------
+ private final Group world = new Group();
+ private final Group pointsGroup = new Group();
+
+ private final Rotate rotateX = new Rotate(20, Rotate.X_AXIS);
+ private final Rotate rotateY = new Rotate(-35, Rotate.Y_AXIS);
+
+ private double anchorX;
+ private double anchorY;
+ private double anchorAngleX;
+ private double anchorAngleY;
+
+ private PerspectiveCamera camera;
+
+ private static final double DRAW_SCALE = 5.0;
+ private static final double POINT_RADIUS = 1.8;
+
+ private enum DetectorKind {
+ SVT,
+ BMT_C,
+ BMT_Z,
+ UNKNOWN
+ }
+
+ private static class HitPoint {
+ final double x;
+ final double y;
+ final double z;
+ final DetectorKind kind;
+ final int pointloc;
+ final int layer;
+ final int sector;
+ final int mctrue;
+
+ HitPoint(double x, double y, double z, DetectorKind kind, int loc, int layer, int sector, int mct) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ this.kind = kind;
+ this.pointloc = loc;
+ this.layer = layer;
+ this.sector = sector;
+ this.mctrue = mct;
+ }
+ }
+
+ @Override
+ public void start(Stage stage) {
+ if (inputFile == null || bankName == null) {
+ throw new IllegalStateException("CVTViewer.configure(inputFile) must be called before launch.");
+ }
+
+ initializeFile(inputFile);
+ buildWorld();
+
+ camera = new PerspectiveCamera(true);
+ camera.setNearClip(0.1);
+ camera.setFarClip(100000);
+ camera.setTranslateZ(-1400);
+
+ SubScene subScene = new SubScene(world, 1200, 850, true, SceneAntialiasing.BALANCED);
+ subScene.setFill(Color.rgb(18, 18, 22));
+ subScene.setCamera(camera);
+
+ enableMouseControls(subScene);
+
+ BorderPane root = new BorderPane();
+ root.setCenter(subScene);
+ root.setTop(buildTopBar());
+ root.setRight(buildLegendPane());
+
+ Scene scene = new Scene(root, 1450, 900, true);
+
+ scene.setOnKeyPressed(e -> {
+ if (e.getCode() == KeyCode.RIGHT || e.getCode() == KeyCode.N) {
+ nextEvent();
+ } else if (e.getCode() == KeyCode.LEFT || e.getCode() == KeyCode.P) {
+ previousEvent();
+ } else if (e.getCode() == KeyCode.PLUS || e.getCode() == KeyCode.ADD || (e.getCode() == KeyCode.EQUALS && e.isShiftDown())) {
+ camera.setTranslateZ(camera.getTranslateZ() + 50);
+ } else if (e.getCode() == KeyCode.MINUS || e.getCode() == KeyCode.SUBTRACT) {
+ camera.setTranslateZ(camera.getTranslateZ() - 50);
+ } else if (e.getCode() == KeyCode.R) {
+ resetView();
+ }
+ });
+
+ stage.setTitle("COATJAVA Bank 3D Event Browser");
+ stage.setScene(scene);
+ stage.show();
+
+ if (totalEvents == 0) {
+ infoLabel.setText("No events found in file: " + inputFile);
+ countsLabel.setText("");
+ } else if (loadNextEventIntoCache()) {
+ renderCachedEvent();
+ } else {
+ infoLabel.setText("Could not load first event");
+ countsLabel.setText("");
+ }
+ }
+
+ private void initializeFile(String fileName) {
+ File f = new File(fileName);
+ if (!f.exists()) {
+ throw new IllegalArgumentException("Input file does not exist: " + fileName);
+ }
+
+ HipoDataSource counter = new HipoDataSource();
+ counter.open(fileName);
+ totalEvents = 0;
+ while (counter.hasEvent()) {
+ counter.getNextEvent();
+ totalEvents++;
+ }
+ counter.close();
+
+ reader = new HipoDataSource();
+ reader.open(fileName);
+
+ eventPointCache.clear();
+ eventRowCountCache.clear();
+
+ cacheStartEventNumber = 0;
+ currentEventNumber = -1;
+ nextUnreadEventNumber = 0;
+
+ System.out.printf("Initialized file %s with %d events%n", fileName, totalEvents);
+ }
+
+ private boolean loadNextEventIntoCache() {
+ if (reader == null || !reader.hasEvent()) {
+ return false;
+ }
+
+ DataEvent event = reader.getNextEvent();
+ int thisEventNumber = nextUnreadEventNumber;
+ nextUnreadEventNumber++;
+
+ if (!event.hasBank(bankName)) {
+ return false;
+ }
+
+ DataBank bank = event.getBank(bankName);
+ List points = extractAllThreePoints(bank);
+
+ eventPointCache.add(points);
+ eventRowCountCache.add(bank.rows());
+
+ if (eventPointCache.size() > MAX_CACHE_SIZE) {
+ eventPointCache.remove(0);
+ eventRowCountCache.remove(0);
+ cacheStartEventNumber++;
+ }
+
+ currentEventNumber = thisEventNumber;
+ return true;
+ }
+
+ private boolean isCurrentEventCached() {
+ return currentEventNumber >= cacheStartEventNumber
+ && currentEventNumber < cacheStartEventNumber + eventPointCache.size();
+ }
+
+ private int currentCacheIndex() {
+ return currentEventNumber - cacheStartEventNumber;
+ }
+
+ private void renderCachedEvent() {
+ if(!showPersistence.isSelected())
+ pointsGroup.getChildren().clear();
+
+ if (!isCurrentEventCached()) {
+ infoLabel.setText(String.format(
+ "Event %d is no longer in cache. Cache window: [%d .. %d]",
+ currentEventNumber + 1,
+ cacheStartEventNumber + 1,
+ cacheStartEventNumber + eventPointCache.size()
+ ));
+ countsLabel.setText("");
+ hoverLabel.setText("Hover over a point to see coordinates");
+ return;
+ }
+
+ int cacheIndex = currentCacheIndex();
+ List points = eventPointCache.get(cacheIndex);
+ int rowCount = eventRowCountCache.get(cacheIndex);
+
+ int nSVT = 0;
+ int nBMTC = 0;
+ int nBMTZ = 0;
+ int nLoc1 = 0;
+ int nLoc2 = 0;
+ int nLoc3 = 0;
+ int nMcTrue0 = 0;
+ int nOther = 0;
+
+ for (HitPoint p : points) {
+ if (!isVisible(p.kind) || !isVisibleLoc(p.pointloc) || !isVisibleLayer(p.layer) || !isVisibleSector(p.sector)) {
+ continue;
+ }
+
+ Node marker = makeMarker(p);
+ pointsGroup.getChildren().add(marker);
+
+ switch (p.kind) {
+ case SVT -> nSVT++;
+ case BMT_C -> nBMTC++;
+ case BMT_Z -> nBMTZ++;
+ default -> { }
+ }
+
+ switch (p.pointloc) {
+ case 1 -> nLoc1++;
+ case 2 -> nLoc2++;
+ case 3 -> nLoc3++;
+ default -> { }
+ }
+
+ if (p.mctrue == 0) {
+ nMcTrue0++;
+ } else {
+ nOther++;
+ }
+ }
+
+ infoLabel.setText(String.format(
+ "File: %s Bank: %s Event %d / %d Rows: %d Cache: [%d .. %d]",
+ new File(inputFile).getName(),
+ bankName,
+ currentEventNumber + 1,
+ totalEvents,
+ rowCount,
+ cacheStartEventNumber + 1,
+ cacheStartEventNumber + eventPointCache.size()
+ ));
+
+ countsLabel.setText(String.format(
+ "SVT=%d BMT_C=%d BMT_Z=%d loc1=%d loc2=%d loc3=%d mctrue0=%d other=%d",
+ nSVT, nBMTC, nBMTZ, nLoc1, nLoc2, nLoc3, nMcTrue0, nOther
+ ));
+ }
+
+ private void buildWorld() {
+ world.getTransforms().addAll(rotateX, rotateY);
+
+ Group axes = new Group(
+ makeAxis(300, 1.0, 1.0, Color.RED),
+ makeAxis(1.0, 300, 1.0, Color.LIME),
+ makeAxis(1.0, 1.0, 300, Color.DEEPSKYBLUE)
+ );
+
+ world.getChildren().add(axes);
+ world.getChildren().add(pointsGroup);
+ }
+
+ private Node makeAxis(double sx, double sy, double sz, Color color) {
+ Box box = new Box(sx, sy, sz);
+ box.setMaterial(new PhongMaterial(color));
+ return box;
+ }
+
+ private HBox buildTopBar() {
+ Button prev = new Button("Previous Event");
+ Button next = new Button("Next Event");
+ Button reset = new Button("Reset View");
+
+ prev.setOnAction(e -> previousEvent());
+ next.setOnAction(e -> nextEvent());
+ reset.setOnAction(e -> resetView());
+
+ infoLabel = new Label("Loading...");
+ countsLabel = new Label("");
+ hoverLabel = new Label("Hover over a point to see coordinates");
+
+ infoLabel.setTextFill(Color.BLACK);
+ countsLabel.setTextFill(Color.BLACK);
+ hoverLabel.setTextFill(Color.DARKBLUE);
+
+ HBox controls = new HBox(
+ 10,
+ prev, next, reset,
+ new Separator(),
+ infoLabel,
+ new Separator(),
+ countsLabel,
+ new Separator(),
+ hoverLabel
+ );
+ controls.setPadding(new Insets(10));
+ controls.setStyle("-fx-background-color: #e9edf2;");
+ return controls;
+ }
+
+ private HBox buildLegendPane() {
+
+ Label persistenceTitle = new Label("Persistence");
+ persistenceTitle.setStyle("-fx-font-size: 14px; -fx-font-weight: bold;");
+ showPersistence = new CheckBox("Persist");
+ showPersistence.setSelected(false);
+ Button clearPersistence = new Button("Clear");
+ clearPersistence.setOnAction(e -> { pointsGroup.getChildren().clear(); });
+
+ Label legendTitle = new Label("Detectors");
+ legendTitle.setStyle("-fx-font-size: 14px; -fx-font-weight: bold;");
+
+ showSVT = new CheckBox("SVT");
+ showSVT.setSelected(true);
+ showBMTC = new CheckBox("BMT C");
+ showBMTC.setSelected(true);
+ showBMTZ = new CheckBox("BMT Z");
+ showBMTZ.setSelected(true);
+
+ showSVT.setOnAction(e -> renderCachedEvent());
+ showBMTC.setOnAction(e -> renderCachedEvent());
+ showBMTZ.setOnAction(e -> renderCachedEvent());
+
+ Label svtColor = coloredLabel("Magenta");
+ svtColor.setTextFill(Color.MAGENTA);
+
+ Label cColor = coloredLabel("LimeGreen");
+ cColor.setTextFill(Color.LIMEGREEN);
+
+ Label zColor = coloredLabel("Cyan");
+ zColor.setTextFill(Color.CYAN);
+
+ Label locTitle = new Label("Point location");
+ locTitle.setStyle("-fx-font-size: 14px; -fx-font-weight: bold;");
+
+ showLoc1 = new CheckBox("loc 1 = origin");
+ showLoc1.setSelected(true);
+ showLoc2 = new CheckBox("loc 2 = midpoint");
+ showLoc2.setSelected(true);
+ showLoc3 = new CheckBox("loc 3 = end");
+ showLoc3.setSelected(true);
+
+ showLoc1.setOnAction(e -> renderCachedEvent());
+ showLoc2.setOnAction(e -> renderCachedEvent());
+ showLoc3.setOnAction(e -> renderCachedEvent());
+
+ Label layerTitle = new Label("Layer");
+ layerTitle.setStyle("-fx-font-size: 14px; -fx-font-weight: bold;");
+ for (int l=0; l renderCachedEvent());
+ }
+ Button layerNone = new Button("None");
+ layerNone.setOnAction(e -> { for(int l=0; l { for(int l=0; l renderCachedEvent());
+ }
+ Button sectorNone = new Button("None");
+ sectorNone.setOnAction(e -> { for(int s=0; s { for(int s=0; s {
+ anchorX = e.getSceneX();
+ anchorY = e.getSceneY();
+ anchorAngleX = rotateX.getAngle();
+ anchorAngleY = rotateY.getAngle();
+ });
+
+ scene.addEventHandler(MouseEvent.MOUSE_DRAGGED, e -> {
+ rotateX.setAngle(anchorAngleX - (anchorY - e.getSceneY()) * 0.35);
+ rotateY.setAngle(anchorAngleY + (anchorX - e.getSceneX()) * 0.35);
+ });
+
+ scene.addEventHandler(ScrollEvent.SCROLL, e -> {
+ camera.setTranslateZ(camera.getTranslateZ() + e.getDeltaY() * 0.6);
+ });
+ }
+
+ private void resetView() {
+ rotateX.setAngle(20);
+ rotateY.setAngle(-35);
+ if (camera != null) {
+ camera.setTranslateZ(-1400);
+ }
+ }
+
+ private void previousEvent() {
+ if (totalEvents == 0) {
+ return;
+ }
+ if (currentEventNumber > cacheStartEventNumber) {
+ currentEventNumber--;
+ renderCachedEvent();
+ } else if (currentEventNumber > 0) {
+ infoLabel.setText(String.format(
+ "Cannot go back farther: event %d is outside the cache window [%d .. %d]",
+ currentEventNumber,
+ cacheStartEventNumber + 1,
+ cacheStartEventNumber + eventPointCache.size()
+ ));
+ }
+ }
+
+ private void nextEvent() {
+ if (totalEvents == 0) {
+ return;
+ }
+
+ if (currentEventNumber + 1 < nextUnreadEventNumber) {
+ currentEventNumber++;
+ renderCachedEvent();
+ return;
+ }
+
+ if (loadNextEventIntoCache()) {
+ renderCachedEvent();
+ }
+ }
+
+ private Node makeMarker(HitPoint p) {
+ PhongMaterial material = new PhongMaterial(colorFor(p.kind));
+
+ Node marker;
+ if (p.mctrue == 0) {
+ Box b = new Box(2.8 * POINT_RADIUS, 2.8 * POINT_RADIUS, 2.8 * POINT_RADIUS);
+ b.setMaterial(material);
+ marker = b;
+ } else {
+ Sphere s = new Sphere(POINT_RADIUS);
+ s.setMaterial(material);
+ marker = s;
+ }
+
+ marker.setTranslateX(p.x * DRAW_SCALE);
+ marker.setTranslateY(-p.y * DRAW_SCALE);
+ marker.setTranslateZ(p.z * DRAW_SCALE);
+
+ marker.setOnMouseEntered(e -> hoverLabel.setText(String.format(
+ "%s loc=%d mctrue=%d (x, y, z) = (%.4f, %.4f, %.4f)",
+ detectorName(p.kind), p.pointloc, p.mctrue, p.x, p.y, p.z
+ )));
+
+ marker.setOnMouseExited(e -> hoverLabel.setText("Hover over a point to see coordinates"));
+
+ return marker;
+ }
+
+ private String detectorName(DetectorKind kind) {
+ return switch (kind) {
+ case SVT -> "SVT";
+ case BMT_C -> "BMT_C";
+ case BMT_Z -> "BMT_Z";
+ case UNKNOWN -> "UNKNOWN";
+ };
+ }
+
+ private boolean isVisible(DetectorKind kind) {
+ return switch (kind) {
+ case SVT -> showSVT.isSelected();
+ case BMT_C -> showBMTC.isSelected();
+ case BMT_Z -> showBMTZ.isSelected();
+ default -> true;
+ };
+ }
+
+ private boolean isVisibleLoc(int loc) {
+ return switch (loc) {
+ case 1 -> showLoc1.isSelected();
+ case 2 -> showLoc2.isSelected();
+ case 3 -> showLoc3.isSelected();
+ default -> true;
+ };
+ }
+
+ private boolean isVisibleLayer(int layer) {
+ return showLayer[layer-1].isSelected();
+ }
+
+ private boolean isVisibleSector(int sector) {
+ return showSector[sector-1].isSelected();
+ }
+
+ private List extractAllThreePoints(DataBank bank) {
+ List out = new ArrayList<>();
+
+ for (int i = 0; i < bank.rows(); i++) {
+ int layer = bank.getByte("layer", i);
+ int sector = bank.getByte("sector", i);
+ DetectorKind kind = detectorKindFromLayer(layer);
+ int mct = bank.getByte("mctrue", i);
+
+ out.add(new HitPoint(
+ bank.getFloat("x1", i),
+ bank.getFloat("y1", i),
+ bank.getFloat("z1", i),
+ kind, 1, layer, sector, mct
+ ));
+
+ out.add(new HitPoint(
+ bank.getFloat("x2", i),
+ bank.getFloat("y2", i),
+ bank.getFloat("z2", i),
+ kind, 2, layer, sector, mct
+ ));
+
+ out.add(new HitPoint(
+ bank.getFloat("x3", i),
+ bank.getFloat("y3", i),
+ bank.getFloat("z3", i),
+ kind, 3, layer, sector, mct
+ ));
+ }
+
+ return out;
+ }
+
+ private DetectorKind detectorKindFromLayer(int layer) {
+ if (layer >= 1 && layer <= 6) {
+ return DetectorKind.SVT;
+ }
+ if (layer >= 7 && layer <= 12) {
+ return isBmtCLayer(layer) ? DetectorKind.BMT_C : DetectorKind.BMT_Z;
+ }
+ return DetectorKind.UNKNOWN;
+ }
+
+ private boolean isBmtCLayer(int layer) {
+ return (layer == 7 || layer == 10 || layer == 12);
+ }
+
+ private Color colorFor(DetectorKind kind) {
+ return switch (kind) {
+ case SVT -> Color.MAGENTA;
+ case BMT_C -> Color.LIMEGREEN;
+ case BMT_Z -> Color.CYAN;
+ case UNKNOWN -> Color.WHITE;
+ };
+ }
+}
diff --git a/common-tools/pom.xml b/common-tools/pom.xml
index 2cb363df1a..3f09e738c3 100644
--- a/common-tools/pom.xml
+++ b/common-tools/pom.xml
@@ -29,6 +29,7 @@
clara-io
clas-tracking
clas-decay-tools
+ clas-qcddat
coat-libs
diff --git a/etc/bankdefs/hipo4/cvtqcddat.json b/etc/bankdefs/hipo4/cvtqcddat.json
new file mode 100644
index 0000000000..3a3118c34b
--- /dev/null
+++ b/etc/bankdefs/hipo4/cvtqcddat.json
@@ -0,0 +1,27 @@
+[
+ {
+ "name": "CVT::QCDDATHit",
+ "group": 20500,
+ "item" : 71,
+ "info": "reconstructed hits",
+ "entries": [
+ {"name":"id", "type":"S", "info":"id of the hit"},
+ {"name":"mctid", "type":"S", "info":"MC track id associated with the hit"},
+ {"name":"sector", "type":"B", "info":"sector"},
+ {"name":"layer", "type":"B", "info":"layer (1...6)=SVT; (7...12)=BMT"},
+ {"name":"strip", "type":"S", "info":"strip number"},
+ {"name":"energy", "type":"F", "info":"energy"},
+ {"name":"time", "type":"F", "info":"time"},
+ {"name":"mctrue", "type":"B", "info":"order 0=MC hit-on-track; 1=noise hit"},
+ {"name":"x1", "type":"F", "info":"geometric strip origin x-coordinate"},
+ {"name":"y1", "type":"F", "info":"geometric strip origin y-coordinate"},
+ {"name":"z1", "type":"F", "info":"geometric strip origin z-coordinate"},
+ {"name":"x2", "type":"F", "info":"geometric strip mid-point x-coordinate"},
+ {"name":"y2", "type":"F", "info":"geometric strip mid-point y-coordinate"},
+ {"name":"z2", "type":"F", "info":"geometric strip mid-point z-coordinate"},
+ {"name":"x3", "type":"F", "info":"geometric strip end-point x-coordinate"},
+ {"name":"y3", "type":"F", "info":"geometric strip end-point y-coordinate"},
+ {"name":"z3", "type":"F", "info":"geometric strip end-point z-coordinate"}
+ ]
+ }
+]
diff --git a/etc/services/mc-qcddat.yaml b/etc/services/mc-qcddat.yaml
new file mode 100644
index 0000000000..147d5dcea3
--- /dev/null
+++ b/etc/services/mc-qcddat.yaml
@@ -0,0 +1,26 @@
+io-services:
+ reader:
+ class: org.jlab.io.clara.Clas12Reader
+ name: Clas12Reader
+ writer:
+ class: org.jlab.io.clara.Clas12Writer
+ name: Clas12Writer
+services:
+ - class: org.jlab.clas.reco.DecoderEngine
+ name: DECO
+ - class: org.jlab.clas.swimtools.MagFieldsEngine
+ name: MAGFIELDS
+ - class: org.jlab.rec.cvt.ml.QCDDATSampleMaker
+ name: CVTFP
+configuration:
+ global:
+ variation: rga_fall2018_bg
+ io-services:
+ writer:
+ schema_dir: cvtqcddat
+ services:
+ MAGFIELDS:
+ magfieldSolenoidMap: Symm_solenoid_r601_phi1_z1201_13June2018.dat
+ magfieldTorusMap: Symm_torus_r2501_phi16_z251_24Apr2018.dat
+mime-types:
+ - binary/data-hipo
diff --git a/pom.xml b/pom.xml
index 0334839d09..a2cf0e6699 100644
--- a/pom.xml
+++ b/pom.xml
@@ -102,6 +102,13 @@
linux
+
+ org.openjfx
+ javafx-controls
+ 23.0.2
+ linux
+
+
org.slf4j
slf4j-api
diff --git a/reconstruction/cvt/src/main/java/org/jlab/rec/cvt/Constants.java b/reconstruction/cvt/src/main/java/org/jlab/rec/cvt/Constants.java
index 51303f0636..1ae3a6267f 100644
--- a/reconstruction/cvt/src/main/java/org/jlab/rec/cvt/Constants.java
+++ b/reconstruction/cvt/src/main/java/org/jlab/rec/cvt/Constants.java
@@ -20,7 +20,7 @@ public class Constants {
public static double CAANGLE4=19.;
public static double CAANGLE5=3.5;
public boolean seedingDebugMode =false;
-
+ public boolean QCDDATSample=true; // FIXME(CD): should this be configurable from `yaml`?
// private constructor for a singleton
diff --git a/reconstruction/cvt/src/main/java/org/jlab/rec/cvt/banks/HitReader.java b/reconstruction/cvt/src/main/java/org/jlab/rec/cvt/banks/HitReader.java
index 86174629b7..a243f2272e 100644
--- a/reconstruction/cvt/src/main/java/org/jlab/rec/cvt/banks/HitReader.java
+++ b/reconstruction/cvt/src/main/java/org/jlab/rec/cvt/banks/HitReader.java
@@ -117,7 +117,11 @@ public void fetch_BMTHits(DataEvent event, Swim swim, IndexedTable status,
int strip = bankDGTZ.getShort("component", i);
double ADCtoEdep = bankDGTZ.getInt("ADC", i);
double time = bankDGTZ.getFloat("time", i);
- int order = bankDGTZ.trueOrder(i);
+ int order = bankDGTZ.getByte("order", i);;
+ if(Constants.getInstance().timeCuts
+ && !Constants.getInstance().QCDDATSample) {
+ order = bankDGTZ.trueOrder(i);
+ }
//if (order == 1) {
// continue;
//}
@@ -133,11 +137,13 @@ public void fetch_BMTHits(DataEvent event, Swim swim, IndexedTable status,
// create the strip object for the BMT
Strip BmtStrip = new Strip(strip, ADCtoEdep, time);
BmtStrip.setStatus(status.getIntValue("status", sector, layer, strip));
- if(Constants.getInstance().timeCuts) {
+ if(Constants.getInstance().timeCuts
+ && !Constants.getInstance().QCDDATSample) {
if(time!=0 && (timetmax))
BmtStrip.setStatus(2);// calculate the strip parameters for the BMT hit
}
- if(Constants.getInstance().bmtHVCuts) {
+ if(Constants.getInstance().bmtHVCuts
+ && !Constants.getInstance().QCDDATSample) {
if(bmtStripVoltage!=null && bmtStripVoltage.hasEntry(sector,layer,0) &&
bmtStripVoltageThresh!=null && bmtStripVoltageThresh.hasEntry(sector,layer,0)) {
double hv = bmtStripVoltage.getDoubleValue("HV", sector,layer,0);
@@ -299,7 +305,8 @@ public void fetch_SVTHits(DataEvent event, int omitLayer, int omitHemisphere,
if(tdcs.containsKey(key)) {
time = tdcs.get(key);
//time tag
- if(Constants.getInstance().useSVTTimingCuts) {
+ if(Constants.getInstance().useSVTTimingCuts &&
+ !Constants.getInstance().QCDDATSample) {
if(this.passTimingCuts(ADC, time)==false)
continue;
}
diff --git a/reconstruction/cvt/src/main/java/org/jlab/rec/cvt/hit/Hit.java b/reconstruction/cvt/src/main/java/org/jlab/rec/cvt/hit/Hit.java
index 296a82605c..2406c183b5 100644
--- a/reconstruction/cvt/src/main/java/org/jlab/rec/cvt/hit/Hit.java
+++ b/reconstruction/cvt/src/main/java/org/jlab/rec/cvt/hit/Hit.java
@@ -29,12 +29,19 @@ public class Hit implements Comparable {
private int _TrkgStatus = -1; // TrkgStatusFlag factor (-1: no fit; 0: global helical fit; 1: KF fit)
public double _QualityFac; // a quality factor depending on the hit status and goodness of fit
private int _AssociatedClusterID = -1; // the cluster ID associated with that hit
+ private int AssociatedSeedID = -1; // the seed ID associated with that hit
private int AssociatedTrackID = -1; // the track ID associated with that hit
public boolean newClustering = false;
public int MCstatus = -1;
public boolean isCorrupted;
-
+ private int _seedBankRow=-1;
+ private int _trackBankRow=-1;
+ private int associateMCTrkId=-1;
+
+ private double cweight = 0; //normalized difference to associated cluster centroid
+ private double sweight = 0; //normalized difference to associated cluster seed
+
// constructor
public Hit(DetectorType detector, BMTType type, int sector, int layer, Strip strip) {
this._Detector = detector; // 0 = SVT, 1 = BMT
@@ -286,6 +293,86 @@ public void setAssociatedTrackID(int associatedTrackID) {
AssociatedTrackID = associatedTrackID;
}
+ public int getAssociatedSeedID() {
+ return AssociatedSeedID;
+ }
+
+
+ public void setAssociatedSeedID(int associatedSeedID) {
+ this.AssociatedSeedID = associatedSeedID;
+ }
+
+
+ /**
+ * @return the associateMCTrkId
+ */
+ public int getAssociateMCTrkId() {
+ return associateMCTrkId;
+ }
+
+ /**
+ * @param associateMCTrkId the associateMCTrkId to set
+ */
+ public void setAssociateMCTrkId(int associateMCTrkId) {
+ this.associateMCTrkId = associateMCTrkId;
+ }
+
+ /**
+ * @return the _seedBankRow
+ */
+ public int getSeedBankRow() {
+ return _seedBankRow;
+ }
+
+ /**
+ * @param _seedBankRow the _seedBankRow to set
+ */
+ public void setSeedBankRow(int _seedBankRow) {
+ this._seedBankRow = _seedBankRow;
+ }
+
+ /**
+ * @return the _trackBankRow
+ */
+ public int getTrackBankRow() {
+ return _trackBankRow;
+ }
+
+ /**
+ * @param _trackBankRow the _trackBankRow to set
+ */
+ public void setTrackBankRow(int _trackBankRow) {
+ this._trackBankRow = _trackBankRow;
+ }
+
+ /**
+ * @return the cweight
+ */
+ public double getCweight() {
+ return cweight;
+ }
+
+ /**
+ * @param cweight the cweight to set
+ */
+ public void setCweight(double cweight) {
+ this.cweight = cweight;
+ }
+
+ /**
+ * @return the sweight
+ */
+ public double getSweight() {
+ return sweight;
+ }
+
+ /**
+ * @param sweight the sweight to set
+ */
+ public void setSweight(double sweight) {
+ this.sweight = sweight;
+ }
+
public String toString() {
String str = String.format("Hit id=%d, layer=%d, sector=%d, strip=%d, energy=%.3f, time=%.3f, residual=%.3f, clusterID=%d, trackID=%d",
this.getId(), this.getLayer(), this.getSector(),
diff --git a/reconstruction/cvt/src/main/java/org/jlab/rec/cvt/ml/QCDDATSampleMaker.java b/reconstruction/cvt/src/main/java/org/jlab/rec/cvt/ml/QCDDATSampleMaker.java
new file mode 100644
index 0000000000..51cc049540
--- /dev/null
+++ b/reconstruction/cvt/src/main/java/org/jlab/rec/cvt/ml/QCDDATSampleMaker.java
@@ -0,0 +1,194 @@
+package org.jlab.rec.cvt.ml;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.jlab.clas.reco.ReconstructionEngine;
+import org.jlab.clas.swimtools.Swim;
+import org.jlab.detector.base.DetectorType;
+import org.jlab.io.base.DataBank;
+import org.jlab.io.base.DataEvent;
+import org.jlab.rec.cvt.Constants;
+import org.jlab.rec.cvt.Geometry;
+import org.jlab.rec.cvt.bmt.BMTType;
+import org.jlab.rec.cvt.hit.Hit;
+import org.jlab.utils.groups.IndexedTable;
+
+import org.jlab.geom.prim.Line3D;
+import org.jlab.geom.prim.Arc3D;
+import org.jlab.rec.cvt.banks.HitReader;
+
+/**
+ * Service to return reconstructed TRACKS
+ * format
+ *
+ * @author ziegler
+ *
+ */
+public class QCDDATSampleMaker extends ReconstructionEngine {
+
+ private String svtHitBank;
+
+ public QCDDATSampleMaker(String name) {
+ super(name, "ziegler", "6.0");
+ }
+
+ public QCDDATSampleMaker() {
+ super("CVTQCDDATEngine", "ziegler", "6.0");
+ }
+
+ @Override
+ public void detectorChanged(int run) {}
+
+ @Override
+ public boolean init() {
+ this.initConstantsTables();
+ this.registerBanks();
+ return true;
+ }
+
+ public void registerBanks() {
+ this.setSvtHitBank("CVT::QCDDATHit");
+ super.registerOutputBank(this.svtHitBank);
+ }
+
+ public int getRun(DataEvent event) {
+
+ if (event.hasBank("RUN::config") == false) {
+ System.err.println("RUN CONDITIONS NOT READ!");
+ return 0;
+ }
+
+ DataBank bank = event.getBank("RUN::config");
+ int run = bank.getInt("run", 0);
+ if(Constants.getInstance().seedingDebugMode) {
+ System.out.println("EVENT "+bank.getInt("event", 0));
+ }
+ return run;
+ }
+
+ @Override
+ public boolean processDataEventUser(DataEvent event) {
+
+ int run = this.getRun(event);
+ Swim swimmer = new Swim();
+ IndexedTable svtStatus = this.getConstantsManager().getConstants(run, "/calibration/svt/status");
+ IndexedTable svtLorentz = this.getConstantsManager().getConstants(run, "/calibration/svt/lorentz_angle");
+ IndexedTable bmtStatus = this.getConstantsManager().getConstants(run, "/calibration/mvt/bmt_status");
+ IndexedTable bmtTime = this.getConstantsManager().getConstants(run, "/calibration/mvt/bmt_time");
+ IndexedTable bmtVoltage = this.getConstantsManager().getConstants(run, "/calibration/mvt/bmt_voltage");
+ IndexedTable bmtStripVoltage = this.getConstantsManager().getConstants(run, "/calibration/mvt/bmt_strip_voltage");
+ IndexedTable bmtStripThreshold = this.getConstantsManager().getConstants(run, "/calibration/mvt/bmt_strip_voltage_thresholds");
+ IndexedTable adcStatus = this.getConstantsManager().getConstants(run, "/calibration/svt/adcstatus");
+
+ Geometry.getInstance().initialize(this.getConstantsManager().getVariation(), run, svtLorentz, bmtVoltage);
+
+ HitReader hitRead = new HitReader();
+ hitRead.fetch_SVTHits(event, -1, -1, svtStatus, adcStatus);
+ hitRead.fetch_BMTHits(event, swimmer, bmtStatus, bmtTime,
+ bmtStripVoltage, bmtStripThreshold);
+ List> hits = new ArrayList<>();
+ if(hitRead.getSVTHits() == null) {
+ hits.add(new ArrayList<>());
+ }
+ else {
+ hits.add((ArrayList) hitRead.getSVTHits());
+ }
+ if(hitRead.getBMTHits() == null) {
+ hits.add(new ArrayList<>());
+ }
+ else {
+ hits.add((ArrayList) hitRead.getBMTHits());
+ }
+
+ if (event.hasBank("MC::True")) {
+ DataBank mcTrue = event.getBank("MC::True");
+ TrackingPerformance.MatchHitsToMC(hits, mcTrue);
+ }
+
+ if (hits.isEmpty())
+ return false;
+ DataBank bank = event.createBank(this.svtHitBank, hits.get(0).size()+hits.get(1).size());
+ int index=0;
+ for(int i = 0; i < hits.size(); i++) {
+ for(int j = 0; j < hits.get(i).size(); j++) {
+ bank.setShort("id", index, (short) hits.get(i).get(j).getId());
+ bank.setShort("mctid", index, (short) hits.get(i).get(j).getAssociateMCTrkId());
+ bank.setByte("sector", index, (byte) hits.get(i).get(j).getSector());
+ int layer = hits.get(i).get(j).getLayer();
+ if(i>0) layer+=6;
+ bank.setByte("layer", index, (byte) layer);
+ bank.setShort("strip", index, (short) hits.get(i).get(j).getStrip().getStrip());
+ bank.setFloat("energy", index, (short) hits.get(i).get(j).getStrip().getEdep());
+ bank.setFloat("time", index, (short) hits.get(i).get(j).getStrip().getTime());
+ int mctrue=-1;
+ if(hits.get(i).get(j).MCstatus==0) {
+ mctrue=0;
+ } else {
+ mctrue=1;
+ }
+ bank.setByte("mctrue", index, (byte) mctrue);
+ if(hits.get(i).get(j).getDetector()==DetectorType.BST ||
+ (hits.get(i).get(j).getDetector()==DetectorType.BMT
+ && hits.get(i).get(j).getType()==BMTType.Z)) {
+ Line3D sline = hits.get(i).get(j).getStrip().getLine();
+ bank.setFloat("x1", index, (float) sline.origin().x()/10);
+ bank.setFloat("y1", index, (float) sline.origin().y()/10);
+ bank.setFloat("z1", index, (float) sline.origin().z()/10);
+ bank.setFloat("x2", index, (float) sline.midpoint().x()/10);
+ bank.setFloat("y2", index, (float) sline.midpoint().y()/10);
+ bank.setFloat("z2", index, (float) sline.midpoint().z()/10);
+ bank.setFloat("x3", index, (float) sline.end().x()/10);
+ bank.setFloat("y3", index, (float) sline.end().y()/10);
+ bank.setFloat("z3", index, (float) sline.end().z()/10);
+
+ }
+ if(hits.get(i).get(j).getDetector()==DetectorType.BMT
+ && hits.get(i).get(j).getType()==BMTType.C) {
+ Arc3D sarc = hits.get(i).get(j).getStrip().getArc();
+ bank.setFloat("x1", index, (float) sarc.origin().x()/10);
+ bank.setFloat("y1", index, (float) sarc.origin().y()/10);
+ bank.setFloat("z1", index, (float) sarc.origin().z()/10);
+ bank.setFloat("x2", index, (float) sarc.point(sarc.theta()/2).x()/10);
+ bank.setFloat("y2", index, (float) sarc.point(sarc.theta()/2).y()/10);
+ bank.setFloat("z2", index, (float) sarc.point(sarc.theta()/2).z()/10);
+ bank.setFloat("x3", index, (float) sarc.end().x()/10);
+ bank.setFloat("y3", index, (float) sarc.end().y()/10);
+ bank.setFloat("z3", index, (float) sarc.end().z()/10);
+ }
+
+ index++;
+ }
+ }
+ //bank.show();
+ event.appendBanks(bank);
+
+ return true;
+ }
+
+
+
+ public void initConstantsTables() {
+ String[] tables = new String[]{
+ "/calibration/svt/status",
+ "/calibration/svt/lorentz_angle",
+ "/calibration/mvt/bmt_time",
+ "/calibration/mvt/bmt_status",
+ "/calibration/mvt/bmt_voltage",
+ "/calibration/mvt/bmt_strip_voltage",
+ "/calibration/mvt/bmt_strip_voltage_thresholds",
+ "/geometry/beam/position",
+ "/calibration/svt/adcstatus"
+ };
+ requireConstants(Arrays.asList(tables));
+ this.getConstantsManager().setVariation("default");
+ }
+
+ public void setSvtHitBank(String bstHitBank) {
+ this.svtHitBank = bstHitBank;
+ }
+ public String getSvtHitBank() {
+ return this.svtHitBank;
+ }
+}
diff --git a/reconstruction/cvt/src/main/java/org/jlab/rec/cvt/ml/TrackingPerformance.java b/reconstruction/cvt/src/main/java/org/jlab/rec/cvt/ml/TrackingPerformance.java
new file mode 100644
index 0000000000..70fc59df73
--- /dev/null
+++ b/reconstruction/cvt/src/main/java/org/jlab/rec/cvt/ml/TrackingPerformance.java
@@ -0,0 +1,248 @@
+/*
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
+ */
+package org.jlab.rec.cvt.ml;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.jlab.detector.base.DetectorType;
+import org.jlab.io.base.DataBank;
+import org.jlab.io.base.DataEvent;
+import org.jlab.rec.cvt.cluster.Cluster;
+import org.jlab.rec.cvt.hit.Hit;
+import org.jlab.rec.cvt.track.Seed;
+import org.jlab.rec.cvt.track.Track;
+
+/**
+ *
+ * @author ziegler
+ */
+public class TrackingPerformance {
+ public static void MatchHitToMC(Hit h, Map map) {
+ if(map.containsKey(h.getId())) {
+ h.setAssociateMCTrkId(map.get(h.getId()));
+ }
+ }
+
+ public static Map TruthMap(DataBank mcTrue, int detId) {
+ Map map = new HashMap<>();
+ for (int k = 0; k < mcTrue.rows(); k++) {
+ if(mcTrue.getInt("mtid", k)==0 && mcTrue.getByte("detector", k) == detId) {
+ map.put(mcTrue.getInt("hitn", k), mcTrue.getInt("tid", k));
+ }
+ }
+ return map;
+ }
+
+ public static void MatchHitsToMC(List>hits, DataBank mcTrue){ //1.
+ Map smap = TrackingPerformance.TruthMap(mcTrue, DetectorType.BST.getDetectorId());
+ Map bmap = TrackingPerformance.TruthMap(mcTrue, DetectorType.BMT.getDetectorId());
+ for(Hit h : hits.get(0)) {
+ TrackingPerformance.MatchHitToMC(h, smap);
+ }
+ for(Hit h : hits.get(1)) {
+ TrackingPerformance.MatchHitToMC(h, bmap);
+ }
+ }
+
+ private static double calcPurity(Seed s, int mctid) {
+ int nTotalHits=0;
+ int nMCMatchedHits=0;
+ for(Cluster c: s.getClusters()) {
+ for(Hit h : c) {
+ nTotalHits++;
+ if(h.getAssociateMCTrkId()==mctid) {
+ nMCMatchedHits++;
+ }
+ }
+ }
+
+ if(nTotalHits==0) return 0;
+ return (double) nMCMatchedHits/(double) nTotalHits;
+ }
+
+ private static double calcPurity(Track t, int mctid) {
+ return calcPurity(t.getSeed(), mctid);
+ }
+
+ private static void setPurity(Seed s, int mctid) {
+ double purity = calcPurity(s, mctid);
+ s.setPurity(purity);
+ }
+
+ private static void setPurity(Track t, int mctid) {
+ double purity = calcPurity(t, mctid);
+ t.setPurity(purity);
+ }
+
+ private static void setSeedsPurity(List seeds) { //2.
+ for(Seed s : seeds) {
+ int mcmatchId = matchToMCtID(s);
+ double purity = calcPurity(s, mcmatchId);
+ s.setPurity(purity);
+ }
+ }
+
+ private static void setTracksPurity(List