diff --git a/js/main.js b/js/main.js index 192fcd09..e9ab8b74 100644 --- a/js/main.js +++ b/js/main.js @@ -20,6 +20,8 @@ const selectedObjectTypes = { "edm4hep::MCRecoClusterParticleAssociation", "edm4hep::Cluster", "edm4hep::Track", + "edm4hep::Vertex", + "edm4hep::ParticleID", ], }; diff --git a/js/types/links.js b/js/types/links.js index fa19e997..5a7c7391 100644 --- a/js/types/links.js +++ b/js/types/links.js @@ -9,6 +9,7 @@ const colors = { "particles": "#AA00AA", "mcclusters": "#D8F1A0", "mctracks": "#fe5e41", + "vertex": "#593746", }; export class Link { @@ -106,6 +107,13 @@ class Tracks extends Link { } } +class Vertex extends Link { + constructor(from, to) { + super(from, to); + this.color = colors["vertex"]; + } +} + class MCRecoTrackParticleAssociation extends Link { constructor(from, to, weight) { super(from, to); @@ -139,5 +147,7 @@ export const linkTypes = { "clusters": Clusters, "tracks": Tracks, "particles": Particles, - "startVertex": Link, + "particle": Particles, + "startVertex": Vertex, + "associatedParticle": Vertex, }; diff --git a/js/types/load.js b/js/types/load.js index d2b12bd7..b9ee63d7 100644 --- a/js/types/load.js +++ b/js/types/load.js @@ -102,9 +102,12 @@ export function loadObjects(jsonData, event, objectsToLoad) { // load One To One Relations for (const { type, name } of oneToOneRelations) { if (objects.datatypes?.[type] === undefined) continue; - const oneToOneRelationData = element.collection.map( - (object) => object[name] - ); + const oneToOneRelationData = element.collection + .map((object) => object[name]) + .filter((object) => object !== undefined); + + if (oneToOneRelationData.length === 0) continue; + const toCollectionID = oneToOneRelationData.find( (relation) => relation.collectionID !== undefined @@ -131,9 +134,11 @@ export function loadObjects(jsonData, event, objectsToLoad) { // load One To Many Relations for (const { type, name } of oneToManyRelations) { if (objects.datatypes?.[type] === undefined) continue; - const oneToManyRelationData = element.collection.map( - (object) => object[name] - ); + const oneToManyRelationData = element.collection + .map((object) => object[name]) + .filter((object) => object !== undefined); + + if (oneToManyRelationData.length === 0) continue; const toCollectionID = oneToManyRelationData.find( diff --git a/js/types/objects.js b/js/types/objects.js index 1bf8d773..5d526164 100644 --- a/js/types/objects.js +++ b/js/types/objects.js @@ -307,7 +307,7 @@ class Track extends EDMObject { lines.push("type: " + this.type); const chi2 = parseInt(this.chi2 * 100) / 100; const ndf = parseInt(this.ndf * 100) / 100; - const chiNdf = `${chi2} / ${ndf}`; + const chiNdf = `${chi2}/${ndf}`; lines.push("chi2/ndf = " + chiNdf); lines.push("dEdx = " + this.dEdx); @@ -323,13 +323,77 @@ class Track extends EDMObject { class ParticleID extends EDMObject { constructor() { super(); + this.width = 140; + this.height = 120; } + + draw(ctx) { + const boxCenterX = this.x + this.width / 2; + + drawRoundedRect( + ctx, + this.x, + this.y, + this.width, + this.height, + "#c9edf7", + 25 + ); + + const topY = this.y + 20; + + const lines = []; + lines.push("ID: " + this.index); + lines.push("type: " + this.type); + lines.push("PDG: " + this.PDG); + lines.push("algorithm: " + this.algorithmType); + lines.push("likelihood: " + this.likelihood); + + drawTextLines(ctx, lines, boxCenterX, topY, 23); + } + + static setup(particleIDCollection) {} } class Vertex extends EDMObject { constructor() { super(); + this.width = 140; + this.height = 140; + } + + draw(ctx) { + const boxCenterX = this.x + this.width / 2; + + drawRoundedRect( + ctx, + this.x, + this.y, + this.width, + this.height, + "#f5d3ef", + 25 + ); + + const topY = this.y + 20; + + const lines = []; + lines.push("ID: " + this.index); + const x = parseInt(this.position.x * 100) / 100; + const y = parseInt(this.position.y * 100) / 100; + const z = parseInt(this.position.z * 100) / 100; + lines.push(`pos = (x=${x},`); + lines.push(`y=${y},`); + lines.push(`z=${z}) mm`); + const chi2 = parseInt(this.chi2 * 100) / 100; + const ndf = parseInt(this.ndf * 100) / 100; + const chiNdf = `${chi2}/${ndf}`; + lines.push("chi2/ndf = " + chiNdf); + + drawTextLines(ctx, lines, boxCenterX, topY, 23); } + + static setup(vertexCollection) {} } export const objectTypes = { diff --git a/js/views/association-view.js b/js/views/association-view.js index 00eeb28c..5c931c04 100644 --- a/js/views/association-view.js +++ b/js/views/association-view.js @@ -2,18 +2,16 @@ import { canvas } from "../main.js"; // List 1:1 association in a vertical list export function buildAssociationView(viewObjects, associationName) { - const association = viewObjects.associations[associationName]; + const associations = viewObjects.associations[associationName]; + const length = associations.length; - const fromCollection = association.map((association) => association.from); - const toCollection = association.map((association) => association.to); - - if (fromCollection.length === 0 || toCollection.length === 0) { + if (length === 0) { alert("No association found!"); return; } - const fromWidth = fromCollection[0].width; - const toWidth = toCollection[0].width; + const fromWidth = associations[0].from.width; + const toWidth = associations[0].to.width; const fromHorizontalGap = 0.3 * fromWidth; const toHorizontalGap = 0.3 * toWidth; const gap = 2 * (fromWidth + toWidth); @@ -22,14 +20,13 @@ export function buildAssociationView(viewObjects, associationName) { const width = totalWidth > window.innerWidth ? totalWidth : window.innerWidth; canvas.width = width; - const fromHeight = fromCollection[0].height; - const toHeight = toCollection[0].height; + const fromHeight = associations[0].from.height; + const toHeight = associations[0].to.height; const height = Math.max(fromHeight, toHeight); const verticalGap = 0.3 * height; - const totalHeight = - fromCollection.length * (height + verticalGap) + verticalGap; + const totalHeight = length * (height + verticalGap) + verticalGap; canvas.height = totalHeight; @@ -39,13 +36,15 @@ export function buildAssociationView(viewObjects, associationName) { const toX = width / 2 + toHorizontalGap; - for (let i = 0; i < fromCollection.length; i++) { - fromCollection[i].x = fromX; - toCollection[i].x = toX; + associations.forEach((association) => { + association.from.x = fromX; + association.to.x = toX; const space = height + verticalGap; - fromCollection[i].y = accHeight + space / 2 - fromHeight / 2; - toCollection[i].y = accHeight + space / 2 - toHeight / 2; + const fromY = accHeight + space / 2 - fromHeight / 2; + const toY = accHeight + space / 2 - toHeight / 2; + association.from.y = fromY; + association.to.y = toY; accHeight += height + verticalGap; - } + }); } diff --git a/js/views/list.js b/js/views/list.js new file mode 100644 index 00000000..99aa7ce4 --- /dev/null +++ b/js/views/list.js @@ -0,0 +1,32 @@ +import { canvas } from "../main.js"; + +export function listView(collection) { + if (collection.length === 0) { + alert("No objects found!"); + return; + } + const width = window.innerWidth; + canvas.width = width; + + const gap = 1; + const objWidth = collection[0].width; + const objHorizontalGap = gap * objWidth; + const objHeight = collection[0].height; + const objVerticalGap = gap * objHeight; + + const cols = Math.ceil(width / (objWidth + objHorizontalGap)); + const rows = Math.ceil(collection.length / cols); + + const height = rows * (objHeight + objVerticalGap / 2) + objVerticalGap / 2; + canvas.height = height > window.innerHeight ? height : window.innerHeight; + + for (let i = 0; i < collection.length; i++) { + const x = (i % cols) * objWidth + (((i % cols) + 1) * objHorizontalGap) / 2; + const y = + Math.floor(i / cols) * objHeight + + ((Math.floor(i / cols) + 1) * objVerticalGap) / 2; + + collection[i].x = x; + collection[i].y = y; + } +} diff --git a/js/views/onewayview.js b/js/views/onewayview.js new file mode 100644 index 00000000..1a6f8683 --- /dev/null +++ b/js/views/onewayview.js @@ -0,0 +1,51 @@ +import { canvas } from "../main.js"; + +export function oneWayView(viewObjects, fromCollectionName, relationName) { + const relations = + viewObjects.datatypes[fromCollectionName].oneToOne[relationName]; + + const fromCollection = relations.map((relation) => relation.from); + const toCollection = relations.map((relation) => relation.to); + + if (fromCollection.length === 0 || toCollection.length === 0) { + alert("No association found!"); + return; + } + + const fromWidth = fromCollection[0].width; + const toWidth = toCollection[0].width; + const fromHorizontalGap = 0.3 * fromWidth; + const toHorizontalGap = 0.3 * toWidth; + const gap = 2 * (fromWidth + toWidth); + const totalWidth = gap + fromWidth + toWidth; + + const width = totalWidth > window.innerWidth ? totalWidth : window.innerWidth; + canvas.width = width; + + const fromHeight = fromCollection[0].height; + const toHeight = toCollection[0].height; + + const height = Math.max(fromHeight, toHeight); + const verticalGap = 0.3 * height; + + const totalHeight = + fromCollection.length * (height + verticalGap) + verticalGap; + + canvas.height = totalHeight; + + let accHeight = 0; + + const fromX = width / 2 - fromWidth - fromHorizontalGap; + + const toX = width / 2 + toHorizontalGap; + + for (let i = 0; i < fromCollection.length; i++) { + fromCollection[i].x = fromX; + toCollection[i].x = toX; + + const space = height + verticalGap; + fromCollection[i].y = accHeight + space / 2 - fromHeight / 2; + toCollection[i].y = accHeight + space / 2 - toHeight / 2; + accHeight += height + verticalGap; + } +} diff --git a/js/views/particleidlist.js b/js/views/particleidlist.js new file mode 100644 index 00000000..c3c13cb6 --- /dev/null +++ b/js/views/particleidlist.js @@ -0,0 +1,13 @@ +import { listView } from "./list.js"; +import { preFilterList } from "./pre-filter.js"; + +export function particleIDList(viewCurrentObjects) { + const vertexCollection = + viewCurrentObjects.datatypes["edm4hep::ParticleID"].collection ?? []; + + listView(vertexCollection); +} + +export function preFilterParticleIDList(currentObjects, viewObjects) { + preFilterList(currentObjects, viewObjects, "edm4hep::ParticleID"); +} diff --git a/js/views/pre-filter.js b/js/views/pre-filter.js index 8334ee6b..bad016ed 100644 --- a/js/views/pre-filter.js +++ b/js/views/pre-filter.js @@ -37,3 +37,30 @@ export function preFilterTree( currentObjects.datatypes[collectionName].oneToMany[relationName]; }); } + +export function preFilterList(currentObjects, viewObjects, collectionName) { + emptyCopyObject(currentObjects, viewObjects); + + viewObjects.datatypes[collectionName].collection = + currentObjects.datatypes[collectionName].collection; +} + +export function preFilterOneWay( + currentObjects, + viewObjects, + relationName, + fromCollectionName, + toCollectionName +) { + emptyCopyObject(currentObjects, viewObjects); + + const relations = + currentObjects.datatypes[fromCollectionName].oneToOne[relationName]; + + const fromCollection = relations.map((relation) => relation.from); + const toCollection = relations.map((relation) => relation.to); + + viewObjects.datatypes[fromCollectionName].oneToOne[relationName] = relations; + viewObjects.datatypes[fromCollectionName].collection = fromCollection; + viewObjects.datatypes[toCollectionName].collection = toCollection; +} diff --git a/js/views/recoclustertrack.js b/js/views/recoclustertrack.js index 1ecee9fd..874f9630 100644 --- a/js/views/recoclustertrack.js +++ b/js/views/recoclustertrack.js @@ -1,7 +1,7 @@ import { canvas } from "../main.js"; import { emptyCopyObject } from "../lib/copy.js"; -export function recoClusterTrack(viewObjects) { +export function recoClusterTrackVertex(viewObjects) { const recoParticles = viewObjects.datatypes["edm4hep::ReconstructedParticle"].collection; @@ -37,7 +37,23 @@ export function recoClusterTrack(viewObjects) { const trackVerticalGap = trackHeight * 0.3; const trackWidth = firstTrack.width; - const widestObject = Math.max(clusterWidth, trackWidth); + const firstVertex = recoParticles.find((particle) => { + const vertexRelation = particle.oneToOneRelations["startVertex"]; + if (vertexRelation !== undefined) { + return vertexRelation.to; + } + }); + + let vertexHeight = 0; + let vertexVerticalGap = 0; + let vertexWidth = 0; + if (firstVertex !== undefined) { + vertexHeight = firstVertex.height; + vertexVerticalGap = vertexHeight * 0.3; + vertexWidth = firstVertex.width; + } + + const widestObject = Math.max(clusterWidth, trackWidth, vertexWidth); const widestGap = widestObject * 0.3; const totalHorizontalGap = @@ -59,10 +75,12 @@ export function recoClusterTrack(viewObjects) { recoParticles.forEach((particle) => { const clusterRelations = particle.oneToManyRelations["clusters"]; const trackRelations = particle.oneToManyRelations["tracks"]; + const vertexRelation = particle.oneToOneRelations["startVertex"]; const relationsHeight = parseInt( clusterRelations.length * (clusterHeight + clusterVerticalGap) + - trackRelations.length * (trackHeight + trackVerticalGap) + trackRelations.length * (trackHeight + trackVerticalGap) + + (vertexRelation !== undefined ? vertexHeight + vertexVerticalGap : 0) ); const height = @@ -94,13 +112,22 @@ export function recoClusterTrack(viewObjects) { accumulatedRelationsHeight += trackHeight + trackVerticalGap / 2; }); + if (vertexRelation !== undefined) { + const vertex = vertexRelation.to; + vertex.x = otherX; + + const y = vertexVerticalGap / 2 + accumulatedRelationsHeight; + vertex.y = y; + accumulatedRelationsHeight += vertexHeight + vertexVerticalGap / 2; + } + totalHeight += height; }); canvas.height = totalHeight; } -export function preFilterRecoClusterTrack(currentObjects, viewObjects) { +export function preFilterRecoClusterTrackVertex(currentObjects, viewObjects) { emptyCopyObject(currentObjects, viewObjects); const fromDatatype = @@ -111,10 +138,12 @@ export function preFilterRecoClusterTrack(currentObjects, viewObjects) { const recoParticles = []; const clusters = []; const tracks = []; + const vertexCollection = []; fromCollection.forEach((particle) => { const clusterRelations = particle.oneToManyRelations["clusters"]; const trackRelations = particle.oneToManyRelations["tracks"]; + const vertexRelation = particle.oneToOneRelations["startVertex"]; const total = clusterRelations.length + trackRelations.length; @@ -132,6 +161,11 @@ export function preFilterRecoClusterTrack(currentObjects, viewObjects) { tracks.push(track); }); + if (vertexRelation !== undefined) { + const vertex = vertexRelation.to; + vertexCollection.push(vertex); + } + recoParticles.push(particle); }); @@ -147,8 +181,14 @@ export function preFilterRecoClusterTrack(currentObjects, viewObjects) { currentObjects.datatypes["edm4hep::ReconstructedParticle"].oneToMany[ "tracks" ]; + viewObjects.datatypes["edm4hep::ReconstructedParticle"].oneToOne[ + "startVertex" + ] = + currentObjects.datatypes["edm4hep::ReconstructedParticle"].oneToOne[ + "startVertex" + ]; viewObjects.datatypes["edm4hep::Cluster"].collection = clusters; - viewObjects.datatypes["edm4hep::Track"].collection = tracks; + viewObjects.datatypes["edm4hep::Vertex"].collection = vertexCollection; } diff --git a/js/views/recoparticleid.js b/js/views/recoparticleid.js new file mode 100644 index 00000000..e145b9de --- /dev/null +++ b/js/views/recoparticleid.js @@ -0,0 +1,16 @@ +import { preFilterOneWay } from "./pre-filter.js"; +import { oneWayView } from "./onewayview.js"; + +export function recoParticleID(viewObjects) { + oneWayView(viewObjects, "edm4hep::ParticleID", "particle"); +} + +export function preFilterRecoParticleID(currentObjects, viewObjects) { + preFilterOneWay( + currentObjects, + viewObjects, + "particle", + "edm4hep::ParticleID", + "edm4hep::ReconstructedParticle" + ); +} diff --git a/js/views/vertexlist.js b/js/views/vertexlist.js new file mode 100644 index 00000000..59962c5f --- /dev/null +++ b/js/views/vertexlist.js @@ -0,0 +1,13 @@ +import { listView } from "./list.js"; +import { preFilterList } from "./pre-filter.js"; + +export function vertexList(viewCurrentObjects) { + const vertexCollection = + viewCurrentObjects.datatypes["edm4hep::Vertex"].collection ?? []; + + listView(vertexCollection); +} + +export function preFilterVertexList(currentObjects, viewObjects) { + preFilterList(currentObjects, viewObjects, "edm4hep::Vertex"); +} diff --git a/js/views/views-dictionary.js b/js/views/views-dictionary.js index 716983d1..03112d6d 100644 --- a/js/views/views-dictionary.js +++ b/js/views/views-dictionary.js @@ -11,10 +11,13 @@ import { mcClusterAssociation, } from "./mcclusterassociation.js"; import { - recoClusterTrack, - preFilterRecoClusterTrack, + recoClusterTrackVertex, + preFilterRecoClusterTrackVertex, } from "./recoclustertrack.js"; import { setupNoFilter } from "../filter/nofilter.js"; +import { vertexList, preFilterVertexList } from "./vertexlist.js"; +import { particleIDList, preFilterParticleIDList } from "./particleidlist.js"; +import { recoParticleID, preFilterRecoParticleID } from "./recoparticleid.js"; export const views = { "Monte Carlo Particle Tree": { @@ -32,20 +35,20 @@ export const views = { "Track Tree": { filters: setupNoFilter, viewFunction: trackTree, - scrollFunction: scrollTopCenter, + scrollFunction: scrollTopLeft, preFilterFunction: preFilterTrackTree, }, "Cluster Tree": { filters: setupNoFilter, viewFunction: clusterTree, - scrollFunction: scrollTopCenter, + scrollFunction: scrollTopLeft, preFilterFunction: preFilterClusterTree, }, - "Reco Particle-Cluster-Track": { + "RecoParticle-Cluster-Track-Vertex": { filters: setupNoFilter, - viewFunction: recoClusterTrack, + viewFunction: recoClusterTrackVertex, scrollFunction: scrollTopCenter, - preFilterFunction: preFilterRecoClusterTrack, + preFilterFunction: preFilterRecoClusterTrackVertex, }, "Monte Carlo-Reconstructed Particle": { filters: setupNoFilter, @@ -65,4 +68,22 @@ export const views = { scrollFunction: scrollTopCenter, preFilterFunction: preFilterMCCluster, }, + "ParticleID List": { + filters: setupNoFilter, + viewFunction: particleIDList, + scrollFunction: scrollTopLeft, + preFilterFunction: preFilterParticleIDList, + }, + "Vertex List": { + filters: setupNoFilter, + viewFunction: vertexList, + scrollFunction: scrollTopLeft, + preFilterFunction: preFilterVertexList, + }, + "ParticleID-Reconstructed Particle": { + filters: setupNoFilter, + viewFunction: recoParticleID, + scrollFunction: scrollTopCenter, + preFilterFunction: preFilterRecoParticleID, + }, }; diff --git a/output/datatypes.js b/output/datatypes.js index 31826a96..92cb2b88 100644 --- a/output/datatypes.js +++ b/output/datatypes.js @@ -135,9 +135,6 @@ export const datatypes = { }, { "name": "dEdxError" - }, - { - "name": "radiusOfInnermostHit" } ], "oneToManyRelations": [ @@ -154,13 +151,13 @@ export const datatypes = { "edm4hep::Vertex": { "members": [ { - "name": "primary" + "name": "type" }, { "name": "chi2" }, { - "name": "probability" + "name": "ndf" }, { "name": "position"