EventDisplay - EMPHATICSoft/emphaticsoft GitHub Wiki
Web-based Event Display
webDisplay.fcl draws a 3D picture of the detector in your browser that you can rotate in real time. This should be a good way to debug our algorithms and make impressive pictures of our data.
Usage
- Log in to an emphaticgpvm with a special ssh flag:
ssh -L 3490:localhost:3490 emphaticgpvm03.fnal.gov - Set up to develop emphaticsoft normally. You'll forward port 3490 through the SL7 container since you're already on this branch.
- Find a file you want to read. It must have SSD LineSegments from the MakeSSDClusters module since that's all the event display shows.
- Start the "web server" ART job:
art -c webDisplay.fcl <yourFile.root> - Open a web browser on your desktop. Type into the URL bar:
localhost:3490 - The browser make take a few seconds to load the first event. Then you should see a black screen with teal lines and some blue and red shapes:
- Teal lines are SSD LineSegments
- Red shapes are the magnet and the target
- Blue shapes are the outlines of SSD sensors
- How to interact with the browser:
- Left click and drag to change your viewing angle
- Right click and drag to change your viewing position in the plane you're viewing from
- Scroll to zoom in or out
- Reload the page to go to the next event
Supported Platforms
- Your laptop (client side):
- Chrome on OS X
- Chrome on Ubuntu
- Firefox on Ubuntu
- Firefox on Windows through WSL
Details
Might be useful in case you need to debug something
- The event display just draws LineSegments and MC trajectories right now. No tracks, hits, or Cherenkov information (yet!). The magnet and target positions are from the Geometry service. The magnet geometry is the only geometry component loaded directly from the GDML file right now. Ask if you want more volumes like this. I'm trying to limit detailed geometry to keep the focus on the physics.
- The WebDisplay is acting as a trivial "web server" that sends and receives HTTP over a port. It now figures out when to go to the next event by counting requests. I get 1 request per magnet segment + 1 request to go to the new event. You'll get a bunch of numbers on your screen if it gets out of sync somehow. Please tell me if that happens so I can fix it!
- You have to get port 3490 from the ART job to your web browser on your device. In these instructions, it is forwarded by ssh which should even encrypt traffic over the port and requires you to authenticate using kerberos to use it effectively. Not so friendly to non-collaborators, but hopefully overly safe since I'm a web neophyte.
- The 3D display is run client-side (on your computer, not the GPVM) using a Javascript library called three.js. I got the idea to use this library from CMS's outreach event display.
- The GPVM "web server" is just a code generator that fills in some Javascript based on the LineSegments in the display and the Geometry service.
- We require a POSIX OS (i.e. not Windows) for 2 reasons that I know of: the sockets API, and the stat() API. stat() is easy to replace with ROOT shenanigans that I don't like from TSystem. ROOT and Boost may have more robust socket implementations. The only reason I didn't use a portable socket implementation was to save myself work on the demonstration.
Plan for Next Version
Development is just about finished! I need volunteers to try to break it.
- You can now skip to any event in the file in any order. This might cause problems with things like reading directly from the DAQ though. If we use other ART InputSources later, we need to think about adapting this event display to handle them gracefully.
- Client-side code is a complete Javascript+HTML+CSS file
- We could serve this through a real web server like Apache with a bit of work to rewrite the back end (= ART job). This gives us the flexibility to do outreach with 100s of students, dodge web policy problems, or scale with growth of our collaboration.
- Hopefully also makes it easier to maintain. Writing valid Javascript in c++ string literals is hard!
- The web server in the ART job got a bit more complicated in order to handle loading multiple files
- All geometry volumes now have accurate shapes. They come from our GDML file.
- The SSDs, the magnet, and the target still get their positions from the Geometry service. This is how we should propagate alignment constants into the event display.
- This makes future upgrades easier. I want to add any GDML volume to the event display with a FHICL file. We're just missing the FHICL interface (easy) and a way to get positions out of the GDML file. I'll add an Issue when this goes live...
How to Add a Data Product
How can you add new detector systems or reconstruction results to the event display? Recall that a data product is ART's name for things like Tracks and LineSegments that save information from the reconstruction into files and between modules. Suppose you want to a tracks to the event display.
- Find an example that looks a little like the object you want to add. MC trajectories are lines like tracks, so use them as an example. Line Segments are a good example of basic geometric shapes. Complicated shapes from the GDML file are possible but a little more complicated and don't have an example yet.
Javascript side
Javascript looks a little like c++ but without pointers! It's useful to us because it can run on (just about) any modern web browser and interfaces with the high-level 3D graphics library that CMS uses.
2. Decide what shape you want to draw. Like MC trajectories, Tracks are probably a good match to THREE.Line. You can see the full list of simple shapes at [https://threejs.org/manual/#en/primitives]. Using complicated shapes from our GDML file is a bit more complicated but not impossible.
3. Decide what information you need to transfer between ART and the event display. For MC trajectories, that looks like:
//Request a new list of sim::MCTrajectory from the backend.
//Returns a Future that's ready when the backend has responded and scene has been updated.
//Backend must respond to MC/trajs.json with a list of JSON objects of format:
//{
// name,
// pdgCode,
// points[](/EMPHATICSoft/emphaticsoft/wiki/)
//}
namewill be visible when someone mouses over your new objectpdgCodeis how I decide what color to make aLinepoints[](/EMPHATICSoft/emphaticsoft/wiki/)is a nested array of points eachLinemust go through
I chose to use JSON to transfer information between ART and Javascript because Javascript can compile it directly into code, it's human readable, and it's relatively simple to write by hand. I suggest we keep using JSON for things like object positions, names, and colors to keep the event display consistent.
- Write a function to create the 3D objects you want to draw. It's going to get a JSON "file" and use that information to draw
Object3Ds in the right places. You can get the JSON file with code like this:
return fetch("MC/trajs.json").then(function (response) {
return response.json().then(function (listOfTrajs) {
for(const traj of listOfTrajs) {
fetch()is how Javascript asks a web site for more information without reloading the whole page. This means your camera settings stay the same and also makes it a little easier to maintain our event display. It returns aPromisewhich just means you have to write your code as a function called byPromise.then(). UsingPromisealso means that the camera keeps working while your new objects are loading!- This code uses several "lambda functions". If you recognize this term from c++11, great! Javascript lamdba functions work like c++ except they capture all local variables by default. For the rest of us, a lamdba function is a function you declare right when you need it that can "see" variables from outside that function. Its syntax looks like this:
function (response) {}.responseis a variable that gets passed into the function. In this case,responseis the in-memorystructmade from JSON your c++ code passed. - Create a "geometry", a "material", and an
Object3Dbased on your JSON file. Your JSON file is just an array of structs. Notice that we loop over arrays in Javascript withfor(... of ...)instead offor(const auto& ... : ...)that we would do in c++11. Your goal is to create anObject3Dandpush()it into the parameter that your function takes,nextScene. To make anObject3Din THREE.js, you have to combine a "geometry" and a "material" object. Use (MeshPhongMaterial)[https://threejs.org/docs/#api/en/materials/MeshPhongMaterial] for e.g. boxes andLineBasicMaterialorLineDashedMaterialfor lines.
- Add your code to
EventLoader.requestNewEvent(). UseloadMCTrajs()as an example.
c++ side
- Write a function that takes a vector of your data product and returns a string. For MC Trajectories, that looks like:
std::string writeMCTrajList(const std::vector<sim::Particle>& trajs) const;
So, for Track, it might look like:
std::string writeMCTrajList(const std::vector<recob::Track>& tracks) const;
Make it a member function of the WebDisplay class like I did for MC trajectories so you can configure it from .fcl files later.
- Write your JSON data as a string based on your
vector<DataProduct>.std::stringstreamis a convenient way to write simple JSON because it automatically converts numbers into text. We lose some precision by converting numbers this way, but the 3D display loses more precision than that anyway. Be careful to not put a comma after the last entry in a JSON array. SeewriteMCTrajList()for an example of how to do this.
auto writeTraj = [&pdgDB, &trajList](const auto& traj)
{
std::string partName = std::to_string(traj.fpdgCode);
const auto partData = pdgDB.GetParticle(traj.fpdgCode);
if(partData) partName = partData->GetName(); //This should almost always happen
trajList << "{\n"
<< " \"name\": \"" << traj.ftrajectory.Momentum(0).Vect().Mag()/1000. << "GeV/c " << partName << "\",\n"
<< " \"pdgCode\": " << traj.fpdgCode << ",\n"
<< " \"points\": [\n";
for(size_t whichTrajPoint = 0; whichTrajPoint < traj.ftrajectory.size()-1; ++whichTrajPoint)
{
const auto& point = traj.ftrajectory.Position(whichTrajPoint);
trajList << "[" << point.X()/10. << ", " << point.Y()/10. << ", " << point.Z()/10. << "],\n"; //Convert mm to cm
}
if(!traj.ftrajectory.empty())
{
const auto& lastPoint = traj.ftrajectory.Position(traj.ftrajectory.size()-1);
trajList << "[" << lastPoint.X()/10. << ", " << lastPoint.Y()/10. << ", " << lastPoint.Z()/10. << "]\n";
}
trajList << " ]\n"
<< "}";
};
if(!trajs.empty())
{
for(auto whichTraj = trajs.begin(); whichTraj < std::prev(trajs.end()); ++whichTraj)
{
writeTraj(*whichTraj);
trajList << ",\n";
}
writeTraj(*std::prev(trajs.end()));
trajList << "\n"; //Don't put a comma on end of last trajectory! Very important for JSON apparently.
}
- Update the web server to send your JSON string. The code looks something like this:
else if(!strcmp(request.uri.c_str(), "/MC/trajs.json"))
{
art::Handle<std::vector<sim::Particle>> mcParts;
e.getByLabel(fConfig.mcPartLabel(), mcParts);
if(mcParts)
{
sendString(writeMCTrajList(*mcParts), messageSocket, "application/json");
}
else sendBadRequest(messageSocket); //The frontend can ignore this request and keep going without showing MC trajectories. Handling this is important for viewing data!
}
- The
!strcmp(request.uri.c_str(), ...)checks what file the Javascript is asking for. Only ask for the name of the JSON file you designed. Your file name will begin with/even if yourfetch()Javascript code doesn't. That's just how websites work. You can include more/s in your file name if that helps make your code more clear. - Get your data products from ART however you prefer. Just make sure you can react to failing to find your data product of choice. If you can't find your data product in the event, just
sendBadRequest(messageSocket)so the Javascript knows to skip your data product. This behavior is important for a Monte Carlo file that hasn't been reconstructed yet for example. You don't want to prevent someone working on the MC simulation from using the event display because they don't have reconstructed Tracks yet! sendString()your JSON file contents if you found your data product of choice. Even if the handle points to an empty vector. If your data product doesn't exist in this event,sendBadRequest()instead.
- TODO: Explain how to use Chrome's "developer tools" to debug Javascript code. "Console" shows interpreter errors.
console.log()let you print your own messages. Link to Javascript style guidelines from THREE.js. Include an example of developer tools.