1
0
Fork 0
mirror of https://github.com/gwm17/Specter.git synced 2024-11-23 02:38:52 -05:00

Preparing for switch to new name: Specter

This commit is contained in:
Gordon McCann 2022-06-19 16:21:09 -04:00
parent a4c731f100
commit a5a2b4a786
96 changed files with 11443 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,34 @@
Font Awesome Free License
-------------------------
Font Awesome Free is free, open source, and GPL friendly. You can use it for
commercial projects, open source projects, or really almost whatever you want.
Full Font Awesome Free license: https://fontawesome.com/license/free.
# Icons: CC BY 4.0 License (https://creativecommons.org/licenses/by/4.0/)
In the Font Awesome Free download, the CC BY 4.0 license applies to all icons
packaged as SVG and JS file types.
# Fonts: SIL OFL 1.1 License (https://scripts.sil.org/OFL)
In the Font Awesome Free download, the SIL OFL license applies to all icons
packaged as web and desktop font files.
# Code: MIT License (https://opensource.org/licenses/MIT)
In the Font Awesome Free download, the MIT license applies to all non-font and
non-icon files.
# Attribution
Attribution is required by MIT, SIL OFL, and CC BY licenses. Downloaded Font
Awesome Free files already contain embedded comments with sufficient
attribution, so you shouldn't need to do anything additional when using these
files normally.
We've kept attribution comments terse, so we ask that you do not actively work
to remove them from files, especially code. They're a great way for folks to
learn about Font Awesome.
# Brand Icons
All brand icons are trademarks of their respective owners. The use of these
trademarks does not indicate endorsement of the trademark holder by Font
Awesome, nor vice versa. **Please do not use brand logos for any purpose except
to represent the company, product, or service to which they refer.**

View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,69 @@
/*
MassMap.h
A represnetation of the AMDC mass table. We provide capability to retrieve the mass of an isotope
as well as the isotopic symbol. This sort of code is pretty ubiquitous in flexible nuclear physics
analysis.
GWM -- Feb 2022
*/
#include "MassMap.h"
/*
Read in AMDC mass file, preformated to remove excess info. Here assumes that by default
the file is in a local directory Resources. Navigator build process handles this.
*/
MassMap::MassMap()
{
SPEC_PROFILE_FUNCTION();
std::ifstream massfile("Assets/amdc2016_mass.txt");
if (massfile.is_open())
{
std::string junk, A, element;
int Z;
double atomicMassBig, atomicMassSmall, isotopicMass;
getline(massfile, junk);
getline(massfile, junk);
while (massfile >> junk)
{
massfile >> Z >> A >> element >> atomicMassBig >> atomicMassSmall;
isotopicMass = (atomicMassBig + atomicMassSmall * 1e-6 - Z * electron_mass) * u_to_mev;
std::string key = "(" + std::to_string(Z) + "," + A + ")";
massTable[key] = isotopicMass;
elementTable[Z] = element;
}
}
else
{
SPEC_ERROR("Unable to load mass file! Make sure that the Resources folder exists!");
}
}
MassMap::~MassMap() {}
//Returns nuclear mass in MeV
double MassMap::FindMass(int Z, int A)
{
SPEC_PROFILE_FUNCTION();
std::string key = "(" + std::to_string(Z) + "," + std::to_string(A) + ")";
auto data = massTable.find(key);
if (data == massTable.end())
{
SPEC_ERROR("Invalid nucleus at MassMap::FindMass()! Z: {0} A: {1}", Z, A);
return 0.0;
}
return data->second;
}
//returns element symbol
std::string MassMap::FindSymbol(int Z, int A)
{
SPEC_PROFILE_FUNCTION();
auto data = elementTable.find(Z);
if (data == elementTable.end())
{
SPEC_ERROR("Invalid nucleus at MassMap::FindSymbol()! Z: {0} A: {1}", Z, A);
return "";
}
std::string fullsymbol = std::to_string(A) + data->second;
return fullsymbol;
}

31
SpecProject/src/MassMap.h Normal file
View File

@ -0,0 +1,31 @@
/*
MassMap.h
A represnetation of the AMDC mass table. We provide capability to retrieve the mass of an isotope
as well as the isotopic symbol. This sort of code is pretty ubiquitous in flexible nuclear physics
analysis.
GWM -- Feb 2022
*/
#ifndef MASS_MAP_H
#define MASS_MAP_H
#include "Specter.h"
class MassMap
{
public:
MassMap();
~MassMap();
double FindMass(int Z, int A);
std::string FindSymbol(int Z, int A);
private:
std::unordered_map<std::string, double> massTable;
std::unordered_map<int, std::string> elementTable;
//constants
static constexpr double u_to_mev = 931.4940954;
static constexpr double electron_mass = 0.000548579909;
};
#endif

View File

@ -0,0 +1,135 @@
/*
SPSAnalysisStage.cpp
Example of a user AnalysisStage. This one is based around the SE-SPS detector system in FoxLab at FSU.
GWM -- Feb 2022
*/
#include "SPSAnalysisStage.h"
namespace Specter {
//Construct each NavParameter with their unique name. Then bind them to the SpectrumManager.
SPSAnalysisStage::SPSAnalysisStage() :
AnalysisStage("SPSAnalysis"), delayFLTime("delayFLTime"), delayFRTime("delayFRTime"), delayBLTime("delayBLTime"), delayBRTime("delayBRTime"), x1("x1"), x2("x2"), xavg("xavg"),
scintLeft("scintLeft"), anodeBack("anodeBack"), anodeFront("anodeFront"), cathode("cathode"), xavg_sabreCoinc("xavg_sabreCoinc"), x1_weight("x1_weight"), x2_weight("x2_weight")
{
SPEC_PROFILE_FUNCTION();
SpectrumManager& manager = SpectrumManager::GetInstance();
manager.BindParameter(delayFLTime);
manager.BindParameter(delayFRTime);
manager.BindParameter(delayBLTime);
manager.BindParameter(delayBRTime);
//Bind parameters with some default histograms. Saves us the effort of making them in the UI.
manager.BindParameter(x1, 600, -300.0, 300.0);
manager.BindParameter(x2, 600, -300.0, 300.0);
manager.BindParameter(xavg, 600, -300.0, 300.0);
manager.BindParameter(scintLeft, 4096, 0.0, 4096.0);
manager.BindParameter(anodeBack, 4096, 0.0, 4096.0);
manager.BindParameter(anodeFront, 4096, 0.0, 4096.0);
manager.BindParameter(cathode, 4096, 0.0, 4096);
manager.BindParameter(xavg_sabreCoinc, 600, -300.0, 300.0);
std::vector<std::string> sabre_list;
for (int i = 0; i < 127; i++)
{
sabre_list.push_back("sabre_" + std::to_string(i));
sabre.emplace_back(sabre_list[i]);
manager.BindParameter(sabre[i]);
}
//If you want to make a histogram default available, you can add one like this.
manager.AddHistogramSummary(HistogramArgs("sabre_summary", "", 512, 0.0, 16384), sabre_list);
//Note that if you save a config, the config rep of this histogram will supercede this version.
manager.BindVariable(x1_weight);
manager.BindVariable(x2_weight);
}
SPSAnalysisStage::~SPSAnalysisStage() {}
//Do some physics!
void SPSAnalysisStage::AnalyzePhysicsEvent(const SpecEvent& event)
{
SPEC_PROFILE_FUNCTION();
//Most analysis stages will start kinda like this. Take the raw event data and
//put it into NavParameters using the hit id. Switches are perfect for this. Can also
//create mapping classes to use text-file-based id association (commonly called channel maps).
bool sabreFlag = false;
for(auto& hit : event)
{
if (hit.id < 127)
{
sabreFlag = true;
if (hit.longEnergy > sabre[hit.id].GetValue())
sabre[hit.id].SetValue(hit.longEnergy);
}
switch (hit.id)
{
case 129:
{
if (hit.longEnergy > scintLeft.GetValue())
scintLeft.SetValue(hit.longEnergy);
break;
}
case 135:
{
if (hit.longEnergy > cathode.GetValue())
cathode.SetValue(hit.longEnergy);
break;
}
case 136:
{
if (!delayFLTime.IsValid())
delayFLTime.SetValue(hit.timestamp / 1.0e3);
break;
}
case 137:
{
if (!delayFRTime.IsValid())
delayFRTime.SetValue(hit.timestamp / 1.0e3);
break;
}
case 138:
{
if (!delayBLTime.IsValid())
delayBLTime.SetValue(hit.timestamp / 1.0e3);
break;
}
case 139:
{
if (!delayBRTime.IsValid())
delayBRTime.SetValue(hit.timestamp / 1.0e3);
break;
}
case 141:
{
if (hit.longEnergy > anodeFront.GetValue())
anodeFront.SetValue(hit.longEnergy);
break;
}
case 143:
{
if (hit.longEnergy > anodeBack.GetValue())
anodeBack.SetValue(hit.longEnergy);
break;
}
}
}
//If you want to use parameters to calculate another parameter, you
//need to check that the parameter is valid (set in this event)!
if(delayFLTime.IsValid() && delayFRTime.IsValid())
x1.SetValue((delayFLTime.GetValue() - delayFRTime.GetValue())*0.5*0.4762);
if(delayBLTime.IsValid() && delayBRTime.IsValid())
x2.SetValue((delayBLTime.GetValue() - delayBRTime.GetValue())*0.5*0.5051);
if (x1.IsValid() && x2.IsValid())
{
xavg.SetValue(x1_weight.GetValue() * x1.GetValue() + x2_weight.GetValue() * x2.GetValue());
if (sabreFlag)
xavg_sabreCoinc.SetValue(xavg.GetValue());
}
}
}

View File

@ -0,0 +1,41 @@
/*
SPSAnalysisStage.h
Example of a user AnalysisStage. This one is based around the SE-SPS detector system in FoxLab at FSU.
GWM -- Feb 2022
*/
#include "Specter.h"
namespace Specter {
class SPSAnalysisStage : public AnalysisStage
{
public:
SPSAnalysisStage();
virtual ~SPSAnalysisStage();
virtual void AnalyzePhysicsEvent(const SpecEvent& event) override;
private:
//Create a bunch of parameters
Parameter delayFLTime;
Parameter delayFRTime;
Parameter delayBLTime;
Parameter delayBRTime;
Parameter x1;
Parameter x2;
Parameter xavg;
Parameter scintLeft;
Parameter anodeBack;
Parameter anodeFront;
Parameter cathode;
Parameter xavg_sabreCoinc;
std::vector<Parameter> sabre;
//Create a few variables
Variable x1_weight;
Variable x2_weight;
};
}

View File

@ -0,0 +1,140 @@
/*
SPSInputLayer.cpp
An example of what a user created layer might look like. This is how one would extend the base editor to have more
functionality, specific to their experiment/setup. In this case, we provide inputs for reaction information so that
the kinematic shift of the SE-SPS focal plane can be calculated, and weights for tracing particle trajectories are
produced for use in analysis (as NavVariables).
A reminder that these layers should not be that intense. The more work that is shoved into the UI, the less responsive
and more sluggish overall the UI will become. The vast bulk of the analysis work should be left to the PhysicsLayer which has its own
thread to work upon.
GWM -- Feb 2022
*/
#include "SPSInputLayer.h"
#include "imgui.h"
namespace Specter {
SPSInputLayer::SPSInputLayer() :
Layer("SPSInputLayer"), x1_weight("x1_weight"), x2_weight("x2_weight"), m_bfield(0.0), m_theta(0.0), m_beamKE(0.0),
m_rxnEqn("")
{
for (int i = 0; i < 2; i++)
{
m_targNums[i] = 0;
m_projNums[i] = 0;
m_ejectNums[i] = 0;
m_residNums[i] = 0;
}
SpectrumManager& manager = SpectrumManager::GetInstance();
manager.BindVariable(x1_weight);
manager.BindVariable(x2_weight);
}
SPSInputLayer::~SPSInputLayer() {}
void SPSInputLayer::OnAttach() {}
void SPSInputLayer::OnDetach() {}
void SPSInputLayer::OnUpdate(Timestep& step) {}
void SPSInputLayer::OnEvent(Event& event) {}
void SPSInputLayer::OnImGuiRender()
{
SPEC_PROFILE_FUNCTION();
if (ImGui::Begin("SPS Input"))
{
//Create widgets for all of our inputs
ImGui::InputDouble("Bfield(kG)", &m_bfield, 0.01, 0.1);
ImGui::InputDouble("Theta(deg)", &m_theta, 0.1, 1.0);
ImGui::InputDouble("BeamKE(MeV)", &m_beamKE, 0.1, 1.0);
ImGui::InputInt2("Target Z,A", m_targNums);
ImGui::InputInt2("Projectile Z,A", m_projNums);
ImGui::InputInt2("Ejectile Z,A", m_ejectNums);
if (ImGui::Button("Set"))
{
//We dont want to calculate the weights every frame, so
//we lock that calculation behind a button.
UpdateWeights();
}
//Display some info about the internal state
ImGui::Text("-------Current Settings-------");
ImGui::Text("Reaction Equation: ");
ImGui::SameLine();
ImGui::Text(m_rxnEqn.c_str());
ImGui::Text("X1 Weight: %f", x1_weight.GetValue());
ImGui::Text("X2 Weight: %f", x2_weight.GetValue());
}
ImGui::End();
}
void SPSInputLayer::UpdateWeights()
{
SPEC_PROFILE_FUNCTION();
m_rxnEqn = ""; //reset
//Calculate residual nucleus from reaction
for (int i = 0; i < 2; i++)
m_residNums[i] = m_targNums[i] + m_projNums[i] - m_ejectNums[i];
if (m_residNums[0] < 0 || m_residNums[1] <= 0)
{
SPEC_ERROR("Invalid residual nucleus at SPSInputLayer::UpdateMasses()! ZR: {0} AR: {1}", m_residNums[0], m_residNums[1]);
return;
}
if (m_bfield == 0.0 || m_beamKE == 0.0)
{
SPEC_ERROR("Invaild kinematic settings at SPSInputLayer::UpdateWeights()! BeamKE: {0} Bfield: {1}", m_beamKE, m_bfield);
return;
}
//Obtain masses from the AMDC table
double targMass = m_masses.FindMass(m_targNums[0], m_targNums[1]);
double projMass = m_masses.FindMass(m_projNums[0], m_projNums[1]);
double ejectMass = m_masses.FindMass(m_ejectNums[0], m_ejectNums[1]);
double residMass = m_masses.FindMass(m_residNums[0], m_residNums[1]);
if (targMass == 0.0 || projMass == 0.0 || ejectMass == 0.0 || residMass == 0.0)
return;
std::string temp;
temp = m_masses.FindSymbol(m_targNums[0], m_targNums[1]);
m_rxnEqn += temp + "(";
temp = m_masses.FindSymbol(m_projNums[0], m_projNums[1]);
m_rxnEqn += temp + ",";
temp = m_masses.FindSymbol(m_ejectNums[0], m_ejectNums[1]);
m_rxnEqn += temp + ")";
temp = m_masses.FindSymbol(m_residNums[0], m_residNums[1]);
m_rxnEqn += temp;
double theta_rad = m_theta * c_deg2rad; //convert to radians
double bfield_t = m_bfield * 0.1; //convert to tesla
double Q = targMass + projMass - ejectMass - residMass;
//kinematics a la Iliadis p.590
double term1 = std::sqrt(projMass * ejectMass * m_beamKE) / (ejectMass + residMass) * std::cos(theta_rad);
double term2 = (m_beamKE * (residMass - projMass) + residMass * Q) / (ejectMass + residMass);
double ejectKE = term1 + std::sqrt(term1 * term1 + term2);
ejectKE *= ejectKE;
//momentum
double ejectP = std::sqrt(ejectKE * (ejectKE + 2.0 * ejectMass));
//calculate rho from B a la B*rho = (proj. momentum)/(proj. charge)
double rho = (ejectP * c_mev2j) / (m_ejectNums[0] * c_e * c_C * bfield_t) * 100.0; //in cm
double K;
K = sqrt(projMass * ejectMass * m_beamKE / ejectKE);
K *= std::sin(theta_rad);
double denom = ejectMass + residMass - std::sqrt(projMass * ejectMass * m_beamKE / ejectKE) * std::cos(theta_rad);
K /= denom;
double zshift = -1 * rho * c_spsDisp * c_spsMag * K; //delta-Z in cm
x1_weight.SetValue((0.5 - zshift / c_wireDist));
x2_weight.SetValue((1.0 - x1_weight.GetValue()));
}
}

View File

@ -0,0 +1,68 @@
/*
SPSInputLayer.h
An example of what a user created layer might look like. This is how one would extend the base editor to have more
functionality, specific to their experiment/setup. In this case, we provide inputs for reaction information so that
the kinematic shift of the SE-SPS focal plane can be calculated, and weights for tracing particle trajectories are
produced for use in analysis (as Variables).
A reminder that these layers should not be that intense. The more work that is shoved into the UI, the less responsive
and more sluggish overall the UI will become. The vast bulk of the analysis work should be left to the PhysicsLayer which has its own
thread to work upon.
GWM -- Feb 2022
*/
#ifndef SPS_INPUT_LAYER_H
#define SPS_INPUT_LAYER_H
#include "Specter.h"
#include "MassMap.h"
namespace Specter {
class SPSInputLayer : public Layer
{
public:
SPSInputLayer();
~SPSInputLayer();
virtual void OnAttach() override;
virtual void OnDetach() override;
virtual void OnUpdate(Timestep& step) override;
virtual void OnEvent(Event& event) override; //If you want to respond to events
virtual void OnImGuiRender() override; //"Main" function
private:
void UpdateWeights();
//Variables for use in analysis
Variable x1_weight;
Variable x2_weight;
//UI facing inputs
double m_bfield; //kG
double m_theta; //deg
double m_beamKE; //MeV
//Z, A inputs for reaction nuclei
int m_targNums[2];
int m_projNums[2];
int m_ejectNums[2];
int m_residNums[2];
//Text for UI
std::string m_rxnEqn;
//Map for mass table
MassMap m_masses;
static constexpr double c_mev2j = 1.60218E-13; //J per MeV
static constexpr double c_e = 1.602E-19; //unit charge Coulombs
static constexpr double c_C = 2.9979E8; //speed of light m/s
static constexpr double c_spsDisp = 1.96; //dispersion (x/rho)
static constexpr double c_spsMag = 0.39; //magnification in x
static constexpr double c_wireDist = 4.28625; //FPD anode wire separation in cm
static constexpr double c_deg2rad = 3.14159265358979323846 / 180.0; //pi/180
};
}
#endif

44
SpecProject/src/main.cpp Normal file
View File

@ -0,0 +1,44 @@
/*
main.cpp
Entry point for the example NavProject. Also contains example of a simple user Navigator::Application.
GWM -- Feb 2022
*/
#include "Specter.h"
#include "SPSAnalysisStage.h"
#include "SPSInputLayer.h"
//User application class. Pushes user analysis stages.
class SPSApp : public Specter::Application
{
public:
SPSApp() :
Specter::Application()
{
PushLayer(new Specter::SPSInputLayer());
//PushLayer(new Navigator::TestServerLayer());
PushAnalysisStage(new Specter::SPSAnalysisStage());
}
};
//Define the creation function to make our user application
Specter::Application* Specter::CreateApplication() { return new SPSApp(); }
//Make sure to initialize log BEFORE creating application.
int main(int argc, const char** argv)
{
Specter::Logger::Init();
SPEC_TRACE("Logger Initialized!");
SPEC_PROFILE_BEGIN_SESSION("Startup", "navprofile_startup.json");
auto app = Specter::CreateApplication();
SPEC_PROFILE_END_SESSION();
SPEC_PROFILE_BEGIN_SESSION("Runtime", "navprofile_runtime.json");
app->Run();
SPEC_PROFILE_END_SESSION();
SPEC_PROFILE_BEGIN_SESSION("Shutdown", "navprofile_shutdown.json");
delete app;
SPEC_PROFILE_END_SESSION();
}

View File

@ -0,0 +1,41 @@
/*
OpenGLContext.h
Implementation of OpenGL rendering context. Entirely based upon the work done by @TheCherno in his game engine series. See his content for more details.
GWM -- Feb 2022
*/
#include "OpenGLContext.h"
#include "GLFW/glfw3.h"
#include "glad/glad.h"
namespace Specter {
OpenGLContext::OpenGLContext(GLFWwindow* window) :
m_windowHandle(window)
{
}
OpenGLContext::~OpenGLContext() {}
void OpenGLContext::Init()
{
SPEC_PROFILE_FUNCTION();
glfwMakeContextCurrent(m_windowHandle);
int status = gladLoadGLLoader((GLADloadproc)glfwGetProcAddress);
//Report graphics status
SPEC_TRACE("Loaded OpenGL with glad Init status {0}", status);
SPEC_INFO("Loaded OpenGL renderer");
SPEC_INFO("Vendor: {0}", glGetString(GL_VENDOR));
SPEC_INFO("Renderer: {0}", glGetString(GL_RENDERER));
SPEC_INFO("Version: {0}", glGetString(GL_VERSION));
}
void OpenGLContext::SwapBuffers()
{
glfwSwapBuffers(m_windowHandle);
}
}

View File

@ -0,0 +1,31 @@
/*
OpenGLContext.h
Implementation of OpenGL rendering context. Entirely based upon the work done by @TheCherno in his game engine series. See his content for more details.
GWM -- Feb 2022
*/
#ifndef OPEGL_CONTEXT_H
#define OPEGL_CONTEXT_H
#include "Specter/Core/SpecCore.h"
#include "Specter/Renderer/GraphicsContext.h"
struct GLFWwindow;
namespace Specter {
class OpenGLContext : public GraphicsContext
{
public:
OpenGLContext(GLFWwindow* window);
~OpenGLContext();
virtual void Init() override;
virtual void SwapBuffers() override;
private:
GLFWwindow* m_windowHandle;
};
}
#endif

View File

@ -0,0 +1,29 @@
/*
OpenGLRendererAPI.h
Implementation of OpenGL API in Specter context. Note here only a few things exist. We don't need to implement much ourselves,
ImGui handles a lot of the heavy lifting for us. Based entirely upon @TheCherno's work in his game engine series.
GWM -- Feb 2022
*/
#include "OpenGLRendererAPI.h"
#include "glad/glad.h"
#include "GLFW/glfw3.h"
namespace Specter {
void OpenGLRendererAPI::Clear()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
void OpenGLRendererAPI::SetClearColor(const glm::vec4& color_array)
{
glClearColor(color_array[0], color_array[1], color_array[2], color_array[3]);
}
float OpenGLRendererAPI::GetFrameTime()
{
return (float)glfwGetTime();
}
}

View File

@ -0,0 +1,26 @@
/*
OpenGLRendererAPI.h
Implementation of OpenGL API in Specter context. Note here only a few things exist. We don't need to implement much ourselves,
ImGui handles a lot of the heavy lifting for us. Based entirely upon @TheCherno's work in his game engine series.
GWM -- Feb 2022
*/
#ifndef OPENGL_RENDERER_API_H
#define OPENGL_RENDERER_API_H
#include "Specter/Core/SpecCore.h"
#include "Specter/Renderer/RendererAPI.h"
namespace Specter {
class OpenGLRendererAPI : public RendererAPI
{
public:
virtual void Clear() override;
virtual void SetClearColor(const glm::vec4& color_array) override;
virtual float GetFrameTime() override;
};
}
#endif

View File

@ -0,0 +1,180 @@
/*
OpenGLWindow.h
Implementation of a window with OpenGL context. Not really OpenGL specific, other than in creation of GraphicsContext.
Bulk of creation can be used in any api/context (glfw compatible with Cocoa, X11, or Windows). Based entirely upon the
work of @TheCherno in his game engine series.
GWM -- Feb 2022
*/
#include "OpenGLWindow.h"
#include "OpenGLContext.h"
#include "Specter/Core/SpecCore.h"
#include "Specter/Events/AppEvent.h"
#include "Specter/Events/KeyEvent.h"
#include "Specter/Events/MouseEvent.h"
namespace Specter {
static bool s_glfwInitialized = false;
static void GLFWErrorCallback(int error, const char* description)
{
SPEC_ERROR("GLFW Error Code {0}: {1}", error, description);
}
Window* Window::Create(const WindowProperties& props) { return new OpenGLWindow(props); }
OpenGLWindow::OpenGLWindow(const WindowProperties& props)
{
Init(props);
}
OpenGLWindow::~OpenGLWindow()
{
Shutdown();
}
void OpenGLWindow::Init(const WindowProperties& props)
{
SPEC_PROFILE_FUNCTION();
m_data.width = props.width;
m_data.height = props.height;
m_data.name = props.name;
SPEC_INFO("Creating OpenGLWindow with height {0} width {1} and name {2}", m_data.height, m_data.width, m_data.name);
if(!s_glfwInitialized)
{
int passed = glfwInit();
SPEC_INFO("Initializing GLFW ... Returned value {0}", passed);
glfwSetErrorCallback(GLFWErrorCallback);
}
//Apple specific. OpenGL is technically deprecated, so a little extra work to force the correct version
#ifdef __APPLE__
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
glfwWindowHint(GLFW_OPENGL_PROFILE,GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT,true);
#endif
m_window = glfwCreateWindow((int)m_data.width, (int)m_data.height, m_data.name.c_str(), nullptr, nullptr);
m_context = new OpenGLContext(m_window); //This is the only seriously OpenGL specific code
m_context->Init();
glfwSetWindowUserPointer(m_window, &m_data);
SetVSync(true);
//Set all of the callback functions for the window.
glfwSetWindowSizeCallback(m_window, [](GLFWwindow* window, int width, int height)
{
Data& data = *(Data*) glfwGetWindowUserPointer(window);
data.width = width;
data.height = height;
WindowResizeEvent event(width, height);
data.event_callback_func(event);
});
glfwSetWindowCloseCallback(m_window, [](GLFWwindow* window)
{
Data& data = *(Data*) glfwGetWindowUserPointer(window);
WindowCloseEvent event;
data.event_callback_func(event);
});
glfwSetKeyCallback(m_window, [](GLFWwindow* window, int key, int scancode, int action, int mods)
{
Data& data = *(Data*) glfwGetWindowUserPointer(window);
switch(action)
{
case GLFW_PRESS:
{
KeyPressedEvent event(key, 0);
data.event_callback_func(event);
break;
}
case GLFW_RELEASE:
{
KeyReleasedEvent event(key);
data.event_callback_func(event);
break;
}
case GLFW_REPEAT:
{
//GLFW doesnt have a hold count, so here we just pass 1 to indicate a hold is happening.
KeyPressedEvent event(key, 1);
data.event_callback_func(event);
break;
}
}
});
glfwSetCharCallback(m_window, [](GLFWwindow* window, unsigned int character)
{
Data& data = *(Data*) glfwGetWindowUserPointer(window);
KeyTypedEvent event(character);
data.event_callback_func(event);
});
glfwSetMouseButtonCallback(m_window, [](GLFWwindow* window, int button, int action, int mods)
{
Data& data = *(Data*) glfwGetWindowUserPointer(window);
switch(action)
{
case GLFW_PRESS:
{
MouseButtonPressedEvent event(button);
data.event_callback_func(event);
}
case GLFW_RELEASE:
{
MouseButtonReleasedEvent event(button);
data.event_callback_func(event);
}
}
});
glfwSetScrollCallback(m_window, [](GLFWwindow* window, double xoffset, double yoffset)
{
Data& data = *(Data*) glfwGetWindowUserPointer(window);
MouseScrolledEvent event((float)xoffset, (float)yoffset);
data.event_callback_func(event);
});
glfwSetCursorPosCallback(m_window, [](GLFWwindow* window, double xpos, double ypos)
{
Data& data = *(Data*) glfwGetWindowUserPointer(window);
MouseMovedEvent event((float)xpos, (float)ypos);
data.event_callback_func(event);
});
}
void OpenGLWindow::Shutdown()
{
glfwDestroyWindow(m_window);
}
void OpenGLWindow::OnUpdate()
{
glfwPollEvents();
m_context->SwapBuffers();
}
void OpenGLWindow::SetVSync(bool enabled)
{
if(enabled)
{
glfwSwapInterval(1);
}
else
{
glfwSwapInterval(0);
}
m_data.vsyncFlag = enabled;
}
bool OpenGLWindow::IsVSync() const
{
return m_data.vsyncFlag;
}
}

View File

@ -0,0 +1,56 @@
/*
OpenGLWindow.h
Implementation of a window with OpenGL context. Not really OpenGL specific, other than in creation of GraphicsContext.
Bulk of creation can be used in any api/context (glfw compatible with Cocoa, X11, or Windows). Based entirely upon the
work of @TheCherno in his game engine series.
GWM -- Feb 2022
*/
#ifndef OPENGL_WINDOW_H
#define OPENGL_WINDOW_H
#include "Specter/Core/SpecCore.h"
#include "Specter/Core/Window.h"
#include "Specter/Renderer/GraphicsContext.h"
#include "GLFW/glfw3.h"
namespace Specter {
class OpenGLWindow : public Window
{
public:
OpenGLWindow(const WindowProperties& props);
virtual ~OpenGLWindow();
void OnUpdate() override;
inline void SetEventCallback(const EventCallbackFunc& function) override { m_data.event_callback_func = function; }
inline unsigned int GetWidth() const override { return m_data.width; }
inline unsigned int GetHeight() const override { return m_data.height; }
inline std::string GetName() const override { return m_data.name; }
void SetVSync(bool enabled) override;
bool IsVSync() const override;
inline virtual void* GetNativeWindow() const override { return m_window; }
private:
virtual void Init(const WindowProperties& props);
virtual void Shutdown();
GraphicsContext* m_context;
GLFWwindow* m_window;
struct Data
{
int height, width;
std::string name;
bool vsyncFlag;
EventCallbackFunc event_callback_func;
};
Data m_data;
};
}
#endif

38
Specter/src/Specter.h Normal file
View File

@ -0,0 +1,38 @@
/*
Specter.h
This header file contains all of the essential includes for a project made using the Specter library.
Should be included into your main project files.
GWM -- Feb 2022
*/
#ifndef SPECTER_H
#define SPECTER_H
#include <iostream>
#include <fstream>
#include <memory>
#include <utility>
#include <algorithm>
#include <functional>
#include <string>
#include <sstream>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <cstdint>
#include "Specter/Core/Logger.h"
#include "Specter/Core/Application.h"
#include "Specter/Physics/PhysicsLayer.h"
#include "Specter/Physics/AnalysisStage.h"
#include "Specter/Core/Parameter.h"
#include "Specter/Core/SpectrumManager.h"
#include "Specter/Core/Layer.h"
#include "Specter/Events/Event.h"
#include "Specter/Utils/TestServerLayer.h"
#include "Specter/Utils/Instrumentor.h"
#endif

View File

@ -0,0 +1,105 @@
/*
Application.cpp
This is the main application class, master and controller of program flow. Contains a layer stack, where each layer represets an
aspect of the application. Based on the Application class written by @TheCherno during his tutorial series on making a game engine.
Check out his work to learn more!
Note that Application is a singleton. Should only ever be one application ever created in the project.
GWM -- Feb 2022
*/
#include "Application.h"
#include "Specter/Renderer/RenderCommand.h"
#include "Specter/Editor/EditorLayer.h"
#include "Timestep.h"
namespace Specter {
Application* Application::s_instance = nullptr;
Application::Application() :
m_runFlag(true)
{
SPEC_PROFILE_FUNCTION();
s_instance = this;
m_window = std::unique_ptr<Window>(Window::Create());
m_window->SetEventCallback(BIND_EVENT_FUNCTION(Application::OnEvent)); //Allow window to pass events back
m_physicsLayer = new PhysicsLayer();
PushLayer(m_physicsLayer);
EditorLayer* editor = new EditorLayer(); //memory handled by layer stack
editor->SetEventCallbackFunc(BIND_EVENT_FUNCTION(Application::OnEvent)); //Allow editor to pass events back
PushLayer(editor);
m_imgui_layer = new ImGuiLayer();
PushOverlay(m_imgui_layer);
}
Application::~Application()
{
}
void Application::OnEvent(Event& event)
{
SPEC_PROFILE_FUNCTION();
EventDispatcher dispatch(event);
dispatch.Dispatch<WindowCloseEvent>(BIND_EVENT_FUNCTION(Application::OnWindowCloseEvent));
for(auto iter = m_stack.end(); iter != m_stack.begin(); )
{
(*(--iter))->OnEvent(event);
if(event.handledFlag)
break;
}
}
bool Application::OnWindowCloseEvent(WindowCloseEvent& event)
{
m_runFlag = false;
SPEC_WARN("Closing the window!");
return true;
}
void Application::PushLayer(Layer* layer)
{
SPEC_PROFILE_FUNCTION();
m_stack.PushLayer(layer);
layer->OnAttach();
}
void Application::PushOverlay(Layer* layer)
{
SPEC_PROFILE_FUNCTION();
m_stack.PushOverlay(layer);
layer->OnAttach();
}
void Application::Run()
{
SPEC_PROFILE_FUNCTION();
float lastFrameTime = 0;
float time;
Timestep step;
while(m_runFlag)
{
RenderCommand::SetClearColor(m_bckgnd_color);
RenderCommand::Clear();
time = RenderCommand::GetFrameTime();
step.SetTime(time - lastFrameTime);
lastFrameTime = time;
for(auto layer : m_stack)
layer->OnUpdate(step);
m_imgui_layer->Begin();
for(auto layer : m_stack)
layer->OnImGuiRender();
m_imgui_layer->End();
m_window->OnUpdate();
}
}
}

View File

@ -0,0 +1,68 @@
/*
Application.h
This is the main application class, master and controller of program flow. Contains a layer stack, where each layer represets an
aspect of the application. Based on the Application class written by @TheCherno during his tutorial series on making a game engine.
Check out his work to learn more!
Note that Application is a singleton. Should only ever be one application ever created in the project.
GWM -- Feb 2022
*/
#ifndef APPLICATION_H
#define APPLICATION_H
#include "Specter/Core/SpecCore.h"
#include "Specter/Events/Event.h"
#include "Specter/Events/AppEvent.h"
#include "Specter/Events/PhysicsEvent.h"
#include "Specter/Core/LayerStack.h"
#include "Specter/Core/Layer.h"
#include "Specter/Core/Window.h"
#include "Specter/ImGui/ImGuiLayer.h"
#include "Specter/Physics/PhysicsLayer.h"
#include "glm/vec4.hpp"
namespace Specter {
class Application
{
public:
Application();
virtual ~Application();
void Run();
inline void Close() { m_runFlag = false; }
void OnEvent(Event& event);
void PushLayer(Layer* layer);
inline void PushAnalysisStage(AnalysisStage* stage) { m_physicsLayer->PushStage(stage); }
void PushOverlay(Layer* layer);
inline static Application& Get() { return *s_instance; }
inline Window& GetWindow() { return *m_window; }
private:
bool OnWindowCloseEvent(WindowCloseEvent& event);
LayerStack m_stack;
std::unique_ptr<Window> m_window;
ImGuiLayer* m_imgui_layer;
PhysicsLayer* m_physicsLayer;
bool m_runFlag;
//Dark grey background
glm::vec4 m_bckgnd_color = {0.1f, 0.1f, 0.1f, 1.0f};
static Application* s_instance;
};
/*
This function is left to be defined by the user. In principle we don't need to do this, as the Specter library doesn't handle creation of the application,
but I like it and might be useful for changing to a system with a pre-defined entry point.
*/
Application* CreateApplication();
}
#endif

View File

@ -0,0 +1,133 @@
/*
Cut.cpp
Cut related classes. A cut here is defined as a filter upon a histogram based on some range for a parameter or set of parameters.
CutArgs is the underlying data that defines a cut (excluding the actual points).
Cut is the base class for all cut objects. Should not be used in practice. All cut objects have functions which can query what kind of cut it is. If one has the cut object,
Is1D() or Is2D() can be called. If one has the CutArgs, a 1D cut will have y_par set to "None" while a 2D cut will have a valid parameter name.
Cut1D is a one-dimensional (single parameter cut) while Cut2D is a two-dimensional (two parameter cut). There are a few differences between 1D and 2D cuts.
A Cut1D only contains two values, a min and a max. The parameter is checked that it falls within these bounds.
A Cut2D contains a set of (x,y) points that form a closed polygon. The polygon may be convex. The parameter is checked that it falls within the polygon using similar methods
to flood-fill algorithms.
GWM -- Feb 2022
*/
#include "Cut.h"
#include "implot.h"
namespace Specter {
std::string ConvertCutTypeToString(CutType type)
{
SPEC_PROFILE_FUNCTION();
switch(type)
{
case CutType::Cut1D: return "Cut1D";
case CutType::Cut2D: return "Cut2D";
case CutType::CutSummaryAll: return "CutSummaryAll";
case CutType::CutSummaryAny: return "CutSummaryAny";
case CutType::None: return "None";
}
return "None";
}
/*1D Cuts -- Can be made on and applied to either 1D or 2D histograms*/
Cut1D::Cut1D(const CutArgs& params, double min, double max) :
Cut(params), m_minVal(min), m_maxVal(max)
{
m_params.type = CutType::Cut1D;
}
Cut1D::~Cut1D() {}
void Cut1D::IsInside(double x, double y)
{
SPEC_PROFILE_FUNCTION();
m_isValid = x >= m_minVal && x <= m_maxVal;
}
//Only within an ImPlot/ImGui context!!!
void Cut1D::Draw() const
{
SPEC_PROFILE_FUNCTION();
double points[2] = { m_minVal, m_maxVal };
ImPlot::PlotVLines(m_params.name.c_str(), points, 2);
}
/*2D Cuts -- Can only be made on 2D histogram, but applied to either 1D or 2D histograms*/
Cut2D::Cut2D(const CutArgs& params, const std::vector<double>& xpoints, const std::vector<double>& ypoints) :
Cut(params), m_xpoints(xpoints), m_ypoints(ypoints)
{
m_params.type = CutType::Cut2D;
}
Cut2D::~Cut2D() {}
/*
Even-odd point in polygon algorithm (see Wikipedia)
Walk around the sides of the polygon and check intersection with each of the sides.
Cast a ray from the point to infinity in any direction and check the number of intersections.
If odd number of intersections, point is inside. Even, point is outside.
Edge cases of point is a vertex or on a side considered.
*/
void Cut2D::IsInside(double x, double y)
{
SPEC_PROFILE_FUNCTION();
m_isValid = false;
double slope;
for(size_t i=0; i<(m_xpoints.size()-1); i++)
{
if (x == m_xpoints[i + 1] && y == m_ypoints[i + 1])
{
m_isValid = true;
return;
}
else if((m_ypoints[i+1] > y) != (m_ypoints[i] > y))
{
slope = (x - m_xpoints[i+1])*(m_ypoints[i] - m_ypoints[i+1]) - (m_xpoints[i] - m_xpoints[i+1])*(y - m_ypoints[i+1]);
if (slope == 0.0)
{
m_isValid = true;
return;
}
else if ((slope < 0.0) != (m_ypoints[i] < m_ypoints[i + 1]))
{
m_isValid = !m_isValid;
}
}
}
}
//Only in ImPlot/ImGui context!!!!
void Cut2D::Draw() const
{
SPEC_PROFILE_FUNCTION();
ImPlot::PlotLine(m_params.name.c_str(), m_xpoints.data(), m_ypoints.data(), (int)m_xpoints.size());
}
/*CutSummaryAll -- Can only be made on a HistogramSummary but can be applied to any*/
CutSummary::CutSummary(const CutArgs& params, const std::vector<std::string>& subhistos, double min, double max) :
Cut(params), m_subhistos(subhistos), m_minVal(min), m_maxVal(max)
{
}
CutSummary::~CutSummary()
{
}
void CutSummary::IsInside(double x, double y)
{
SPEC_PROFILE_FUNCTION();
m_isValid = x >= m_minVal && x <= m_maxVal;
}
//Only within an ImPlot/ImGui context!!!
void CutSummary::Draw() const
{
SPEC_PROFILE_FUNCTION();
double points[2] = { m_minVal, m_maxVal };
ImPlot::PlotVLines(m_params.name.c_str(), points, 2);
}
}

View File

@ -0,0 +1,126 @@
/*
Cut.h
Cut related classes. A cut here is defined as a filter upon a histogram based on some range for a parameter or set of parameters.
CutArgs is the underlying data that defines a cut (excluding the actual points).
Cut is the base class for all cut objects. Should not be used in practice. All cut objects have functions which can query what kind of cut it is. If one has the cut object,
Is1D() or Is2D() can be called. If one has the CutArgs, a 1D cut will have y_par set to "None" while a 2D cut will have a valid parameter name.
Cut1D is a one-dimensional (single parameter cut) while Cut2D is a two-dimensional (two parameter cut). There are a few differences between 1D and 2D cuts.
A Cut1D only contains two values, a min and a max. The parameter is checked that it falls within these bounds.
A Cut2D contains a set of (x,y) points that form a closed polygon. The polygon may be convex. The parameter is checked that it falls within the polygon using similar methods
to flood-fill algorithms.
GWM -- Feb 2022
*/
#ifndef CUT_H
#define CUT_H
#include "SpecCore.h"
#include "imgui.h"
namespace Specter {
enum class CutType
{
Cut1D,
Cut2D,
CutSummaryAny,
CutSummaryAll,
None
};
std::string ConvertCutTypeToString(CutType type);
struct CutArgs
{
CutArgs() {}
CutArgs(const std::string& n, const std::string& x, const std::string& y = "None") :
name(n), x_par(x), y_par(y)
{
}
CutType type = CutType::None;
std::string name = "None";
std::string x_par = "None";
std::string y_par = "None";
};
class Cut
{
public:
Cut(const CutArgs& params) :
m_params(params), m_isValid(false)
{
}
virtual ~Cut() {}
virtual void IsInside(double x, double y=0.0) = 0;
virtual void Draw() const = 0;
virtual std::vector<double> GetXValues() const = 0;
virtual std::vector<double> GetYValues() const = 0;
inline const bool IsValid() const { return m_isValid; }
inline void ResetValidity() { m_isValid = false; }
inline CutType GetType() const { return m_params.type; }
inline const std::string& GetName() const { return m_params.name; }
inline const std::string& GetXParameter() const { return m_params.x_par; }
inline const std::string& GetYParameter() const { return m_params.y_par; }
inline const CutArgs& GetCutArgs() const { return m_params; }
protected:
CutArgs m_params;
bool m_isValid;
};
class Cut1D : public Cut
{
public:
Cut1D(const CutArgs& params, double min, double max);
virtual ~Cut1D();
virtual void IsInside(double x, double y=0.0) override;
virtual void Draw() const override;
virtual std::vector<double> GetXValues() const override { return std::vector<double>({ m_minVal, m_maxVal }); }
virtual std::vector<double> GetYValues() const override { return std::vector<double>(); }
private:
double m_minVal, m_maxVal;
};
class Cut2D : public Cut
{
public:
Cut2D(const CutArgs& params, const std::vector<double>& xpoints, const std::vector<double>& ypoints);
virtual ~Cut2D();
virtual void IsInside(double x, double y) override;
virtual void Draw() const override;
virtual std::vector<double> GetXValues() const override { return m_xpoints; }
virtual std::vector<double> GetYValues() const override { return m_ypoints; }
private:
std::vector<double> m_xpoints;
std::vector<double> m_ypoints;
};
class CutSummary : public Cut
{
public:
CutSummary(const CutArgs& params, const std::vector<std::string>& subhistos, double min, double max);
virtual ~CutSummary();
virtual void IsInside(double x, double y) override;
virtual void Draw() const override;
virtual std::vector<double> GetXValues() const override { return std::vector<double>({ m_minVal, m_maxVal }); }
virtual std::vector<double> GetYValues() const override { return std::vector<double>(); }
inline const std::vector<std::string>& GetSubHistograms() const { return m_subhistos; }
private:
double m_minVal, m_maxVal;
std::vector<std::string> m_subhistos;
};
}
#endif

View File

@ -0,0 +1,42 @@
#include "Graph.h"
#include "implot.h"
namespace Specter {
ScalerGraph::ScalerGraph(const GraphArgs& args) :
m_args(args), m_lastScalerVal(0)
{
m_xPoints.reserve(m_args.maxPoints);
m_xPoints.reserve(m_args.maxPoints);
}
ScalerGraph::~ScalerGraph() {}
void ScalerGraph::UpdatePoints(double timeStep, uint64_t scalerVal)
{
double rate = (scalerVal - m_lastScalerVal) / timeStep;
if (m_xPoints.size() < m_args.maxPoints)
{
m_xPoints.push_back(timeStep);
m_yPoints.push_back(rate);
}
else
{
for (size_t i = 0; i < (m_args.maxPoints - 1); i++)
{
m_xPoints[i] = m_xPoints[i + 1];
m_yPoints[i] = m_yPoints[i + 1];
}
m_xPoints[m_args.maxPoints - 1] = timeStep;
m_yPoints[m_args.maxPoints - 1] = rate;
}
m_lastScalerVal = scalerVal;
}
void ScalerGraph::Draw()
{
ImPlot::SetupAxes("time (s)", "rate (Hz)");
ImPlot::PlotLine(m_args.name.c_str(), m_xPoints.data(), m_yPoints.data(), m_xPoints.size());
}
}

View File

@ -0,0 +1,39 @@
#ifndef GRAPH_H
#define GRAPH_H
namespace Specter {
struct GraphArgs
{
std::string name;
std::string scalerName;
size_t maxPoints;
};
class ScalerGraph
{
public:
ScalerGraph(const GraphArgs& args);
~ScalerGraph();
void UpdatePoints(double timeStep, uint64_t scalerVal);
void Draw();
inline void Clear() { m_xPoints.clear(); m_yPoints.clear(); }
inline const std::string& GetName() const { return m_args.name; }
inline const std::string& GetScaler() const { return m_args.scalerName; }
inline const GraphArgs& GetArgs() const { return m_args; }
private:
GraphArgs m_args;
uint64_t m_lastScalerVal;
std::vector<double> m_yPoints;
std::vector<double> m_xPoints;
};
}
#endif

View File

@ -0,0 +1,421 @@
/*
Histogram.cpp
Histogram related classes. We use a custom histogram class here because the ImPlot histograms are not a data structure, but rather just a function. This means that at every draw call for an ImPlot
histogram the entire data set will need to be properly binned and memory will need to be allocated to make histogram arrays. For our use case this is obviously bad. For one thing, data runs can have
thousands to millions of events. In the ImPlot paradigm we would need to loop over all of this data and bin it, not to mention explicitly store all of this data in memory for every histogram. I point this
out not to say that ImPlot histograms are bad intrinsically, because they definitely have a use for smaller data sets, but rather to explain why for this program I have re-invented the wheel somewhat.
HistogramArgs are the underlying data which define a histogram. This is grouped in a struct to easily pass these around for use in contexts like the Editor.
Every histogram has a set of histogram parameters.
Histogram is the base class of all histograms. Should not be used in practice. Every histogram contains functions to query what type of underlying histogram it is. If one has
the Histogram object, Is1D() or Is2D() can be called. If one only has the HistogramArgs, the values of x_par and y_par can be inspected. In particular, a 1D histogram will have
y_par set to "None", while a 2D histogram should have a valid parameter name for y_par.
Histogram1D is a one dimensional (single parameter) histogram. Histogram2D is a two dimensional (two parameter) histogram. The only real difference between these in practice, other than
the obvious two vs. one parameter thing, is that a Histogram2D contains methods to set the z-axis range (color scale) which ImPlot does not provide intrinsic access to from the plot itself.
When the range is set to (0,0), the color scale is set to the default (0, maxValue). Otherwise the color is scaled as appropriate. If you query a Histogram1D for its color scale you will recieve
a nullptr.
StatResults is a struct containing statistical information about a region of a histogram.
GWM -- Feb 2022
*/
#include "Histogram.h"
#include "implot.h"
/*
For Specter histograms, binnning is done over ranges as
[min, max), so that each bin is exclusive.
*/
namespace Specter {
std::string ConvertSpectrumTypeToString(SpectrumType type)
{
SPEC_PROFILE_FUNCTION();
switch (type)
{
case SpectrumType::Histo1D: return "Histogram1D";
case SpectrumType::Histo2D: return "Histogram2D";
case SpectrumType::Summary: return "Summary";
case SpectrumType::None: return "None";
}
return "None";
}
/*
1D Histogram class
*/
Histogram1D::Histogram1D(const HistogramArgs& params) :
Histogram(params)
{
InitBins();
}
Histogram1D::~Histogram1D() {}
void Histogram1D::InitBins()
{
SPEC_PROFILE_FUNCTION();
m_params.type = SpectrumType::Histo1D;
if(m_params.nbins_x == 0 || (m_params.min_x >= m_params.max_x))
{
SPEC_WARN("Attempting to create an illegal Histogram1D {0} with {1} bins and a range from {2} to {3}. Historgram not initialized.", m_params.name, m_params.nbins_x, m_params.min_x, m_params.max_x);
m_initFlag = false;
return;
}
m_binWidth = (m_params.max_x - m_params.min_x)/m_params.nbins_x;
m_binCenters.resize(m_params.nbins_x);
m_binCounts.resize(m_params.nbins_x);
for(int i=0; i<m_params.nbins_x; i++)
{
m_binCenters[i] = m_params.min_x + i*m_binWidth + m_binWidth*0.5;
m_binCounts[i] = 0;
}
m_initFlag = true;
}
//Note: only x is used here, y is simply present to maintain compliance with 2D case and can be ignored
void Histogram1D::FillData(double x, double y)
{
SPEC_PROFILE_FUNCTION();
if (x < m_params.min_x || x >= m_params.max_x)
return;
int bin = int((x - m_params.min_x)/(m_binWidth));
m_binCounts[bin] += 1.0;
}
//Can only be used within an ImGui / ImPlot context!!
void Histogram1D::Draw()
{
SPEC_PROFILE_FUNCTION();
ImPlot::SetupAxes(m_params.x_par.c_str(), "Counts",0, ImPlotAxisFlags_LockMin | ImPlotAxisFlags_AutoFit);
ImPlot::PlotBars(m_params.name.c_str(), &m_binCenters.data()[0], &m_binCounts.data()[0], m_params.nbins_x, m_binWidth);
}
void Histogram1D::ClearData()
{
for(int i=0; i<m_params.nbins_x; i++)
m_binCounts[i] = 0;
}
//Again here yvalues can be ignored, only for compliance
StatResults Histogram1D::AnalyzeRegion(double x_min, double x_max, double y_min, double y_max)
{
SPEC_PROFILE_FUNCTION();
int bin_min, bin_max;
StatResults results;
//We clamp to the boundaries of the histogram
if (x_min <= m_params.min_x)
bin_min = 0;
else
bin_min = int((x_min - m_params.min_x) / (m_binWidth));
if (x_max >= m_params.max_x)
bin_max = m_params.nbins_x - 1;
else
bin_max = int((x_max - m_params.min_x) / (m_binWidth));
for (int i = bin_min; i <= bin_max; i++)
{
results.integral += m_binCounts[i];
results.cent_x += m_binCounts[i] * (m_params.min_x + m_binWidth * i);
}
if (results.integral == 0)
return results;
results.cent_x /= results.integral;
for (int i = bin_min; i <= bin_max; i++)
results.sigma_x += m_binCounts[i] * ((m_params.min_x + m_binWidth * i) - results.cent_x) * ((m_params.min_x + m_binWidth * i) - results.cent_x);
results.sigma_x = std::sqrt(results.sigma_x / (results.integral - 1));
return results;
}
/*
2D Histogram class
Note for 2D: Rendering is done from top left to bottom right. So ybins run from top to bottom (ymin is last row, ymax is first row)
*/
Histogram2D::Histogram2D(const HistogramArgs& params) :
Histogram(params)
{
m_colorScaleRange[0] = 0.0f;
m_colorScaleRange[1] = 0.0f;
InitBins();
}
Histogram2D::~Histogram2D() {}
void Histogram2D::InitBins()
{
SPEC_PROFILE_FUNCTION();
m_params.type = SpectrumType::Histo2D;
if(m_params.nbins_x <= 0 || m_params.nbins_y <= 0 || m_params.min_x >= m_params.max_x || m_params.min_y >= m_params.max_y)
{
SPEC_WARN("Attempting to create illegal Histogram2D {0} with {1} x bins, {2} y bins, an x range of {3} to {4}, and a y range of {5} to {6}. Not initialized.", m_params.name, m_params.nbins_x, m_params.nbins_y,
m_params.min_x, m_params.max_x, m_params.min_y, m_params.max_y);
m_initFlag = false;
return;
}
m_binWidthX = (m_params.max_x - m_params.min_x)/m_params.nbins_x;
m_binWidthY = (m_params.max_y - m_params.min_y)/m_params.nbins_y;
m_nBinsTotal = m_params.nbins_x*m_params.nbins_y;
m_binCounts.resize(m_nBinsTotal);
for(int i=0; i<m_nBinsTotal; i++)
m_binCounts[i] = 0;
m_maxBinContent = 0;
m_initFlag = true;
}
void Histogram2D::FillData(double x, double y)
{
SPEC_PROFILE_FUNCTION();
if (x < m_params.min_x || x >= m_params.max_x || y <= m_params.min_y || y > m_params.max_y)
return;
int bin_x = int((x - m_params.min_x)/m_binWidthX);
int bin_y = int((m_params.max_y - y)/m_binWidthY);
int bin = bin_y*m_params.nbins_x + bin_x;
m_binCounts[bin] += 1.0;
m_maxBinContent = m_binCounts[bin] > m_maxBinContent ? (m_binCounts[bin]) : m_maxBinContent;
}
//Can only be used within an ImGui / ImPlot context!!
/*
Brief note on colormaps: There are several kinds of colormaps, each with specific use cases. But broadly, the two main categories are discrete and continuous.
Discrete maps are good for categorical data, and we use these as the standard for plots we draw. This is how you make cuts standout from your histograms.
But for the colors of the bins, we want a continuous scale, i.e. smooth variation on a single hue, as the color differences represent the value differences
relative to other bins. Hence the pushing of the Matplotlib virdis colormap. For more on colormaps, I suggest looking at matplotlib documentation.
*/
void Histogram2D::Draw()
{
SPEC_PROFILE_FUNCTION();
ImPlot::SetupAxes(m_params.x_par.c_str(), m_params.y_par.c_str());
ImPlot::PushColormap(ImPlotColormap_Viridis);
ImPlot::PlotHeatmap(m_params.name.c_str(), &m_binCounts.data()[0], m_params.nbins_y, m_params.nbins_x, m_colorScaleRange[0], m_colorScaleRange[1], NULL,
ImPlotPoint(m_params.min_x, m_params.min_y), ImPlotPoint(m_params.max_x, m_params.max_y));
ImPlot::PopColormap();
}
void Histogram2D::ClearData()
{
for(int i=0; i<m_nBinsTotal; i++)
m_binCounts[i] = 0;
m_maxBinContent = 0;
}
StatResults Histogram2D::AnalyzeRegion(double x_min, double x_max, double y_min, double y_max)
{
SPEC_PROFILE_FUNCTION();
int xbin_min, xbin_max, ybin_min, ybin_max;
int curbin;
StatResults results;
//We clamp to the boundaries of the histogram
if (x_min <= m_params.min_x)
xbin_min = 0;
else
xbin_min = int((x_min - m_params.min_x) / (m_binWidthX));
if (x_max >= m_params.max_x)
xbin_max = m_params.nbins_x - 1;
else
xbin_max = int((x_max - m_params.min_x) / (m_binWidthX));
if (y_min <= m_params.min_y)
ybin_max = m_params.nbins_y - 1;
else
ybin_max = int((m_params.max_y - y_min) / m_binWidthY);
if (y_max >= m_params.max_y)
ybin_min = 0;
else
ybin_min = int((m_params.max_y - y_max) / m_binWidthY);
for (int y = ybin_min; y <= ybin_max; y++)
{
for (int x = xbin_min; x <= xbin_max; x++)
{
curbin = y * m_params.nbins_x + x;
results.integral += m_binCounts[curbin];
results.cent_x += m_binCounts[curbin] * (m_params.min_x + m_binWidthX * x);
results.cent_y += m_binCounts[curbin] * (m_params.max_y - m_binWidthY * y);
}
}
if (results.integral == 0)
return results;
results.cent_x /= results.integral;
results.cent_y /= results.integral;
for (int y = ybin_min; y <= ybin_max; y++)
{
for (int x = xbin_min; x <= xbin_max; x++)
{
curbin = y * m_params.nbins_x + x;
results.sigma_x += m_binCounts[curbin] * ((m_params.min_x + m_binWidthX * x) - results.cent_x) * ((m_params.min_x + m_binWidthX * x) - results.cent_x);
results.sigma_y += m_binCounts[curbin] * ((m_params.max_y - m_binWidthY * y) - results.cent_y) * ((m_params.max_y - m_binWidthY * y) - results.cent_y);
}
}
results.sigma_x = std::sqrt(results.sigma_x / (results.integral - 1));
results.sigma_y = std::sqrt(results.sigma_y / (results.integral - 1));
return results;
}
/*
HistogramSummary class methods
-- 03/18/22 Adding in the basics, unsure of how to exactly approach design
-- Fill data to independent histogram? Steal data from other histograms?
-- Cuts?
-- Literally everything hahaha
*/
HistogramSummary::HistogramSummary(const HistogramArgs& params, const std::vector<std::string>& subhistos) :
Histogram(params), m_subhistos(subhistos), m_labels(nullptr)
{
m_colorScaleRange[0] = 0.0f;
m_colorScaleRange[1] = 0.0f;
InitBins();
}
HistogramSummary::~HistogramSummary()
{
if (m_labels)
delete[] m_labels;
}
void HistogramSummary::InitBins()
{
SPEC_PROFILE_FUNCTION();
m_params.type = SpectrumType::Summary;
if (m_params.nbins_x <= 0 || m_params.min_x >= m_params.max_x)
{
SPEC_WARN("Attempting to create illegal HistogramSummary {0} with {1} x bins and an x range of {2} to {3}. Not initialized.", m_params.name, m_params.nbins_x, m_params.min_x, m_params.max_x);
m_initFlag = false;
return;
}
m_labels = new const char* [m_subhistos.size() + 1];
for (size_t i = 0; i < m_subhistos.size(); i++)
m_labels[i] = m_subhistos[i].c_str();
m_labels[m_subhistos.size()] = "";
m_params.nbins_y = m_subhistos.size();
m_params.min_y = 0.0;
m_params.max_y = m_subhistos.size();
m_binWidthX = (m_params.max_x - m_params.min_x) / m_params.nbins_x;
m_nBinsTotal = m_params.nbins_x * m_params.nbins_y;
m_binCounts.resize(m_nBinsTotal);
for (int i = 0; i < m_nBinsTotal; i++)
m_binCounts[i] = 0;
m_initFlag = true;
}
void HistogramSummary::FillData(double x, double y)
{
SPEC_PROFILE_FUNCTION();
if (x < m_params.min_x || x >= m_params.max_x || y <= m_params.min_y || y > m_params.max_y)
return;
int bin_x = int((x - m_params.min_x) / m_binWidthX);
int bin_y = int((m_params.max_y - y) / m_binWidthY);
int bin = bin_y * m_params.nbins_x + bin_x;
m_binCounts[bin] += 1.0;
}
void HistogramSummary::Draw()
{
SPEC_PROFILE_FUNCTION();
ImPlot::SetupAxisTicks(ImAxis_Y1, m_params.min_y, m_params.max_y, m_params.nbins_y, m_labels, false);
ImPlot::PushColormap(ImPlotColormap_Viridis);
ImPlot::PlotHeatmap(m_params.name.c_str(), &m_binCounts.data()[0], m_params.nbins_y, m_params.nbins_x, m_colorScaleRange[0], m_colorScaleRange[1], NULL,
ImPlotPoint(m_params.min_x, m_params.min_y), ImPlotPoint(m_params.max_x, m_params.max_y));
ImPlot::PopColormap();
}
void HistogramSummary::ClearData()
{
for (int i = 0; i < m_nBinsTotal; i++)
m_binCounts[i] = 0;
}
StatResults HistogramSummary::AnalyzeRegion(double x_min, double x_max, double y_min, double y_max)
{
SPEC_PROFILE_FUNCTION();
int xbin_min, xbin_max, ybin_min, ybin_max;
int curbin;
StatResults results;
//We clamp to the boundaries of the histogram
if (x_min <= m_params.min_x)
xbin_min = 0;
else
xbin_min = int((x_min - m_params.min_x) / (m_binWidthX));
if (x_max >= m_params.max_x)
xbin_max = m_params.nbins_x - 1;
else
xbin_max = int((x_max - m_params.min_x) / (m_binWidthX));
if (y_min <= m_params.min_y)
ybin_max = m_params.nbins_y - 1;
else
ybin_max = int((m_params.max_y - y_min) / m_binWidthY);
if (y_max >= m_params.max_y)
ybin_min = 0;
else
ybin_min = int((m_params.max_y - y_max) / m_binWidthY);
for (int y = ybin_min; y <= ybin_max; y++)
{
for (int x = xbin_min; x <= xbin_max; x++)
{
curbin = y * m_params.nbins_x + x;
results.integral += m_binCounts[curbin];
results.cent_x += m_binCounts[curbin] * (m_params.min_x + m_binWidthX * x);
results.cent_y += m_binCounts[curbin] * (m_params.max_y - m_binWidthY * y);
}
}
if (results.integral == 0)
return results;
results.cent_x /= results.integral;
results.cent_y /= results.integral;
for (int y = ybin_min; y <= ybin_max; y++)
{
for (int x = xbin_min; x <= xbin_max; x++)
{
curbin = y * m_params.nbins_x + x;
results.sigma_x += m_binCounts[curbin] * ((m_params.min_x + m_binWidthX * x) - results.cent_x) * ((m_params.min_x + m_binWidthX * x) - results.cent_x);
results.sigma_y += m_binCounts[curbin] * ((m_params.max_y - m_binWidthY * y) - results.cent_y) * ((m_params.max_y - m_binWidthY * y) - results.cent_y);
}
}
results.sigma_x = std::sqrt(results.sigma_x / (results.integral - 1));
results.sigma_y = std::sqrt(results.sigma_y / (results.integral - 1));
return results;
}
}

View File

@ -0,0 +1,184 @@
/*
Histogram.h
Histogram related classes. We use a custom histogram class here because the ImPlot histograms are not a data structure, but rather just a function. This means that at every draw call for an ImPlot
histogram the entire data set will need to be properly binned and memory will need to be allocated to make histogram arrays. For our use case this is obviously bad. For one thing, data runs can have
thousands to millions of events. In the ImPlot paradigm we would need to loop over all of this data and bin it, not to mention explicitly store all of this data in memory for every histogram. I point this
out not to say that ImPlot histograms are bad intrinsically, because they definitely have a use for smaller data sets, but rather to explain why for this program I have re-invented the wheel somewhat.
HistogramArgs are the underlying data which define a histogram. This is grouped in a struct to easily pass these around for use in contexts like the Editor.
Every histogram has a set of histogram parameters.
Histogram is the base class of all histograms. Should not be used in practice. Every histogram contains functions to query what type of underlying histogram it is. If one has
the Histogram object, Is1D() or Is2D() can be called. If one only has the HistogramArgs, the values of x_par and y_par can be inspected. In particular, a 1D histogram will have
y_par set to "None", while a 2D histogram should have a valid parameter name for y_par.
Histogram1D is a one dimensional (single parameter) histogram. Histogram2D is a two dimensional (two parameter) histogram. The only real difference between these in practice, other than
the obvious two vs. one parameter thing, is that a Histogram2D contains methods to set the z-axis range (color scale) which ImPlot does not provide intrinsic access to from the plot itself.
When the range is set to (0,0), the color scale is set to the default (0, maxValue). Otherwise the color is scaled as appropriate. If you query a Histogram1D for its color scale you will recieve
a nullptr.
StatResults is a struct containing statistical information about a region of a histogram.
GWM -- Feb 2022
*/
#ifndef HISTOGRAM_H
#define HISTOGRAM_H
#include "SpecCore.h"
namespace Specter {
enum class SpectrumType
{
Histo1D,
Histo2D,
Summary,
None
};
std::string ConvertSpectrumTypeToString(SpectrumType type);
struct StatResults
{
double integral = 0.0;
double cent_x = 0.0;
double cent_y = 0.0;
double sigma_x = 0.0;
double sigma_y = 0.0;
};
struct HistogramArgs
{
HistogramArgs() {}
HistogramArgs(const std::string& n, const std::string& x, int bins, double min, double max) :
name(n), x_par(x), nbins_x(bins), min_x(min), max_x(max)
{
}
HistogramArgs(const std::string& n, const std::string& x, const std::string& y, int binsx, double minx, double maxx, int binsy, double miny, double maxy) :
name(n), x_par(x), y_par(y), nbins_x(binsx), min_x(minx), max_x(maxx), nbins_y(binsy), min_y(miny), max_y(maxy)
{
}
SpectrumType type = SpectrumType::None;
std::string name = "None";
std::string x_par = "None";
std::string y_par = "None";
std::vector<std::string> cutsDrawnUpon;
std::vector<std::string> cutsAppliedTo;
int nbins_x = 0;
double min_x = 0;
double max_x = 0;
int nbins_y = 0;
double min_y = 0;
double max_y = 0;
};
class Histogram
{
public:
Histogram() :
m_initFlag(false)
{
}
Histogram(const HistogramArgs& params) :
m_params(params), m_initFlag(false)
{
}
virtual ~Histogram() {};
virtual void FillData(double x, double y=0.0) { SPEC_WARN("Trying to fill a default histogram!"); }
virtual void Draw() {}
virtual void ClearData() {}
virtual StatResults AnalyzeRegion(double x_min, double x_max, double y_min = 0.0, double y_max = 0.0) { return StatResults(); }
inline virtual float* GetColorScaleRange() { return nullptr; }
inline virtual std::vector<double> GetBinData() { return std::vector<double>(); }
inline HistogramArgs& GetParameters() { return m_params; }
inline SpectrumType GetType() { return m_params.type; }
inline const std::string& GetXParam() const { return m_params.x_par; };
inline const std::string& GetYParam() const { return m_params.y_par; };
inline const std::string& GetName() const { return m_params.name; }
inline void AddCutToBeDrawn(const std::string& name) { m_params.cutsDrawnUpon.push_back(name); }
inline void AddCutToBeApplied(const std::string& name) { m_params.cutsAppliedTo.push_back(name); }
protected:
HistogramArgs m_params;
bool m_initFlag;
};
class Histogram1D : public Histogram
{
public:
Histogram1D(const HistogramArgs& params);
virtual ~Histogram1D();
virtual void FillData(double x, double y=0.0) override;
virtual void Draw() override;
virtual void ClearData() override;
virtual StatResults AnalyzeRegion(double x_min, double x_max, double y_min = 0.0, double y_max = 0.0) override;
inline virtual std::vector<double> GetBinData() override { return m_binCounts; }
private:
void InitBins();
std::vector<double> m_binCenters;
std::vector<double> m_binCounts;
double m_binWidth;
};
class Histogram2D : public Histogram
{
public:
Histogram2D(const HistogramArgs& params);
virtual ~Histogram2D();
virtual void FillData(double x, double y) override;
virtual void Draw() override;
virtual void ClearData() override;
virtual StatResults AnalyzeRegion(double x_min, double x_max, double y_min = 0.0, double y_max = 0.0) override;
inline virtual std::vector<double> GetBinData() override { return m_binCounts; }
inline virtual float* GetColorScaleRange() override { return m_colorScaleRange; }
private:
void InitBins();
std::vector<double> m_binCounts;
int m_nBinsTotal;
double m_binWidthY;
double m_binWidthX;
double m_maxBinContent;
float m_colorScaleRange[2];
};
class HistogramSummary : public Histogram
{
public:
HistogramSummary(const HistogramArgs& params, const std::vector<std::string>& subhistos);
~HistogramSummary();
inline const std::vector<std::string>& GetSubHistograms() const { return m_subhistos; }
virtual void FillData(double x, double y) override;
virtual void ClearData() override;
virtual void Draw() override;
inline virtual float* GetColorScaleRange() override { return m_colorScaleRange; }
virtual StatResults AnalyzeRegion(double x_min, double x_max, double y_min = 0.0, double y_max = 0.0) override;
inline virtual std::vector<double> GetBinData() override { return m_binCounts; }
private:
void InitBins();
std::vector<std::string> m_subhistos;
const char** m_labels;
std::vector<double> m_binCounts;
int m_nBinsTotal;
double m_binWidthX;
const double m_binWidthY = 1.0;
float m_colorScaleRange[2];
};
}
#endif

View File

@ -0,0 +1,17 @@
/*
Layer.cpp
Layer is an abstract class representing an aspect of the application. Based entirely upon @TheCherno's tutorials in his game engine series.
GWM -- Feb 2022
*/
#include "Layer.h"
namespace Specter {
Layer::Layer(const std::string& name) :
m_name(name)
{
}
Layer::~Layer() {}
}

View File

@ -0,0 +1,36 @@
/*
Layer.h
Layer is an abstract class representing an aspect of the application. Based entirely upon @TheCherno's tutorials in his game engine series.
GWM -- Feb 2022
*/
#ifndef LAYER_H
#define LAYER_H
#include "SpecCore.h"
#include "Specter/Events/Event.h"
#include "Timestep.h"
namespace Specter {
class Layer
{
public:
Layer(const std::string& name="Layer");
virtual ~Layer();
virtual void OnAttach() {}
virtual void OnDetach() {}
virtual void OnImGuiRender() {}
virtual void OnUpdate(Timestep& step) {}
virtual void OnEvent(Event& event) {}
inline const std::string& GetName() { return m_name; }
private:
std::string m_name;
};
}
#endif

View File

@ -0,0 +1,49 @@
/*
LayerStack.cpp
LayerStack is a container for Layers. Should only be owned by the Application. Layers are
managed by the LayerStack (memory-wise). There are two types of layers, overlays and regular layers.
Overlays are processed first in the event stack. This is entirely based upon @TheCherno's work shown in his
game engine tutorial series.
GWM -- Feb 2022
*/
#include "LayerStack.h"
namespace Specter {
LayerStack::LayerStack() {}
LayerStack::~LayerStack()
{
for(Layer* layer : m_stack)
delete layer;
}
void LayerStack::PushLayer(Layer* layer)
{
m_stack.emplace(m_stack.begin() + m_insertIndex, layer);
m_insertIndex++;
}
void LayerStack::PushOverlay(Layer* layer)
{
m_stack.emplace_back(layer);
}
void LayerStack::PopLayer(Layer* layer)
{
auto iter = std::find(m_stack.begin(), m_stack.end(), layer);
if(iter != m_stack.end())
{
m_stack.erase(iter);
m_insertIndex--;
}
}
void LayerStack::PopOverlay(Layer* layer)
{
auto iter = std::find(m_stack.begin(), m_stack.end(), layer);
if(iter != m_stack.end())
m_stack.erase(iter);
}
}

View File

@ -0,0 +1,39 @@
/*
LayerStack.h
LayerStack is a container for Layers. Should only be owned by the Application. Layers are
managed by the LayerStack (memory-wise). There are two types of layers, overlays and regular layers.
Overlays are processed first in the event stack. This is entirely based upon @TheCherno's work shown in his
game engine tutorial series.
GWM -- Feb 2022
*/
#ifndef LAYER_STACK_H
#define LAYER_STACK_H
#include "SpecCore.h"
#include "Layer.h"
namespace Specter {
class LayerStack
{
public:
LayerStack();
~LayerStack();
void PushLayer(Layer* layer);
void PopLayer(Layer* layer);
void PushOverlay(Layer* layer);
void PopOverlay(Layer* layer);
//helpers for iterator for loops
std::vector<Layer*>::iterator begin() { return m_stack.begin(); }
std::vector<Layer*>::iterator end() { return m_stack.end(); }
private:
std::vector<Layer*> m_stack; //These layers are owned by the LayerStack!
unsigned int m_insertIndex=0; //used to keep track of where to put layers vs. overlays.
};
}
#endif

View File

@ -0,0 +1,26 @@
/*
Logger.cpp
Logging class which is a thin wrapper on the spdlog library. Must be initialized in the project at start (see NavProject main.cpp for example).
Again, strongly based upon @TheCherno's work, see his Hazel repository for more details.
Note Logger is a singleton. Should only ever be intialized once. Macros for calling the log provided to make clean looking code at the other side.
Consider making some logging calls only defined on Debug.
GWM -- Feb 2022
*/
#include "Logger.h"
#include "spdlog/sinks/stdout_color_sinks.h"
namespace Specter {
std::shared_ptr<spdlog::logger> Logger::s_logger;
void Logger::Init()
{
spdlog::set_pattern("%^[%T] %n: %v%$");
s_logger = spdlog::stdout_color_mt("NAV");
s_logger->set_level(spdlog::level::trace);
}
}

View File

@ -0,0 +1,42 @@
/*
Logger.h
Logging class which is a thin wrapper on the spdlog library. Must be initialized in the project at start (see NavProject main.cpp for example).
Again, strongly based upon @TheCherno's work, see his Hazel repository for more details.
Note Logger is a singleton. Should only ever be intialized once. Macros for calling the log provided to make clean looking code at the other side.
Consider making some logging calls only defined on Debug.
GWM -- Feb 2022
*/
#ifndef LOGGER_H
#define LOGGER_H
#include "SpecCore.h"
#include "spdlog/spdlog.h"
#include "spdlog/fmt/ostr.h"
namespace Specter {
class Logger
{
public:
static void Init();
inline static std::shared_ptr<spdlog::logger> GetLogger() { return s_logger; }
private:
static std::shared_ptr<spdlog::logger> s_logger;
};
}
//Macros for clean code. Different logging levels.
#define SPEC_CRITICAL(...) ::Specter::Logger::GetLogger()->critical(__VA_ARGS__)
#define SPEC_WARN(...) ::Specter::Logger::GetLogger()->warn(__VA_ARGS__)
#define SPEC_ERROR(...) ::Specter::Logger::GetLogger()->error(__VA_ARGS__)
#define SPEC_TRACE(...) ::Specter::Logger::GetLogger()->trace(__VA_ARGS__)
#define SPEC_INFO(...) ::Specter::Logger::GetLogger()->info(__VA_ARGS__)
#endif

View File

@ -0,0 +1,96 @@
/*
Parameter.cpp
Contains two data related structures, ParameterData and Parameter. Parameter here refers to a value which
can be associated with the axis of a histogram. ParameterData is a two component struct which represents the state of a single parameter, which is stored
in the global SpectrumManager (see SpectrumManager documentation). Parameter is an access point class for use within analysis. It provides scoped access to
the managed data. There are several important caveats to using the parameters this way, which is mostly related to synchronization:
- Parameter contains no guarantee of thread safety. THIS IS BY DESIGN. Parameters should only be created within the context of an AnalysisStage (see AnalysisStage documentation).
As long as this is true, there is no risk of accessing parameter data outside of the physics thread, and that thread explicitly handles calculation of parameters
and updating of histograms. DO NOT make a Parameter outside of the AnalysisStage context.
- Parameters must be bound to the active SpectrumManager. This is done using the BindParameter function of the manager; Parameters are keyed based on their name string.
If two Parameters are made that are both named "x1", they are in fact the same parameter. In this way, values can be passed from one AnalysisStage to another. If you make a stage
called InitialStage with a Parameter named "x1" where x1 is set to 1.0, and then have a later stage called LaterStage with another Parameter named x1, it implicitly has the value 1.0
due to it being set in the previous stage.
- Each Parameter has a valid flag. This is a boolean which idicates whether or not the parameter is in a valid state (the data event contianed a value for this parameter). Before using a parameter
in a calculation one should check if the parameter is valid using the IsValid() function. When a parameter is set using the SetValue() function, the valid flag is set to true. After the event is completely
processed (all analysis stages have been run and histograms have been updated) the manager should be called to run InvalidateParameters() to set all parameters as invalid (valid flag false).
Those are the key points I can think of now. As more people use the code, I expect this section to grow and change.
Credit to nscldaq and in particular NSCLSpecTcl which provided the inspiration for this parameter model.
GWM -- Feb 2022
*/
#include "Parameter.h"
namespace Specter {
Parameter::Parameter() :
m_name(""), m_pdata(nullptr)
{
}
Parameter::Parameter(const std::string& name) :
m_name(name), m_pdata(nullptr)
{
}
Parameter::~Parameter() {}
//So that you can make arrays, std::vectors of Parameters (useful for big detector arrays)
//SpectrumManager::BindParameter() still needs to be used after this!
void Parameter::SetName(const std::string& name)
{
if (m_name != "")
{
SPEC_ERROR("Attempting to change the name of an already bound Parameter! Set name: {0} New name: {1}", m_name, name);
return;
}
m_name = name;
}
Variable::Variable() :
m_name(""), m_pdata(nullptr)
{
}
Variable::Variable(const std::string& name) :
m_name(name), m_pdata(nullptr)
{
}
Variable::~Variable() {}
void Variable::SetName(const std::string& name)
{
if (m_name != "")
{
SPEC_ERROR("Attempting to change the name of an already bound Variable! Set name: {0} New name: {1}", m_name, name);
return;
}
m_name = name;
}
Scaler::Scaler() :
m_pdata(nullptr), m_name("")
{
}
Scaler::Scaler(const std::string& name) :
m_pdata(nullptr), m_name(name)
{
}
Scaler::~Scaler() {}
void Scaler::SetName(const std::string& name)
{
if (m_name != "")
{
SPEC_ERROR("Attempting to change the name of an already bound Scaler! Set name: {0} New name: {1}", m_name, name);
return;
}
m_name = name;
}
}

View File

@ -0,0 +1,106 @@
/*
Parameter.h
Contains two data related structures, ParameterData and Parameter. Parameter here refers to a value which
can be associated with the axis of a histogram. ParameterData is a two component struct which represents the state of a single parameter, which is stored
in the global SpectrumManager (see SpectrumManager documentation). Parameter is an access point class for use within analysis. It provides scoped access to
the managed data. There are several important caveats to using the parameters this way, which is mostly related to synchronization:
- Parameter contains no guarantee of thread safety. THIS IS BY DESIGN. Parameters should only be created within the context of an AnalysisStage (see AnalysisStage documentation).
As long as this is true, there is no risk of accessing parameter data outside of the physics thread, and that thread explicitly handles calculation of parameters
and updating of histograms. DO NOT make a Parameter outside of the AnalysisStage context.
- Parameters must be bound to the active SpectrumManager. This is done using the BindParameter function of the manager; Parameters are keyed based on their name string.
If two Parameters are made that are both named "x1", they are in fact the same parameter. In this way, values can be passed from one AnalysisStage to another. If you make a stage
called InitialStage with a Parameter named "x1" where x1 is set to 1.0, and then have a later stage called LaterStage with another Parameter named x1, it implicitly has the value 1.0
due to it being set in the previous stage.
- Each Parameter has a valid flag. This is a boolean which idicates whether or not the parameter is in a valid state (the data event contianed a value for this parameter). Before using a parameter
in a calculation one should check if the parameter is valid using the IsValid() function. When a parameter is set using the SetValue() function, the valid flag is set to true. After the event is completely
processed (all analysis stages have been run and histograms have been updated) the manager should be called to run InvalidateParameters() to set all parameters as invalid (valid flag false).
Those are the key points I can think of now. As more people use the code, I expect this section to grow and change rapidly.
Credit to nscldaq and in particular NSCLSpecTcl which provided the inspiration for this parameter model.
GWM -- Feb 2022
*/
#ifndef PARAMETER_H
#define PARAMETER_H
#include "SpecCore.h"
namespace Specter {
//Underlying data
struct ParameterData
{
double value=0.0;
bool validFlag=false;
};
//Interface to parameter data
class Parameter
{
public:
Parameter();
Parameter(const std::string& name);
~Parameter();
inline bool IsValid() const { return m_pdata->validFlag; }
inline void Invalidate() { m_pdata->validFlag = false; }
inline void SetValue(double value) { m_pdata->validFlag = true; m_pdata->value = value; }
inline double GetValue() const { return m_pdata->value; }
inline const std::string& GetName() const { return m_name; }
void SetName(const std::string& name);
friend class SpectrumManager;
private:
std::string m_name;
std::shared_ptr<ParameterData> m_pdata;
};
//Similar to parameters, sometimes you want to have a numeric input (in calculation terms, a constant)
//which you can use with your analysis. To be able to expose these numeric values to the UI, we need to implement them
//in the manager. To help with this, Variables are atomics. So unlike Parameters they are implicity thread safe on read and write.
//However, this does not mean they can be modified in the analysis! To the AnalysisStage they should be treated as constant, while the UI
//should view them as modifiable. These are real god damn dangerous, but I think the power they offer outweighs the risk, for now.
class Variable
{
public:
Variable();
Variable(const std::string& name);
~Variable();
inline void SetValue(double value) { *(m_pdata) = value; }
inline double GetValue() { return *(m_pdata); }
inline const std::string& GetName() { return m_name; }
void SetName(const std::string& name);
friend class SpectrumManager;
private:
std::shared_ptr<std::atomic<double>> m_pdata;
std::string m_name;
};
class Scaler
{
public:
Scaler();
Scaler(const std::string& name);
~Scaler();
inline void Increment() { ++(*m_pdata); }
inline const std::string& GetName() { return m_name; }
inline uint64_t GetCounts() { return *m_pdata; }
void SetName(const std::string& name);
friend class SpectrumManager;
private:
std::shared_ptr<std::atomic<uint64_t>> m_pdata;
std::string m_name;
};
}
#endif

View File

@ -0,0 +1,26 @@
#ifndef SPECCORE_H
#define SPECCORE_H
#ifdef SPEC_WINDOWS
#ifdef _MSC_VER
#pragma warning (disable: 4127) // condition expression is constant
#pragma warning (disable: 4251) // class 'xxx' needs to have dll-interface to be used by clients of struct 'xxx' // when NAV_API is set to__declspec(dllexport)
#pragma warning (disable: 4091) // '__declspec(dllimport)': ignored on left of class 'xxx' when no variable is declared
#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen
#if defined(_MSC_VER) && _MSC_VER >= 1922 // MSVC 2019 16.2 or later
#pragma warning (disable: 5054) // operator '|': deprecated between enumerations of different types
#endif
#pragma warning (disable: 26451) // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator 'xxx' to avoid overflow(io.2).
#pragma warning (disable: 26495) // [Static Analyzer] Variable 'XXX' is uninitialized. Always initialize a member variable (type.6).
#pragma warning (disable: 26812) // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3).
#endif
#endif
//Bit field setter
#define BIT(x) (1<<x)
//Macro to bind a function using lambda expressions
#define BIND_EVENT_FUNCTION(x) [this](auto&&... args) -> decltype(auto) { return this->x(std::forward<decltype(args)>(args)...); }
#endif

View File

@ -0,0 +1,631 @@
/*
SpectrumManager.cpp
SpectrumManager is the global resource management class. Controls everything related to spectra (histograms, cuts, parameters). Since
the manager must traverse threads, explicit synchronoization is handled through a mutex. To this end, excessive calls to the manager should be
avoided if possible, as this will increase the lock deadtime in the application, which is especially bad for online data sources.
Note that SpectrumManager is a singleton. There should only ever be one SpectrumManager with a given application.
GWM -- Feb 2022
*/
#include "SpectrumManager.h"
#include "implot.h"
namespace Specter {
SpectrumManager* SpectrumManager::s_instance = new SpectrumManager();
SpectrumManager::SpectrumManager()
{
}
SpectrumManager::~SpectrumManager()
{
}
/*************Histogram Functions Begin*************/
void SpectrumManager::AddHistogram(const HistogramArgs& params)
{
SPEC_PROFILE_FUNCTION();
std::scoped_lock<std::mutex> guard(m_managerMutex);
if (params.y_par == "None") //Check dimensionality
m_histoMap[params.name].reset(new Histogram1D(params));
else
m_histoMap[params.name].reset(new Histogram2D(params));
}
void SpectrumManager::AddHistogramSummary(const HistogramArgs& params, const std::vector<std::string>& subhistos)
{
SPEC_PROFILE_FUNCTION();
std::scoped_lock<std::mutex> guard(m_managerMutex);
m_histoMap[params.name].reset(new HistogramSummary(params, subhistos));
}
void SpectrumManager::RemoveHistogram(const std::string& name)
{
SPEC_PROFILE_FUNCTION();
std::scoped_lock<std::mutex> guard(m_managerMutex);
m_histoMap.erase(name);
}
void SpectrumManager::AddCutToHistogramDraw(const std::string& cutname, const std::string& histoname)
{
SPEC_PROFILE_FUNCTION();
std::scoped_lock<std::mutex> guard(m_managerMutex);
auto iter = m_histoMap.find(histoname);
if (iter != m_histoMap.end())
iter->second->AddCutToBeDrawn(cutname);
}
void SpectrumManager::AddCutToHistogramApplied(const std::string& cutname, const std::string& histoname)
{
SPEC_PROFILE_FUNCTION();
std::scoped_lock<std::mutex> guard(m_managerMutex);
auto iter = m_histoMap.find(histoname);
if (iter != m_histoMap.end())
iter->second->AddCutToBeApplied(cutname);
}
//Use this to fill histograms. Currently can only be filled in bulk; maybe a use case for individual fills?
void SpectrumManager::UpdateHistograms()
{
SPEC_PROFILE_FUNCTION();
std::scoped_lock<std::mutex> guard(m_managerMutex);
//Set state of all cuts for the event
CheckCuts();
bool cutFlag;
for (auto& pair : m_histoMap)
{
cutFlag = true;
for (auto& cutname : pair.second->GetParameters().cutsAppliedTo) //check the associated cuts
{
if (!IsCutValid(cutname))
{
cutFlag = false;
break;
}
}
if (!cutFlag)
continue;
switch (pair.second->GetType())
{
case SpectrumType::Histo1D:
{
auto iterX = m_paramMap.find(pair.second->GetXParam());
if (iterX != m_paramMap.end() && iterX->second->validFlag)
pair.second->FillData(iterX->second->value);
break;
}
case SpectrumType::Histo2D:
{
auto iterX = m_paramMap.find(pair.second->GetXParam());
auto iterY = m_paramMap.find(pair.second->GetYParam());
if (iterX != m_paramMap.end() && iterY != m_paramMap.end() && iterX->second->validFlag && iterY->second->validFlag)
pair.second->FillData(iterX->second->value, iterY->second->value);
break;
}
case SpectrumType::Summary:
{
const std::vector<std::string>& subhistos = std::static_pointer_cast<HistogramSummary>(pair.second)->GetSubHistograms();
for (size_t i = 0; i < subhistos.size(); i++)
{
auto iterX = m_paramMap.find(subhistos[i]);
if (iterX != m_paramMap.end() && iterX->second->validFlag)
pair.second->FillData(iterX->second->value, i + 0.5); //avoid floating point conversion issues
}
break;
}
case SpectrumType::None:
{
SPEC_WARN("Found a spectrum with None type!");
break;
}
}
}
//Reset the state of all cuts in preparation for next event
ResetCutValidities();
}
void SpectrumManager::ClearHistograms()
{
SPEC_PROFILE_FUNCTION();
std::scoped_lock<std::mutex> guard(m_managerMutex);
for (auto& pair : m_histoMap)
pair.second->ClearData();
}
void SpectrumManager::ClearHistogram(const std::string& name)
{
SPEC_PROFILE_FUNCTION();
std::scoped_lock<std::mutex> guard(m_managerMutex);
auto iter = m_histoMap.find(name);
if (iter != m_histoMap.end())
iter->second->ClearData();
}
void SpectrumManager::DrawHistogram(const std::string& name)
{
SPEC_PROFILE_FUNCTION();
std::scoped_lock<std::mutex> guard(m_managerMutex);
auto iter = m_histoMap.find(name);
if (iter != m_histoMap.end())
{
iter->second->Draw();
for (auto& cutname : iter->second->GetParameters().cutsDrawnUpon) //Draw all cuts made upon the histogram
DrawCut(cutname);
}
}
const HistogramArgs& SpectrumManager::GetHistogramParams(const std::string& name)
{
SPEC_PROFILE_FUNCTION();
std::scoped_lock<std::mutex> guard(m_managerMutex);
auto iter = m_histoMap.find(name);
if (iter != m_histoMap.end())
return iter->second->GetParameters();
else
return m_nullHistoResult;
}
//For 2D spectra, we want to allow zooming along the z-axis (color)
float* SpectrumManager::GetColorScaleRange(const std::string& name)
{
SPEC_PROFILE_FUNCTION();
std::scoped_lock<std::mutex> guard(m_managerMutex);
auto iter = m_histoMap.find(name);
if (iter != m_histoMap.end())
{
return iter->second->GetColorScaleRange();
}
return nullptr;
}
std::vector<double> SpectrumManager::GetBinData(const std::string& name)
{
std::scoped_lock<std::mutex> guard(m_managerMutex);
auto iter = m_histoMap.find(name);
if (iter != m_histoMap.end())
{
return iter->second->GetBinData();
}
return std::vector<double>();
}
std::vector<std::string> SpectrumManager::GetSubHistograms(const std::string& name)
{
SPEC_PROFILE_FUNCTION();
std::scoped_lock<std::mutex> guard(m_managerMutex);
auto iter = m_histoMap.find(name);
if (iter != m_histoMap.end() && iter->second->GetType() == SpectrumType::Summary)
{
auto gram = std::static_pointer_cast<HistogramSummary>(iter->second);
return gram->GetSubHistograms();
}
return std::vector<std::string>();
}
//Pass through for stats
StatResults SpectrumManager::AnalyzeHistogramRegion(const std::string& name, const ImPlotRect& region)
{
std::scoped_lock<std::mutex> guard(m_managerMutex);
auto iter = m_histoMap.find(name);
if (iter != m_histoMap.end())
return iter->second->AnalyzeRegion(region.X.Min, region.X.Max, region.Y.Min, region.Y.Max);
else
return StatResults();
}
//This function allows us to obtain the key histogram info in a list, avoiding excessive manager calls and thread-locks
//in something like the Editor.
std::vector<HistogramArgs> SpectrumManager::GetListOfHistograms()
{
std::scoped_lock<std::mutex> guard(m_managerMutex);
std::vector<HistogramArgs> list;
list.reserve(m_histoMap.size());
for (auto& gram : m_histoMap)
{
list.push_back(gram.second->GetParameters());
}
return list;
}
/*************Histogram Functions End*************/
/*************Graph Functions Begin*************/
void SpectrumManager::AddGraph(const GraphArgs& args)
{
std::scoped_lock<std::mutex> guard(m_managerMutex);
m_graphMap[args.name].reset(new ScalerGraph(args));
}
void SpectrumManager::RemoveGraph(const std::string& name)
{
std::scoped_lock<std::mutex> guard(m_managerMutex);
m_graphMap.erase(name);
}
void SpectrumManager::UpdateGraphs(const Timestep& step)
{
std::scoped_lock<std::mutex> guard(m_managerMutex);
uint64_t scalerVal;
for (auto& graph : m_graphMap)
{
auto& scalerIter = m_scalerMap.find(graph.second->GetScaler());
if (scalerIter != m_scalerMap.end())
{
scalerVal = *(scalerIter->second);
graph.second->UpdatePoints(step, scalerVal);
}
}
}
void SpectrumManager::ClearGraphs()
{
std::scoped_lock<std::mutex> guard(m_managerMutex);
for (auto& graph : m_graphMap)
graph.second->Clear();
}
void SpectrumManager::ClearGraph(const std::string& name)
{
std::scoped_lock<std::mutex> guard(m_managerMutex);
auto& iter = m_graphMap.find(name);
if (iter != m_graphMap.end())
iter->second->Clear();
}
void SpectrumManager::DrawGraph(const std::string& name)
{
std::scoped_lock<std::mutex> guard(m_managerMutex);
auto& iter = m_graphMap.find(name);
if (iter != m_graphMap.end())
iter->second->Draw();
}
const GraphArgs& SpectrumManager::GetGraphArgs(const std::string& name)
{
std::scoped_lock<std::mutex> guard(m_managerMutex);
auto& iter = m_graphMap.find(name);
if (iter != m_graphMap.end())
return iter->second->GetArgs();
return m_nullGraphResult;
}
std::vector<GraphArgs> SpectrumManager::GetListOfGraphs()
{
std::scoped_lock<std::mutex> guard(m_managerMutex);
std::vector<GraphArgs> list;
list.reserve(m_graphMap.size());
for (auto& graph : m_graphMap)
list.push_back(graph.second->GetArgs());
return list;
}
/*************Graph Functions End*************/
/*************Parameter Functions Begin*************/
//Bind a Parameter instance to the manager. If the Parameter doesn't exist, make a new one, otherwise attach to extant memory
void SpectrumManager::BindParameter(Parameter& param)
{
SPEC_PROFILE_FUNCTION();
std::scoped_lock<std::mutex> guard(m_managerMutex);
auto iter = m_paramMap.find(param.GetName());
if (iter == m_paramMap.end())
{
m_paramMap[param.GetName()].reset(new ParameterData());
}
param.m_pdata = m_paramMap[param.GetName()];
}
//Bind a Parameter instance to the manager. If the Parameter doesn't exist, make a new one, otherwise attach to extant memory
//Additionally, make a default 1D histogram for the parameter (histogram has same name as parameter)
void SpectrumManager::BindParameter(Parameter& param, int nbins, double minVal, double maxVal)
{
SPEC_PROFILE_FUNCTION();
std::scoped_lock<std::mutex> guard(m_managerMutex);
auto iter = m_paramMap.find(param.GetName());
if (iter == m_paramMap.end())
{
m_paramMap[param.GetName()].reset(new ParameterData());
}
param.m_pdata = m_paramMap[param.GetName()];
auto histoIter = m_histoMap.find(param.GetName());
if (histoIter == m_histoMap.end())
{
HistogramArgs histo(param.GetName(), param.GetName(), nbins, minVal, maxVal);
m_histoMap[param.GetName()].reset(new Histogram1D(histo));
}
}
//Once an analysis pass is done and histograms filled, reset all parameters
void SpectrumManager::InvalidateParameters()
{
SPEC_PROFILE_FUNCTION();
std::scoped_lock<std::mutex> guard(m_managerMutex);
for (auto& param : m_paramMap)
{
param.second->validFlag = false;
param.second->value = 0.0;
}
}
//Similar to GetListOfHistograms, see that documentation
std::vector<std::string> SpectrumManager::GetListOfParameters()
{
std::scoped_lock<std::mutex> guard(m_managerMutex);
std::vector<std::string> list;
list.reserve(m_paramMap.size());
for (auto iter : m_paramMap)
{
list.push_back(iter.first);
}
return list;
}
/*************Parameter Functions End*************/
/*************Variable Functions Begin*************/
void SpectrumManager::BindVariable(Variable& var)
{
SPEC_PROFILE_FUNCTION();
std::scoped_lock<std::mutex> guard(m_managerMutex);
auto iter = m_varMap.find(var.GetName());
if (iter == m_varMap.end())
{
m_varMap[var.GetName()].reset(new std::atomic<double>(0.0));
}
var.m_pdata = m_varMap[var.GetName()];
}
std::vector<std::string> SpectrumManager::GetListOfVariables()
{
std::scoped_lock<std::mutex> guard(m_managerMutex);
std::vector<std::string> list;
list.reserve(m_varMap.size());
for (auto iter : m_varMap)
list.push_back(iter.first);
return list;
}
/*************Variable Functions End*************/
void SpectrumManager::BindScaler(Scaler& scaler)
{
SPEC_PROFILE_FUNCTION();
std::scoped_lock<std::mutex> guard(m_managerMutex);
auto iter = m_scalerMap.find(scaler.GetName());
if (iter == m_scalerMap.end())
{
m_scalerMap[scaler.GetName()].reset(new std::atomic<uint64_t>(0));
}
scaler.m_pdata = m_scalerMap[scaler.GetName()];
}
void SpectrumManager::ResetScalers()
{
SPEC_PROFILE_FUNCTION();
std::scoped_lock<std::mutex> guard(m_managerMutex);
for (auto& scaler : m_scalerMap)
{
*(scaler.second) = 0;
}
}
std::vector<std::string> SpectrumManager::GetListOfScalers()
{
std::scoped_lock<std::mutex> guard(m_managerMutex);
std::vector<std::string> list;
list.reserve(m_scalerMap.size());
for (auto& iter : m_scalerMap)
list.push_back(iter.first);
return list;
}
/*************Cut Functions Begin*************/
void SpectrumManager::AddCut(const CutArgs& params, double min, double max)
{
std::scoped_lock<std::mutex> guard(m_managerMutex);
m_cutMap[params.name].reset(new Cut1D(params, min, max));
}
void SpectrumManager::AddCut(const CutArgs& params, const std::vector<double>& xpoints, const std::vector<double>& ypoints)
{
std::scoped_lock<std::mutex> guard(m_managerMutex);
m_cutMap[params.name].reset(new Cut2D(params, xpoints, ypoints));
}
void SpectrumManager::AddCut(const CutArgs& params, const std::vector<std::string>& subhistos, double min, double max)
{
std::scoped_lock<std::mutex> guard(m_managerMutex);
m_cutMap[params.name].reset(new CutSummary(params, subhistos, min, max));
}
void SpectrumManager::RemoveCut(const std::string& name)
{
SPEC_PROFILE_FUNCTION();
std::scoped_lock<std::mutex> guard(m_managerMutex);
m_cutMap.erase(name);
RemoveCutFromHistograms(name); //Once a cut is gone, remove all references to it.
}
std::vector<double> SpectrumManager::GetCutXPoints(const std::string& name)
{
std::scoped_lock<std::mutex> guard(m_managerMutex);
std::vector<double> null_result;
auto iter = m_cutMap.find(name);
if (iter != m_cutMap.end())
return iter->second->GetXValues();
return null_result;
}
std::vector<double> SpectrumManager::GetCutYPoints(const std::string& name)
{
std::scoped_lock<std::mutex> guard(m_managerMutex);
std::vector<double> null_result;
auto iter = m_cutMap.find(name);
if (iter != m_cutMap.end())
return iter->second->GetYValues();
return null_result;
}
std::vector<std::string> SpectrumManager::GetCutSubHistograms(const std::string& cutname)
{
std::scoped_lock<std::mutex> guard(m_managerMutex);
auto iter = m_cutMap.find(cutname);
if (iter != m_cutMap.end() && iter->second->GetType() == CutType::CutSummaryAny || iter->second->GetType() == CutType::CutSummaryAll)
{
auto cut = std::static_pointer_cast<CutSummary>(iter->second);
return cut->GetSubHistograms();
}
return std::vector<std::string>();
}
//Similar to GetListOfHistograms, see that documentation
std::vector<CutArgs> SpectrumManager::GetListOfCuts()
{
std::scoped_lock<std::mutex> guard(m_managerMutex);
std::vector<CutArgs> list;
list.reserve(m_cutMap.size());
for (auto& entry : m_cutMap)
list.push_back(entry.second->GetCutArgs());
return list;
}
/*************Cut Functions End*************/
/*
Private Functions
Can only be called from within the SpectrumManager, therefore the lock should already have been aquired by
whatever parent function calls them. No explicit synchronization.
*/
//Can only be called by RemoveCut currently. May be a use case where this should be promoted to public to on the fly mod a gram.
void SpectrumManager::RemoveCutFromHistograms(const std::string& cutname)
{
SPEC_PROFILE_FUNCTION();
for (auto& gram : m_histoMap)
{
auto& params = gram.second->GetParameters();
for (size_t i = 0; i < params.cutsDrawnUpon.size(); ++i)
{
if (params.cutsDrawnUpon[i] == cutname)
{
params.cutsDrawnUpon.erase(params.cutsDrawnUpon.begin() + i);
}
}
for (size_t i = 0; i < params.cutsAppliedTo.size(); ++i)
{
if (params.cutsAppliedTo[i] == cutname)
{
params.cutsAppliedTo.erase(params.cutsAppliedTo.begin() + i);
}
}
}
}
//Obv. only need to draw a cut if its parent histogram is drawn.
void SpectrumManager::DrawCut(const std::string& name)
{
SPEC_PROFILE_FUNCTION();
auto iter = m_cutMap.find(name);
if (iter != m_cutMap.end())
iter->second->Draw();
}
//Set the state of the cuts for the current event. Called by the
void SpectrumManager::CheckCuts()
{
SPEC_PROFILE_FUNCTION();
for (auto& iter : m_cutMap)
{
const std::string& xpar = iter.second->GetXParameter();
const std::string& ypar = iter.second->GetYParameter();
switch (iter.second->GetType())
{
case CutType::Cut1D:
{
auto iterX = m_paramMap.find(xpar);
if (iterX != m_paramMap.end() && iterX->second->validFlag)
iter.second->IsInside(iterX->second->value);
break;
}
case CutType::Cut2D:
{
auto iterX = m_paramMap.find(xpar);
auto iterY = m_paramMap.find(ypar);
if (iterX != m_paramMap.end() && iterX->second->validFlag && iterY != m_paramMap.end() && iterY->second->validFlag)
iter.second->IsInside(iterX->second->value, iterY->second->value);
break;
}
case CutType::CutSummaryAll:
{
std::vector<std::string> paramlist = std::static_pointer_cast<CutSummary>(iter.second)->GetSubHistograms();
for (auto& param : paramlist)
{
auto iterX = m_paramMap.find(param);
if (iterX != m_paramMap.end() && iterX->second->validFlag)
{
iter.second->IsInside(iterX->second->value);
if (!iter.second->IsValid())
break;
}
}
break;
}
case CutType::CutSummaryAny:
{
std::vector<std::string> paramlist = std::static_pointer_cast<CutSummary>(iter.second)->GetSubHistograms();
for (auto& param : paramlist)
{
auto iterX = m_paramMap.find(param);
if (iterX != m_paramMap.end() && iterX->second->validFlag)
{
iter.second->IsInside(iterX->second->value);
if (iter.second->IsValid())
break;
}
}
break;
}
case CutType::None:
{
SPEC_WARN("Found a cut with None type!");
break;
}
}
}
}
bool SpectrumManager::IsCutValid(const std::string& name)
{
SPEC_PROFILE_FUNCTION();
auto iter = m_cutMap.find(name);
if (iter != m_cutMap.end())
return iter->second->IsValid();
return false;
}
void SpectrumManager::ResetCutValidities()
{
for (auto& iter : m_cutMap)
{
iter.second->ResetValidity();
}
}
}

View File

@ -0,0 +1,126 @@
/*
SpectrumManager.h
SpectrumManager is the global resource management class. Controls everything related to spectra (histograms, cuts, parameters). Since
the manager must traverse threads, explicit synchronoization is handled through a mutex. To this end, excessive calls to the manager should be
avoided if possible, as this will increase the lock deadtime in the application, which is especially bad for online data sources.
Note that SpectrumManager is a singleton. There should only ever be one SpectrumManager with a given application.
GWM -- Feb 2022
*/
#ifndef SPECTRUM_MANAGER_H
#define SPECTRUM_MANAGER_H
#include "Histogram.h"
#include "Cut.h"
#include "Parameter.h"
#include "Graph.h"
#include "Timestep.h"
#include <thread>
struct ImPlotRect;
namespace Specter {
class SpectrumManager
{
public:
SpectrumManager();
~SpectrumManager();
inline static SpectrumManager& GetInstance() { return *s_instance; }
//To clear all managed spectra. Note that Parameters are left untouched.
inline void RemoveAllSpectra()
{
std::scoped_lock<std::mutex> guard(m_managerMutex);
m_histoMap.clear();
m_cutMap.clear();
}
/*Histogram Functions*/
void AddHistogram(const HistogramArgs& params);
void AddHistogramSummary(const HistogramArgs& params, const std::vector<std::string>& subhistos);
void RemoveHistogram(const std::string& name);
void AddCutToHistogramDraw(const std::string& cutname, const std::string& histoname);
void AddCutToHistogramApplied(const std::string& cutname, const std::string& histoname);
void UpdateHistograms();
void ClearHistograms();
void ClearHistogram(const std::string& name);
void DrawHistogram(const std::string& name);
const HistogramArgs& GetHistogramParams(const std::string& name);
float* GetColorScaleRange(const std::string& name);
std::vector<double> GetBinData(const std::string& name);
std::vector<std::string> GetSubHistograms(const std::string& name);
StatResults AnalyzeHistogramRegion(const std::string& name, const ImPlotRect& region);
std::vector<HistogramArgs> GetListOfHistograms();
/********************/
/*ScalerGraph Functions*/
void AddGraph(const GraphArgs& args);
void RemoveGraph(const std::string& name);
void UpdateGraphs(const Timestep& step);
void ClearGraphs();
void ClearGraph(const std::string& name);
void DrawGraph(const std::string& name);
const GraphArgs& GetGraphArgs(const std::string& name);
std::vector<GraphArgs> GetListOfGraphs();
/********************/
/*Parameter Functions*/
void BindParameter(Parameter& param);
void BindParameter(Parameter& param, int nbins, double maxVal, double minVal);
void InvalidateParameters();
std::vector<std::string> GetListOfParameters();
/*********************/
/*Variable Functions*/
void BindVariable(Variable& var);
std::vector<std::string> GetListOfVariables();
/********************/
/*Scaler Functions*/
void BindScaler(Scaler& scaler);
void ResetScalers();
std::vector<std::string> GetListOfScalers();
/******************/
/*Cut Functions*/
void AddCut(const CutArgs& params, double min, double max);
void AddCut(const CutArgs& params, const std::vector<double>& xpoints, const std::vector<double>& ypoints);
void AddCut(const CutArgs& params, const std::vector<std::string>& subhistos, double min, double max);
void RemoveCut(const std::string& name);
std::vector<double> GetCutXPoints(const std::string& name);
std::vector<double> GetCutYPoints(const std::string& name);
std::vector<std::string> GetCutSubHistograms(const std::string& cutname);
std::vector<CutArgs> GetListOfCuts();
/**************/
private:
//Only used from within manager
void RemoveCutFromHistograms(const std::string& cutname);
void DrawCut(const std::string& name);
void CheckCuts();
bool IsCutValid(const std::string& name);
void ResetCutValidities();
static SpectrumManager* s_instance;
//Actual data
std::unordered_map<std::string, std::shared_ptr<Histogram>> m_histoMap;
std::unordered_map<std::string, std::shared_ptr<Cut>> m_cutMap;
std::unordered_map<std::string, std::shared_ptr<ParameterData>> m_paramMap;
std::unordered_map<std::string, std::shared_ptr<std::atomic<double>>> m_varMap;
std::unordered_map<std::string, std::shared_ptr<std::atomic<uint64_t>>> m_scalerMap;
std::unordered_map<std::string, std::shared_ptr<ScalerGraph>> m_graphMap;
HistogramArgs m_nullHistoResult; //For handling bad query
GraphArgs m_nullGraphResult; //For handling bad query
std::mutex m_managerMutex; //synchronization
};
}
#endif

View File

@ -0,0 +1,455 @@
/*
SpectrumSerializer.h
SpectrumSerializer class providing method to write/read spectra (histograms and cuts) to/from a .nav file. These are formated text files.
Note that by virtue of the way that cuts work, they are written first.
A couple of notes:
- Writing/reading data is a pretty expensive concept from a thread-locking perspective. Not recommended for use during active analysis.
- Deserializing (reading) by default removes all current histograms and cuts. This avoids any issues with collisions, but may not always be desireable.
- There is no intrinsic checking of whether a parameter for a cut/histogram to be loaded exists within the current project. If you load something and the histograms
don't fill, it is most likely due to the parameter not being defined in the current project.
GWM -- Feb 2022
*/
#include "SpectrumSerializer.h"
#include "SpectrumManager.h"
#include <fstream>
namespace Specter {
SpectrumSerializer::SpectrumSerializer(const std::string& filepath) :
m_filename(filepath)
{
}
SpectrumSerializer::~SpectrumSerializer() {}
void SpectrumSerializer::SerializeData(const std::vector<HistogramArgs>& histoList, const std::vector<CutArgs>& cutList)
{
SpectrumManager& manager = SpectrumManager::GetInstance();
std::ofstream output(m_filename);
if (!output.is_open())
{
SPEC_ERROR("Unable to open {0} to write data.", m_filename);
return;
}
output << "begin_cuts" << std::endl;
for (auto& cut : cutList)
{
if (cut.type == CutType::Cut1D)
{
std::vector<double> xpoints = manager.GetCutXPoints(cut.name);
output << "\tbegin_cut1D" << std::endl;
output << "\t\tname: " << cut.name << std::endl;
output << "\t\txparam: " << cut.x_par << std::endl;
output << "\t\tminValue: " << xpoints[0] << std::endl;
output << "\t\tmaxValue: " << xpoints[1] << std::endl;
output << "\tend_cut1D" << std::endl;
}
else if (cut.type == CutType::Cut2D)
{
std::vector<double> xpoints = manager.GetCutXPoints(cut.name);
std::vector<double> ypoints = manager.GetCutYPoints(cut.name);
output << "\tbegin_cut2D" << std::endl;
output << "\t\tname: " << cut.name << std::endl;
output << "\t\txparam: " << cut.x_par << std::endl;
output << "\t\typaram: " << cut.y_par << std::endl;
output << "\t\tbegin_xvalues" << std::endl;
for (const auto& value : xpoints)
{
output << "\t\t\t" << value << std::endl;
}
output << "\t\tend_xvalues" << std::endl;
output << "\t\tbegin_yvalues" << std::endl;
for (const auto& value : ypoints)
{
output << "\t\t\t" << value << std::endl;
}
output << "\t\tend_yvalues" << std::endl;
output << "\tend_cut2D" << std::endl;
}
else if (cut.type == CutType::CutSummaryAll)
{
std::vector<std::string> subhistos = manager.GetCutSubHistograms(cut.name);
std::vector<double> xpoints = manager.GetCutXPoints(cut.name);
output << "\tbegin_cutSummaryAll" << std::endl;
output << "\t\tname: " << cut.name << std::endl;
output << "\t\tminValue: " << xpoints[0] << std::endl;
output << "\t\tmaxValue: " << xpoints[1] << std::endl;
output << "\t\tbegin_parameters" << std::endl;
for (auto& par : subhistos)
{
output << "\t\t\t" << par << std::endl;
}
output << "\t\tend_parameters" << std::endl;
output << "\tend_cutSummaryAll" << std::endl;
}
else if (cut.type == CutType::CutSummaryAny)
{
std::vector<std::string> subhistos = manager.GetCutSubHistograms(cut.name);
std::vector<double> xpoints = manager.GetCutXPoints(cut.name);
output << "\tbegin_cutSummaryAny" << std::endl;
output << "\t\tname: " << cut.name << std::endl;
output << "\t\tminValue: " << xpoints[0] << std::endl;
output << "\t\tmaxValue: " << xpoints[1] << std::endl;
output << "\t\tbegin_parameters" << std::endl;
for (auto& par : subhistos)
{
output << "\t\t\t" << par << std::endl;
}
output << "\t\tend_parameters" << std::endl;
output << "\tend_cutSummaryAny" << std::endl;
}
}
output << "end_cuts" << std::endl;
output << "begin_histograms" << std::endl;
for (auto& params : histoList)
{
if (params.type == SpectrumType::Histo1D)
{
output << "\tbegin_histogram1D" << std::endl;
output << "\t\tname: " << params.name << std::endl;
output << "\t\txparam: " << params.x_par << std::endl;
output << "\t\tNxbins: " << params.nbins_x << std::endl;
output << "\t\tXMin: " << params.min_x << std::endl;
output << "\t\tXMax: " << params.max_x << std::endl;
output << "\t\tbegin_cutsdrawn" << std::endl;
for (const auto& name : params.cutsDrawnUpon)
{
output << "\t\t\t" << name << std::endl;
}
output << "\t\tend_cutsdrawn" << std::endl;
output << "\t\tbegin_cutsapplied" << std::endl;
for (const auto& name : params.cutsAppliedTo)
{
output << "\t\t\t" << name << std::endl;
}
output << "\t\tend_cutsapplied" << std::endl;
output << "\tend_histogram1D" << std::endl;
}
else if (params.type == SpectrumType::Histo2D)
{
output << "\tbegin_histogram2D" << std::endl;
output << "\t\tname: " << params.name << std::endl;
output << "\t\txparam: " << params.x_par << std::endl;
output << "\t\typaram: " << params.y_par << std::endl;
output << "\t\tNxbins: " << params.nbins_x << std::endl;
output << "\t\tXMin: " << params.min_x << std::endl;
output << "\t\tXMax: " << params.max_x << std::endl;
output << "\t\tNybins: " << params.nbins_y << std::endl;
output << "\t\tYMin: " << params.min_y << std::endl;
output << "\t\tYMax: " << params.max_y << std::endl;
output << "\t\tbegin_cutsdrawn" << std::endl;
for (const auto& name : params.cutsDrawnUpon)
{
output << "\t\t\t" << name << std::endl;
}
output << "\t\tend_cutsdrawn" << std::endl;
output << "\t\tbegin_cutsapplied" << std::endl;
for (const auto& name : params.cutsAppliedTo)
{
output << "\t\t\t" << name << std::endl;
}
output << "\t\tend_cutsapplied" << std::endl;
output << "\tend_histogram2D" << std::endl;
}
else if (params.type == SpectrumType::Summary)
{
output << "\tbegin_histogramSummary" << std::endl;
output << "\t\tname: " << params.name << std::endl;
output << "\t\tNxbins: " << params.nbins_x << std::endl;
output << "\t\tXMin: " << params.min_x << std::endl;
output << "\t\tXMax: " << params.max_x << std::endl;
output << "\t\tbegin_subhistos" << std::endl;
std::vector<std::string> subhistos = manager.GetSubHistograms(params.name);
for (auto& name : subhistos)
{
output << "\t\t\t" << name << std::endl;
}
output << "\t\tend_subhistos" << std::endl;
output << "\t\tbegin_cutsdrawn" << std::endl;
for (const auto& name : params.cutsDrawnUpon)
{
output << "\t\t\t" << name << std::endl;
}
output << "\t\tend_cutsdrawn" << std::endl;
output << "\t\tbegin_cutsapplied" << std::endl;
for (const auto& name : params.cutsAppliedTo)
{
output << "\t\t\t" << name << std::endl;
}
output << "\t\tend_cutsapplied" << std::endl;
output << "\tend_histogram1D" << std::endl;
}
}
output << "end_histograms" << std::endl;
SPEC_INFO("Successfully saved data to {0}", m_filename);
output.close();
}
void SpectrumSerializer::DeserializeData()
{
SpectrumManager& manager = SpectrumManager::GetInstance();
manager.RemoveAllSpectra(); //When loading in, we remove all extant data, to avoid any potential collisions.
std::ifstream input(m_filename);
if (!input.is_open())
{
SPEC_ERROR("Unable to open {0} to read data!", m_filename);
return;
}
std::string check;
double value_doub;
CutArgs cut_data, reset_cut;
std::vector<double> cut_xdata;
std::vector<double> cut_ydata;
std::vector<std::string> subhistos;
HistogramArgs hist_data, reset_hist;
while (input >> check)
{
if (check == "begin_cuts")
{
while (input >> check)
{
cut_data = reset_cut;
cut_xdata.clear();
cut_ydata.clear();
subhistos.clear();
if (check == "begin_cut1D")
{
cut_data.type = CutType::Cut1D;
input >> check >> cut_data.name;
input >> check >> cut_data.x_par;
input >> check >> value_doub;
cut_xdata.push_back(value_doub);
input >> check >> value_doub;
cut_xdata.push_back(value_doub);
input >> check;
manager.AddCut(cut_data, cut_xdata[0], cut_xdata[1]);
}
else if (check == "begin_cut2D")
{
cut_data.type = CutType::Cut2D;
input >> check >> cut_data.name;
input >> check >> cut_data.x_par;
input >> check >> cut_data.y_par;
while (input >> check)
{
if (check == "begin_xvalues")
continue;
else if (check == "end_xvalues")
break;
else
cut_xdata.push_back(std::stod(check));
}
while (input >> check)
{
if (check == "begin_yvalues")
continue;
else if (check == "end_yvalues")
break;
else
cut_ydata.push_back(std::stod(check));
}
input >> check;
manager.AddCut(cut_data, cut_xdata, cut_ydata);
}
else if (check == "begin_cutSummaryAll")
{
cut_data.type = CutType::CutSummaryAll;
input >> check >> cut_data.name;
input >> check >> value_doub;
cut_xdata.push_back(value_doub);
input >> check >> value_doub;
cut_xdata.push_back(value_doub);
while (input >> check)
{
if (check == "begin_parameters")
continue;
else if (check == "end_parameters")
break;
else
subhistos.push_back(check);
}
input >> check;
manager.AddCut(cut_data, subhistos, cut_xdata[0], cut_xdata[1]);
}
else if (check == "begin_cutSummaryAny")
{
cut_data.type = CutType::CutSummaryAny;
input >> check >> cut_data.name;
input >> check >> value_doub;
cut_xdata.push_back(value_doub);
input >> check >> value_doub;
cut_xdata.push_back(value_doub);
while (input >> check)
{
if (check == "begin_parameters")
continue;
else if (check == "end_parameters")
break;
else
subhistos.push_back(check);
}
input >> check;
manager.AddCut(cut_data, subhistos, cut_xdata[0], cut_xdata[1]);
}
else if (check == "end_cuts")
break;
else
{
SPEC_ERROR("Deserialization error; unexpected check condition while parsing cut data! Current value: {0}", check);
input.close();
return;
}
}
}
else if (check == "begin_histograms")
{
while (input >> check)
{
hist_data = reset_hist;
if (check == "begin_histogram1D")
{
hist_data.type = SpectrumType::Histo1D;
input >> check >> hist_data.name;
input >> check >> hist_data.x_par;
input >> check >> hist_data.nbins_x;
input >> check >> hist_data.min_x;
input >> check >> hist_data.max_x;
while (input >> check)
{
if (check == "begin_cutsdrawn")
continue;
else if (check == "end_cutsdrawn")
break;
else
{
hist_data.cutsDrawnUpon.push_back(check);
}
}
while (input >> check)
{
if (check == "begin_cutsapplied")
continue;
else if (check == "end_cutsapplied")
break;
else
{
hist_data.cutsAppliedTo.push_back(check);
}
}
input >> check;
manager.AddHistogram(hist_data);
}
else if (check == "begin_histogram2D")
{
hist_data.type = SpectrumType::Histo2D;
input >> check >> hist_data.name;
input >> check >> hist_data.x_par;
input >> check >> hist_data.y_par;
input >> check >> hist_data.nbins_x;
input >> check >> hist_data.min_x;
input >> check >> hist_data.max_x;
input >> check >> hist_data.nbins_y;
input >> check >> hist_data.min_y;
input >> check >> hist_data.max_y;
while (input >> check)
{
if (check == "begin_cutsdrawn")
continue;
else if (check == "end_cutsdrawn")
break;
else
{
hist_data.cutsDrawnUpon.push_back(check);
}
}
while (input >> check)
{
if (check == "begin_cutsapplied")
continue;
else if (check == "end_cutsapplied")
break;
else
{
hist_data.cutsAppliedTo.push_back(check);
}
}
input >> check;
manager.AddHistogram(hist_data);
}
else if (check == "begin_histogramSummary")
{
subhistos.clear();
hist_data.type = SpectrumType::Summary;
input >> check >> hist_data.name;
input >> check >> hist_data.nbins_x;
input >> check >> hist_data.min_x;
input >> check >> hist_data.max_x;
while (input >> check)
{
if (check == "begin_subhistos")
continue;
else if (check == "end_subhistos")
break;
else
{
subhistos.push_back(check);
}
}
while (input >> check)
{
if (check == "begin_cutsdrawn")
continue;
else if (check == "end_cutsdrawn")
break;
else
{
hist_data.cutsDrawnUpon.push_back(check);
}
}
while (input >> check)
{
if (check == "begin_cutsapplied")
continue;
else if (check == "end_cutsapplied")
break;
else
{
hist_data.cutsAppliedTo.push_back(check);
}
}
input >> check;
manager.AddHistogramSummary(hist_data, subhistos);
}
else if (check == "end_histograms")
break;
else
{
SPEC_ERROR("Deserialization error; unexpected check condition while parsing histogram data! Current value: {0}", check);
input.close();
return;
}
}
}
else
{
SPEC_ERROR("Deserialization error; unexpected check condition at top level! Current value: {0}", check);
input.close();
return;
}
}
SPEC_INFO("Successfully loaded data from {0}", m_filename);
input.close();
}
}

View File

@ -0,0 +1,39 @@
/*
SpectrumSerializer.h
SpectrumSerializer class providing method to write/read spectra (histograms and cuts) to/from a .nav file. These are formated text files.
Note that by virtue of the way that cuts work, they are written first.
A couple of notes:
- Writing/reading data is a pretty expensive concept from a thread-locking perspective. Not recommended for use during active analysis.
- Deserializing (reading) by default removes all current histograms and cuts. This avoids any issues with collisions, but may not always be desireable.
- There is no intrinsic checking of whether a parameter for a cut/histogram to be loaded exists within our framework. If you load something and the histograms
don't fill, it is most likely due to the parameter not being defined in the current project.
GWM -- Feb 2022
*/
#ifndef SPECTRUM_SERIALIZER_H
#define SPECTRUM_SERIALIZER_H
#include "Histogram.h"
#include "Cut.h"
namespace Specter {
class SpectrumSerializer
{
public:
SpectrumSerializer(const std::string& filepath);
~SpectrumSerializer();
void SerializeData(const std::vector<HistogramArgs>& histoList, const std::vector<CutArgs>& cutList);
void DeserializeData();
inline const std::string& GetFilename() { return m_filename; }
private:
std::string m_filename;
};
}
#endif

View File

@ -0,0 +1,25 @@
#ifndef TIMESTEP_H
#define TIMESTEP_H
namespace Specter {
class Timestep
{
public:
Timestep(float time=0.0f) :
m_time(time)
{
}
void SetTime(float time) { m_time = time; }
operator float() const { return m_time; }
float GetElapsedSeconds() const { return m_time; }
float GetElapsedMilliseconds() const { return m_time * 1000.0f; }
private:
float m_time;
};
}
#endif

View File

@ -0,0 +1,53 @@
/*
Window.h
Abstract class for a window in Specter. Based entirely upon the work of @TheCherno, see his Hazel repository. This exists primarily to allow for the option
to extend Specter to other rendering backends, most likely Metal, or potentially DX12. See code in Platform for specific implementations (currently only for OpenGL).
GWM -- Feb 2022
*/
#ifndef WINDOW_H
#define WINDOW_H
#include "SpecCore.h"
#include "Specter/Events/Event.h"
namespace Specter {
class WindowProperties
{
public:
unsigned int width, height;
std::string name;
WindowProperties(const std::string& title="Specter", unsigned int w=1280, unsigned int h=720) :
width(w), height(h), name(title)
{
}
};
class Window
{
public:
using EventCallbackFunc = std::function<void(Event&)>;
virtual ~Window() {}
virtual void OnUpdate() = 0;
virtual unsigned int GetWidth() const = 0;
virtual unsigned int GetHeight() const = 0;
virtual std::string GetName() const = 0;
virtual void SetEventCallback(const EventCallbackFunc& function) = 0;
virtual void SetVSync(bool enabled) = 0;
virtual bool IsVSync() const = 0;
virtual void* GetNativeWindow() const =0;
static Window* Create(const WindowProperties& props = WindowProperties());
};
}
#endif

View File

@ -0,0 +1,416 @@
/*
EditorLayer.cpp
Application layer encapsulating the editor for Specter. Written using the Dear ImGui library. Setup based off of @TheCherno's Hazel game engine.
EditorLayer essentially controls the state for UI related actions.
GWM -- Feb 2022
*/
#include "EditorLayer.h"
#include "imgui.h"
#include "misc/cpp/imgui_stdlib.h"
#include "implot.h"
#include "FileDialog.h"
#include "Specter/Core/Application.h"
#include "Specter/Core/SpectrumSerializer.h"
#include "Specter/Core/SpectrumManager.h"
#include "IconsFontAwesome5.h"
namespace Specter {
bool SortByString(const std::string& p1, const std::string& p2)
{
return p1 < p2;
}
EditorLayer::EditorLayer() :
Layer("EditorLayer"), m_removeHistogram(false), m_removeCut(false), m_exportHistogram(false)
{
}
EditorLayer::~EditorLayer() {}
void EditorLayer::OnAttach()
{
}
void EditorLayer::OnDetach()
{
}
void EditorLayer::OnUpdate(Timestep& step)
{
}
void EditorLayer::OnEvent(Event& e)
{
}
//These updates are used whenever a new object is added to the manager.
void EditorLayer::UpdateHistogramList()
{
m_histoList = SpectrumManager::GetInstance().GetListOfHistograms();
std::sort(m_histoList.begin(), m_histoList.end(), SortByName<HistogramArgs>);
}
void EditorLayer::UpdateCutList()
{
m_cutList = SpectrumManager::GetInstance().GetListOfCuts();
std::sort(m_cutList.begin(), m_cutList.end(), SortByName<CutArgs>);
}
void EditorLayer::UpdateParameterList()
{
m_paramList = SpectrumManager::GetInstance().GetListOfParameters();
std::sort(m_paramList.begin(), m_paramList.end(), SortByString);
}
//The main function
void EditorLayer::OnImGuiRender()
{
SPEC_PROFILE_FUNCTION();
static bool startFlag = true; //first render retrieve base
if(startFlag)
{
UpdateParameterList();
UpdateHistogramList();
UpdateCutList();
startFlag = false;
}
// We are using the ImGuiWindowFlags_NoDocking flag to make the parent window not dockable into,
// because it would be confusing to have two docking targets within each others.
if (opt_fullscreen)
{
ImGuiViewport* viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(viewport->Pos);
ImGui::SetNextWindowSize(viewport->Size);
ImGui::SetNextWindowViewport(viewport->ID);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove;
window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus;
}
// When using ImGuiDockNodeFlags_PassthruCentralNode, DockSpace() will render our background and handle the pass-thru hole, so we ask Begin() to not render a background.
if (dockspace_flags & ImGuiDockNodeFlags_PassthruCentralNode)
window_flags |= ImGuiWindowFlags_NoBackground;
// Important: note that we proceed even if Begin() returns false (aka window is collapsed).
// This is because we want to keep our DockSpace() active. If a DockSpace() is inactive,
// all active windows docked into it will lose their parent and become undocked.
// We cannot preserve the docking relationship between an active window and an inactive docking, otherwise
// any change of dockspace/settings would lead to windows being stuck in limbo and never being visible.
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
ImGui::Begin("MyTestSpace", &dockspaceOpen, window_flags);
ImGui::PopStyleVar();
if (opt_fullscreen)
ImGui::PopStyleVar(2);
// DockSpace
ImGuiIO& io = ImGui::GetIO();
ImGuiStyle& style = ImGui::GetStyle();
float minWinSizeX = style.WindowMinSize.x;
style.WindowMinSize.x = 370.0f;
if (io.ConfigFlags & ImGuiConfigFlags_DockingEnable)
{
ImGuiID dockspace_id = ImGui::GetID("MyDockSpace");
ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags);
}
style.WindowMinSize.x = minWinSizeX;
if(ImGui::BeginMenuBar())
{
if(ImGui::BeginMenu("File"))
{
if(ImGui::MenuItem(ICON_FA_FOLDER_OPEN "\tOpen"))
{
m_fileDialog.OpenDialog(FileDialog::Type::OpenFile);
}
if(ImGui::MenuItem(ICON_FA_SAVE "\tSave"))
{
m_fileDialog.OpenDialog(FileDialog::Type::SaveFile);
}
if (ImGui::MenuItem(ICON_FA_TIMES_CIRCLE "\tExit"))
{
Application::Get().Close();
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Data Source"))
{
if (ImGui::MenuItem(ICON_FA_LINK "\tAttach Source"))
{
m_sourceDialog.OpenSourceDialog();
}
if (ImGui::MenuItem(ICON_FA_UNLINK "\tDetach Source"))
{
PhysicsStopEvent event;
m_callbackFunc(event);
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Add"))
{
if (ImGui::MenuItem(ICON_FA_CHART_BAR "\tSpectrum"))
{
m_spectrumDialog.SetSpectrumDialog();
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Remove"))
{
if (ImGui::MenuItem(ICON_FA_CHART_BAR "\tSpectrum"))
{
m_removeHistogram = true;
}
if (ImGui::MenuItem(ICON_FA_CUT "\tCut"))
{
m_removeCut = true;
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Export"))
{
if (ImGui::MenuItem(ICON_FA_SAVE "\tAs .csv"))
{
m_exportHistogram = true;
}
ImGui::EndMenu();
}
ImGui::EndMenuBar();
}
//Render all of our sub-windows, dialogs, panels, etc
auto fd_result = m_fileDialog.RenderFileDialog(".spec");
if (!fd_result.first.empty())
{
switch (fd_result.second)
{
case FileDialog::Type::OpenFile:
{
SpectrumSerializer serializer(fd_result.first);
serializer.DeserializeData();
UpdateHistogramList();
UpdateCutList();
break;
}
case FileDialog::Type::SaveFile:
{
SPEC_INFO("Found a Save File! {0}", fd_result.first);
SpectrumSerializer serializer(fd_result.first);
serializer.SerializeData(m_histoList, m_cutList);
break;
}
}
}
if(m_spectrumDialog.ImGuiRenderSpectrumDialog(m_histoList, m_cutList, m_paramList))
UpdateHistogramList();
m_sourceDialog.ImGuiRenderSourceDialog();
RemoveHistogramDialog();
RemoveCutDialog();
ExportHistogramDialog();
if(m_spectrumPanel.OnImGuiRender(m_histoList, m_cutList, m_paramList))
{
UpdateCutList();
UpdateHistogramList();
}
if (ImGui::Begin(ICON_FA_CHART_BAR " Spectra"))
{
for (auto& params : m_histoList)
{
if (ImGui::TreeNode(params.name.c_str()))
{
ImGui::BulletText("%s", ("X Parameter: "+params.x_par).c_str());
ImGui::BulletText("X Bins: %d X Min: %f X Max: %f", params.nbins_x, params.min_x, params.max_x);
if (params.y_par != "None")
{
ImGui::BulletText("%s", ("Y Parameter: "+params.y_par).c_str());
ImGui::BulletText("Y Bins: %d Y Min: %f Y Max: %f", params.nbins_y, params.min_y, params.max_y);
}
if(params.cutsDrawnUpon.size() != 0 && ImGui::TreeNode("Cuts Drawn"))
{
for(auto& cut : params.cutsDrawnUpon)
ImGui::BulletText("%s", cut.c_str());
ImGui::TreePop();
}
if(params.cutsAppliedTo.size() != 0 && ImGui::TreeNode("Cuts Applied"))
{
for(auto& cut : params.cutsAppliedTo)
ImGui::BulletText("%s", cut.c_str());
ImGui::TreePop();
}
ImGui::TreePop();
}
}
}
ImGui::End();
if(ImGui::Begin(ICON_FA_CUT " Cuts"))
{
for(auto& params : m_cutList)
{
if(ImGui::TreeNode(params.name.c_str()))
{
ImGui::BulletText("%s", ("X Parameter: "+params.x_par).c_str());
if(params.y_par != "None")
ImGui::BulletText("%s", ("Y Parameter: "+params.y_par).c_str());
ImGui::TreePop();
}
}
}
ImGui::End();
ImGui::End();
}
//Simple dialogs, no need for separate class
void EditorLayer::RemoveHistogramDialog()
{
SPEC_PROFILE_FUNCTION();
static std::string selectedGram = "";
if (m_removeHistogram)
{
selectedGram = "";
m_removeHistogram = false;
ImGui::OpenPopup("Remove Histogram");
}
if (ImGui::BeginPopupModal("Remove Histogram"))
{
if (ImGui::BeginCombo("Histogram", selectedGram.c_str()))
{
for (auto& gram : m_histoList)
{
if (ImGui::Selectable(gram.name.c_str(), gram.name == selectedGram, ImGuiSelectableFlags_DontClosePopups))
selectedGram = gram.name;
}
ImGui::EndCombo();
}
if (ImGui::Button("Ok"))
{
SpectrumManager::GetInstance().RemoveHistogram(selectedGram);
UpdateHistogramList();
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Cancel"))
{
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
void EditorLayer::RemoveCutDialog()
{
SPEC_PROFILE_FUNCTION();
static std::string selectedCut = "";
if (m_removeCut)
{
selectedCut = "";
m_removeCut = false;
ImGui::OpenPopup("Remove Cut");
}
if (ImGui::BeginPopupModal("Remove Cut"))
{
if (ImGui::BeginCombo("Cut", selectedCut.c_str()))
{
for (auto& cut : m_cutList)
{
if (ImGui::Selectable(cut.name.c_str(), cut.name == selectedCut, ImGuiSelectableFlags_DontClosePopups))
selectedCut = cut.name;
}
ImGui::EndCombo();
}
if (ImGui::Button("Ok"))
{
SpectrumManager::GetInstance().RemoveCut(selectedCut);
UpdateHistogramList();
UpdateCutList();
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Cancel"))
{
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
void EditorLayer::ExportHistogramDialog()
{
SPEC_PROFILE_FUNCTION();
static std::string filename = "";
static HistogramArgs selectedGram = HistogramArgs();
if(m_exportHistogram)
{
filename = "";
selectedGram = HistogramArgs();
m_exportHistogram = false;
ImGui::OpenPopup("Export Histogram");
}
if(ImGui::BeginPopupModal("Export Histogram"))
{
if(ImGui::BeginCombo("Histogram", selectedGram.name.c_str()))
{
for (auto& gram : m_histoList)
{
if (ImGui::Selectable(gram.name.c_str(), gram.name == selectedGram.name, ImGuiSelectableFlags_DontClosePopups))
selectedGram = gram;
}
ImGui::EndCombo();
}
ImGui::InputText("File", &filename);
ImGui::SameLine();
if(ImGui::Button("Open"))
{
m_fileDialog.OpenDialog(FileDialog::Type::SaveFile);
}
auto result = m_fileDialog.RenderFileDialog(".csv");
if(!result.first.empty() && result.second == FileDialog::Type::SaveFile)
filename = result.first;
if(ImGui::Button("Ok"))
{
ExportHistogram(selectedGram, filename);
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if(ImGui::Button("Cancel"))
ImGui::CloseCurrentPopup();
ImGui::EndPopup();
}
}
void EditorLayer::ExportHistogram(HistogramArgs selectedGram, const std::string& filename)
{
std::ofstream output(filename);
if(!output.is_open())
{
SPEC_ERROR("Unable to create export file {0}. Check pathing.", filename);
return;
}
std::vector<double> data = SpectrumManager::GetInstance().GetBinData(selectedGram.name);
output<<"Histogram Name,"<<selectedGram.name<<std::endl;
output<<"Min X,"<<selectedGram.min_x<<std::endl<<"Max X,"<<selectedGram.max_x<<std::endl;
if(selectedGram.y_par != "None")
output<<"Min Y,"<<selectedGram.min_y<<std::endl<<"Max Y,"<<selectedGram.max_y<<std::endl;
output<<"Nbins,"<<data.size()<<std::endl;
output<<"Bin,Counts"<<std::endl;
for(size_t i=0; i<data.size(); i++)
output<<i<<","<<data[i]<<std::endl;
output.close();
}
}

View File

@ -0,0 +1,81 @@
/*
EditorLayer.h
Application layer encapsulating the editor for Specter. Written using the Dear ImGui library. Setup based off of @TheCherno's Hazel game engine.
EditorLayer essentially controls the state for UI related actions.
GWM -- Feb 2022
*/
#ifndef EDITOR_LAYER_H
#define EDITOR_LAYER_H
#include "Specter/Core/Layer.h"
#include "Specter/Events/Event.h"
#include "Specter/Events/PhysicsEvent.h"
#include "Specter/Core/Histogram.h"
#include "Specter/Core/Cut.h"
#include "SpectrumPanel.h"
#include "FileDialog.h"
#include "SpectrumDialog.h"
#include "SourceDialog.h"
namespace Specter {
class EditorLayer : public Layer
{
public:
using EventCallbackFunc = std::function<void(Event&)>;
EditorLayer();
~EditorLayer();
void SetEventCallbackFunc(const EventCallbackFunc& f) { m_callbackFunc = f; }
virtual void OnAttach() override;
virtual void OnDetach() override;
virtual void OnImGuiRender() override;
virtual void OnUpdate(Timestep& step) override;
virtual void OnEvent(Event& event) override;
private:
void RemoveCutDialog();
void RemoveHistogramDialog();
void ExportHistogramDialog();
void UpdateHistogramList();
void UpdateCutList();
void UpdateParameterList(); //Currently not really used, only once. Params all made at construction time of PhysicsLayer
void ExportHistogram(HistogramArgs selectedGram, const std::string& filename);
EventCallbackFunc m_callbackFunc;
SpectrumPanel m_spectrumPanel;
FileDialog m_fileDialog;
SpectrumDialog m_spectrumDialog;
SourceDialog m_sourceDialog;
std::vector<HistogramArgs> m_histoList;
std::vector<CutArgs> m_cutList;
std::vector<std::string> m_paramList;
//ImGui Settings
bool dockspaceOpen = true;
bool opt_fullscreen = true;
bool opt_fullscreen_persistant = true;
ImGuiDockNodeFlags dockspace_flags = ImGuiDockNodeFlags_None;
ImGuiWindowFlags window_flags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking;
bool m_removeHistogram;
bool m_removeCut;
bool m_exportHistogram;
};
template<typename T>
bool SortByName(const T& p1, const T& p2)
{
return p1.name < p2.name;
}
}
#endif

View File

@ -0,0 +1,272 @@
/*
FileDialog.cpp
File dialog window in ImGui using std::filesystem. This is slightly complicated, as file dialogs change function
based on the type of action one wants to perform. In our case we have OpenFile, SaveFile, and OpenDirectory. One can also
specify the kind of file (extension). Use FontAwesome icons.
Use style:
if(ImGui::Button())
Set...FileDialog(true);
std::string value = ImGuiRender...(extension);
GWM -- Feb 2022
*/
#include "FileDialog.h"
#include "misc/cpp/imgui_stdlib.h"
#include "IconsFontAwesome5.h"
namespace Specter {
//Helper function to handle file size printing
std::string ConvertFileSystemSizeToString(std::uintmax_t value)
{
SPEC_PROFILE_FUNCTION();
int i = 0;
double mantissa = (double)value;
for (; mantissa >= 1024.0; ++i)
mantissa /= 1024.0;
mantissa = std::ceil(mantissa * 10.0) / 10.0;
return std::to_string(int(mantissa)) + "BKMGTPE"[i];
}
FileDialog::FileDialog() :
m_currentPath(std::filesystem::current_path()), m_type(Type::None), m_selectedItem(""), m_openDialogFlag(false)
{
table_flags = ImGuiTableFlags_BordersH | ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_BordersOuterV | ImGuiTableFlags_RowBg;
select_flags = ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_DontClosePopups;
}
FileDialog::~FileDialog() {}
//Each type of action has its own render function
std::pair<std::string, FileDialog::Type> FileDialog::RenderFileDialog(const std::string& ext)
{
SPEC_PROFILE_FUNCTION();
if (m_openDialogFlag)
{
m_selectedItem = "";
m_openDialogFlag = false;
m_currentPath = std::filesystem::current_path();
ImGui::OpenPopup("File Dialog");
}
std::string result = "";
if (ImGui::BeginPopupModal("File Dialog"))
{
switch (m_type)
{
case Type::OpenFile:
{
result = ImGuiRenderOpenFile(ext);
break;
}
case Type::SaveFile:
{
result = ImGuiRenderSaveFile(ext);
break;
}
case Type::OpenDir:
{
result = ImGuiRenderOpenDir();
break;
}
case Type::None: break;
}
ImGui::EndPopup();
}
return std::make_pair(result, m_type);
}
std::string FileDialog::ImGuiRenderOpenFile(const std::string& ext)
{
SPEC_PROFILE_FUNCTION();
std::string result = "";
std::string text = "";
ImGui::Text("%s", ("Current Directory: " + m_currentPath.lexically_normal().string()).c_str());
ImGui::SameLine();
ImGui::Text("%s", ("Extension Filter: "+ext).c_str());
ImGui::InputText("Selected", &m_selectedItem);
if (ImGui::Button("Ok"))
{
std::filesystem::path filepath = m_currentPath / m_selectedItem;
result = filepath.string();
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Cancel"))
ImGui::CloseCurrentPopup();
if (ImGui::BeginTable("File System", 2, table_flags, ImVec2(-1, -1)))
{
ImGui::TableSetupColumn("Name");
ImGui::TableSetupColumn("Size");
ImGui::TableHeadersRow();
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (ImGui::Selectable(ICON_FA_FOLDER " ..", false, select_flags))
{
m_selectedItem.clear();
m_currentPath.append("..");
}
ImGui::TableNextColumn();
ImGui::Text("N/A");
for (auto& entry : std::filesystem::directory_iterator(m_currentPath))
{
if (entry.is_directory())
{
ImGui::TableNextRow();
text = ICON_FA_FOLDER " " + std::filesystem::relative(entry.path(), m_currentPath).string();
ImGui::TableNextColumn();
if (ImGui::Selectable(text.c_str(), false, select_flags))
{
m_selectedItem.clear();
m_currentPath /= entry.path();
}
ImGui::TableNextColumn();
ImGui::Text("N/A");
}
else if (entry.path().filename().extension() == ext)
{
ImGui::TableNextRow();
text = ICON_FA_FILE " " + entry.path().filename().string();
ImGui::TableNextColumn();
if (ImGui::Selectable(text.c_str(), false, select_flags))
m_selectedItem = entry.path().filename().string();
ImGui::TableNextColumn();
ImGui::Text("%s", ConvertFileSystemSizeToString(entry.file_size()).c_str());
}
}
ImGui::EndTable();
}
return result;
}
std::string FileDialog::ImGuiRenderSaveFile(const std::string& ext)
{
SPEC_PROFILE_FUNCTION();
std::string result = "";
std::string text = "";
ImGui::Text("%s", ("Current Directory: "+m_currentPath.lexically_normal().string()).c_str());
ImGui::SameLine();
ImGui::Text("%s", ("Extension Filter: "+ext).c_str());
ImGui::InputText("Selected", &m_selectedItem);
if (ImGui::Button("Ok"))
{
std::filesystem::path filepath = m_currentPath / m_selectedItem;
result = filepath.string();
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Cancel"))
ImGui::CloseCurrentPopup();
if (ImGui::BeginTable("File System", 2, table_flags, ImVec2(-1, -1)))
{
ImGui::TableSetupColumn("Name");
ImGui::TableSetupColumn("Size");
ImGui::TableHeadersRow();
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (ImGui::Selectable(ICON_FA_FOLDER " ..", false, select_flags))
{
m_selectedItem.clear();
m_currentPath.append("..");
}
ImGui::TableNextColumn();
ImGui::Text("N/A");
for (auto& entry : std::filesystem::directory_iterator(m_currentPath))
{
if (entry.is_directory())
{
ImGui::TableNextRow();
text = ICON_FA_FOLDER " " + std::filesystem::relative(entry.path(), m_currentPath).string();
ImGui::TableNextColumn();
if (ImGui::Selectable(text.c_str(), false, select_flags))
{
m_selectedItem.clear();
m_currentPath /= entry.path();
}
ImGui::TableNextColumn();
ImGui::Text("N/A");
}
else if (entry.path().filename().extension() == ext)
{
ImGui::TableNextRow();
text = ICON_FA_FILE " " + entry.path().filename().string();
ImGui::TableNextColumn();
if (ImGui::Selectable(text.c_str(), false, select_flags))
m_selectedItem = entry.path().filename().string();
ImGui::TableNextColumn();
ImGui::Text("%s", ConvertFileSystemSizeToString(entry.file_size()).c_str());
}
}
ImGui::EndTable();
}
return result;
}
std::string FileDialog::ImGuiRenderOpenDir()
{
SPEC_PROFILE_FUNCTION();
std::string result = "";
std::string text = "";
ImGui::Text("%s", ("Current Directory: "+m_currentPath.lexically_normal().string()).c_str());
ImGui::InputText("Selected", &m_selectedItem);
if (ImGui::Button("Ok"))
{
result = m_selectedItem;
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Cancel"))
ImGui::CloseCurrentPopup();
if (ImGui::BeginTable("File System", 2, table_flags, ImVec2(-1, -1)))
{
ImGui::TableSetupColumn("Name");
ImGui::TableSetupColumn("Size");
ImGui::TableHeadersRow();
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (ImGui::Selectable(ICON_FA_FOLDER " ..", false, select_flags))
{
m_currentPath.append("..");
m_selectedItem = m_currentPath.string();
}
ImGui::TableNextColumn();
ImGui::Text("N/A");
for (auto& entry : std::filesystem::directory_iterator(m_currentPath))
{
ImGui::TableNextRow();
if (entry.is_directory())
{
text = ICON_FA_FOLDER " " + std::filesystem::relative(entry.path(), m_currentPath).string();
ImGui::TableNextColumn();
if (ImGui::Selectable(text.c_str(), false, select_flags))
{
m_currentPath /= entry.path();
m_selectedItem = m_currentPath.string();
}
ImGui::TableNextColumn();
ImGui::Text("N/A");
}
else
{
text = ICON_FA_FILE " " + entry.path().filename().string();
ImGui::TableNextColumn();
ImGui::Text("%s", text.c_str());
ImGui::TableNextColumn();
ImGui::Text("%s", ConvertFileSystemSizeToString(entry.file_size()).c_str());
}
}
ImGui::EndTable();
}
return result;
}
}

View File

@ -0,0 +1,58 @@
/*
FileDialog.h
File dialog window in ImGui using std::filesystem. This is slightly complicated, as file dialogs change function
based on the type of action one wants to perform. In our case we have OpenFile, SaveFile, and OpenDirectory. One can also
specify the kind of file (extension). Use FontAwesome icons.
Use style:
if(ImGui::Button())
Set...FileDialog(true);
std::string value = ImGuiRender...(extension);
GWM -- Feb 2022
*/
#ifndef FILE_DIALOG_H
#define FILE_DIALOG_H
#include <filesystem>
#include "imgui.h"
namespace Specter {
class FileDialog
{
public:
enum class Type
{
OpenFile,
SaveFile,
OpenDir,
None
};
FileDialog();
~FileDialog();
inline void OpenDialog(Type type) { m_type = type; m_openDialogFlag = true; }
std::pair<std::string, Type> RenderFileDialog(const std::string& ext = "");
private:
std::string ImGuiRenderOpenFile(const std::string& ext);
std::string ImGuiRenderSaveFile(const std::string& ext);
std::string ImGuiRenderOpenDir();
std::filesystem::path m_currentPath;
Type m_type;
std::string m_selectedItem;
bool m_openDialogFlag;
ImGuiTableFlags table_flags;
ImGuiSelectableFlags select_flags;
};
}
#endif

View File

@ -0,0 +1,121 @@
/*
SourceDialog.cpp
Handles selection of data source type and location specification. Needs to be updated when new source
types are added to Specter.
GWM -- Feb 2022
*/
#include "SourceDialog.h"
#include "Specter/Events/PhysicsEvent.h"
#include "Specter/Events/Event.h"
#include "Specter/Core/Application.h"
#include "Specter/Physics/Caen/CompassHit.h"
#include "imgui.h"
#include "misc/cpp/imgui_stdlib.h"
#include "IconsFontAwesome5.h"
namespace Specter {
SourceDialog::SourceDialog() :
m_openFlag(false), m_chosenPort("51489"), m_chosenWindow(2000000)
{
}
SourceDialog::~SourceDialog()
{
}
void SourceDialog::ImGuiRenderSourceDialog()
{
SPEC_PROFILE_FUNCTION();
static bool onlineFlag = false;
static bool offlineFlag = false;
static std::vector<DataSource::SourceType> availTypes = { DataSource::SourceType::CompassOnline, DataSource::SourceType::CompassOffline };
if (m_openFlag)
{
onlineFlag = false;
offlineFlag = false;
m_openFlag = false;
m_chosenType = DataSource::SourceType::None;
m_chosenLocation = "";
m_chosenPort = "51489";
m_chosenWindow = 3000000;
m_bitflags = 0;
m_channels_per_board = 16;
ImGui::OpenPopup(ICON_FA_LINK " Attach Source");
}
if (ImGui::BeginPopupModal(ICON_FA_LINK " Attach Source"))
{
if (ImGui::BeginCombo("Source Type", ConvertDataSourceTypeToString(m_chosenType).c_str()))
{
for (auto& type : availTypes)
{
if (ImGui::Selectable(ConvertDataSourceTypeToString(type).c_str(), type == m_chosenType, ImGuiSelectableFlags_DontClosePopups))
{
m_chosenType = type;
}
}
ImGui::EndCombo();
}
if (m_chosenType == DataSource::SourceType::CompassOnline)
{
ImGui::InputText("Hostname", &m_chosenLocation);
ImGui::InputText("Port", &m_chosenPort);
if (ImGui::RadioButton("Energy", (m_bitflags & CompassHeaders::Energy) != 0))
{
m_bitflags = m_bitflags ^ CompassHeaders::Energy;
}
ImGui::SameLine();
if (ImGui::RadioButton("Energy Short", (m_bitflags & CompassHeaders::EnergyShort) != 0))
{
m_bitflags = m_bitflags ^ CompassHeaders::EnergyShort;
}
ImGui::SameLine();
if (ImGui::RadioButton("Energy Calibrated", (m_bitflags & CompassHeaders::EnergyCalibrated) != 0))
{
m_bitflags = m_bitflags ^ CompassHeaders::EnergyCalibrated;
}
ImGui::InputInt("Channels Per Digitizer Board", &m_channels_per_board);
}
else if (m_chosenType == DataSource::SourceType::CompassOffline)
{
ImGui::InputText("Run Directory", &m_chosenLocation);
if (ImGui::Button("Choose Location"))
{
m_fileDialog.OpenDialog(FileDialog::Type::OpenDir);
}
auto temp = m_fileDialog.RenderFileDialog();
if (!temp.first.empty() && temp.second == FileDialog::Type::OpenDir)
m_chosenLocation = temp.first;
ImGui::InputInt("Channels Per Digitizer Board", &m_channels_per_board);
}
ImGui::InputInt("Coinc. Window (ps)", &m_chosenWindow);
if (ImGui::Button("Ok"))
{
if (m_chosenType == DataSource::SourceType::CompassOffline)
{
PhysicsStartEvent event(m_chosenLocation, m_chosenType, m_chosenWindow, m_chosenPort, false, 0U, m_channels_per_board);
Application::Get().OnEvent(event);
}
else if (m_chosenType == DataSource::SourceType::CompassOnline)
{
PhysicsStartEvent event(m_chosenLocation, m_chosenType, m_chosenWindow, m_chosenPort, true, m_bitflags, m_channels_per_board);
Application::Get().OnEvent(event);
}
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Cancel"))
{
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
}

View File

@ -0,0 +1,38 @@
/*
SourceDialog.h
Handles selection of data source type and location specification. Needs to be updated when new source
types are added to Specter.
GWM -- Feb 2022
*/
#ifndef SOURCE_DIALOG_H
#define SOURCE_DIALOG_H
#include "FileDialog.h"
#include "Specter/Physics/DataSource.h"
namespace Specter {
class SourceDialog
{
public:
SourceDialog();
~SourceDialog();
void ImGuiRenderSourceDialog();
inline void OpenSourceDialog() { m_openFlag = true; }
private:
bool m_openFlag;
DataSource::SourceType m_chosenType;
std::string m_chosenLocation;
std::string m_chosenPort;
FileDialog m_fileDialog;
uint16_t m_bitflags;
int m_chosenWindow;
int m_channels_per_board;
};
}
#endif

View File

@ -0,0 +1,280 @@
/*
SpectrumDialog.h
Handles creation of new spectra. Pretty much that simple.
GWM -- Feb 2022
*/
#include "SpectrumDialog.h"
#include "Specter/Core/SpectrumManager.h"
#include "misc/cpp/imgui_stdlib.h"
#include "IconsFontAwesome5.h"
namespace Specter {
SpectrumDialog::SpectrumDialog() :
m_openFlag(false), m_openCutFlag(false)
{
selectFlags = ImGuiSelectableFlags_DontClosePopups;
tableFlags = ImGuiTableFlags_BordersH | ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_BordersOuterV | ImGuiTableFlags_RowBg;
}
SpectrumDialog::~SpectrumDialog()
{
}
bool SpectrumDialog::ImGuiRenderSpectrumDialog(const std::vector<HistogramArgs>& histoList, const std::vector<CutArgs>& cutList, const std::vector<std::string>& paramList)
{
SPEC_PROFILE_FUNCTION();
static std::string selectedCut = "";
bool result = false;
if (m_openFlag)
{
m_openFlag = false;
m_newParams = m_blank;
m_subhistos.clear();
ImGui::OpenPopup(ICON_FA_CHART_BAR " New Spectrum Dialog");
}
if (ImGui::BeginPopupModal(ICON_FA_CHART_BAR " New Spectrum Dialog"))
{
ImGui::InputText("Spectrum Name", &m_newParams.name);
if (ImGui::BeginCombo("Spectrum Type", ConvertSpectrumTypeToString(m_newParams.type).c_str()))
{
if (ImGui::Selectable("Histogram1D", m_newParams.type == SpectrumType::Histo1D, selectFlags))
m_newParams.type = SpectrumType::Histo1D;
else if (ImGui::Selectable("Histogram2D", m_newParams.type == SpectrumType::Histo2D, selectFlags))
m_newParams.type = SpectrumType::Histo2D;
else if (ImGui::Selectable("Summary", m_newParams.type == SpectrumType::Summary, selectFlags))
m_newParams.type = SpectrumType::Summary;
ImGui::EndCombo();
}
switch (m_newParams.type)
{
case SpectrumType::Histo1D: RenderDialog1D(paramList); break;
case SpectrumType::Histo2D: RenderDialog2D(paramList); break;
case SpectrumType::Summary: RenderDialogSummary(paramList); break;
case SpectrumType::None: break;
}
if (ImGui::TreeNode("Applied Cuts"))
{
for (auto& name : m_newParams.cutsAppliedTo)
{
ImGui::BulletText("%s", name.c_str());
}
ImGui::TreePop();
}
if (ImGui::Button("Apply Cuts"))
{
m_openCutFlag = true;
}
RenderCutDialog(cutList);
if (ImGui::Button("Ok"))
{
switch (m_newParams.type)
{
case SpectrumType::Histo1D: SpectrumManager::GetInstance().AddHistogram(m_newParams); break;
case SpectrumType::Histo2D: SpectrumManager::GetInstance().AddHistogram(m_newParams); break;
case SpectrumType::Summary: SpectrumManager::GetInstance().AddHistogramSummary(m_newParams, m_subhistos); break;
case SpectrumType::None: break;
}
ImGui::CloseCurrentPopup();
result = true;
}
ImGui::SameLine();
if (ImGui::Button("Cancel"))
{
ImGui::CloseCurrentPopup();
result = false;
}
ImGui::EndPopup();
}
return result;
}
void SpectrumDialog::RenderDialog1D(const std::vector<std::string>& paramList)
{
if (ImGui::BeginTable("SpecParamsTable", 4))
{
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (ImGui::BeginCombo("X Param.", m_newParams.x_par.c_str()))
{
for (auto& params : paramList)
{
if (ImGui::Selectable(params.c_str(), params == m_newParams.x_par, selectFlags))
m_newParams.x_par = params;
}
ImGui::EndCombo();
}
ImGui::TableNextColumn();
ImGui::InputInt("X Bins", &m_newParams.nbins_x);
ImGui::TableNextColumn();
ImGui::InputDouble("Min X", &m_newParams.min_x);
ImGui::TableNextColumn();
ImGui::InputDouble("Max X", &m_newParams.max_x);
ImGui::EndTable();
}
}
void SpectrumDialog::RenderDialog2D(const std::vector<std::string>& paramList)
{
if (ImGui::BeginTable("SpecParamsTable", 4))
{
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (ImGui::BeginCombo("X Param.", m_newParams.x_par.c_str()))
{
for (auto& params : paramList)
{
if (ImGui::Selectable(params.c_str(), params == m_newParams.x_par, selectFlags))
m_newParams.x_par = params;
}
ImGui::EndCombo();
}
ImGui::TableNextColumn();
ImGui::InputInt("X Bins", &m_newParams.nbins_x);
ImGui::TableNextColumn();
ImGui::InputDouble("Min X", &m_newParams.min_x);
ImGui::TableNextColumn();
ImGui::InputDouble("Max X", &m_newParams.max_x);
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (ImGui::BeginCombo("Y Param.", m_newParams.y_par.c_str()))
{
for (auto& params : paramList)
{
if (ImGui::Selectable(params.c_str(), params == m_newParams.y_par, selectFlags))
m_newParams.y_par = params;
}
ImGui::EndCombo();
}
ImGui::TableNextColumn();
ImGui::InputInt("Y Bins", &m_newParams.nbins_y);
ImGui::TableNextColumn();
ImGui::InputDouble("Min Y", &m_newParams.min_y);
ImGui::TableNextColumn();
ImGui::InputDouble("Max Y", &m_newParams.max_y);
ImGui::EndTable();
}
}
void SpectrumDialog::RenderDialogSummary(const std::vector<std::string>& paramList)
{
if (ImGui::BeginTable("SpecParamsTable", 3))
{
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::InputInt("X Bins", &m_newParams.nbins_x);
ImGui::TableNextColumn();
ImGui::InputDouble("Min X", &m_newParams.min_x);
ImGui::TableNextColumn();
ImGui::InputDouble("Max X", &m_newParams.max_x);
ImGui::EndTable();
}
if (ImGui::TreeNode("Selected Parameters"))
{
for (auto& name : m_subhistos)
{
ImGui::BulletText("%s", name.c_str());
}
ImGui::TreePop();
}
if (ImGui::Button("Add Parameter"))
{
m_subhistos.clear();
ImGui::OpenPopup("Param List");
}
if (ImGui::BeginPopupModal("Param List"))
{
if (ImGui::Button("Ok"))
{
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Cancel"))
{
m_subhistos.clear();
ImGui::CloseCurrentPopup();
}
auto check_func = [this](const std::string& name)
{
for (auto& par : m_subhistos)
{
if (name == par)
return true;
}
return false;
};
if (ImGui::BeginTable("Parameters", 1, tableFlags, ImVec2(-1, -1)))
{
for (auto& param : paramList)
{
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (ImGui::Selectable(param.c_str(), check_func(param), selectFlags))
{
if (!check_func(param))
{
m_subhistos.push_back(param);
}
else
{
auto iter = std::remove(m_subhistos.begin(), m_subhistos.end(), param);
m_subhistos.erase(iter, m_subhistos.end());
}
}
}
ImGui::EndTable();
}
ImGui::EndPopup();
}
}
void SpectrumDialog::RenderCutDialog(const std::vector<CutArgs>& cutList)
{
static std::string selectedCut = "";
if (m_openCutFlag)
{
selectedCut = "";
m_openCutFlag = false;
ImGui::OpenPopup("Cut List");
}
if (ImGui::BeginPopup("Cut List"))
{
for (auto& cut : cutList)
{
if (ImGui::Selectable(cut.name.c_str(), cut.name == selectedCut, selectFlags))
selectedCut = cut.name;
}
ImGui::InputText("Selected Cut", &selectedCut);
if (ImGui::Button("Ok"))
{
m_newParams.cutsAppliedTo.push_back(selectedCut);
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Cancel"))
{
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
}

View File

@ -0,0 +1,45 @@
/*
SpectrumDialog.h
Handles creation of new spectra. Pretty much that simple.
GWM -- Feb 2022
*/
#ifndef SPECTRUM_DIALOG_H
#define SPECTRUM_DIALOG_H
#include "Specter/Core/Histogram.h"
#include "Specter/Core/Cut.h"
#include "imgui.h"
namespace Specter {
class SpectrumDialog
{
public:
SpectrumDialog();
~SpectrumDialog();
bool ImGuiRenderSpectrumDialog(const std::vector<HistogramArgs>& histoList, const std::vector<CutArgs>& cutList, const std::vector<std::string>& paramList);
inline void SetSpectrumDialog() { m_openFlag = true; }
private:
void RenderDialog1D(const std::vector<std::string>& paramList);
void RenderDialog2D(const std::vector<std::string>& paramList);
void RenderDialogSummary(const std::vector<std::string>& paramList);
void RenderCutDialog(const std::vector<CutArgs>& cutList);
bool m_openFlag;
bool m_openCutFlag;
HistogramArgs m_newParams;
HistogramArgs m_blank;
std::vector<std::string> m_subhistos;
ImGuiSelectableFlags selectFlags;
ImGuiTableFlags tableFlags;
};
}
#endif

View File

@ -0,0 +1,393 @@
/*
SpectrumPanel.cpp
This is the big boi. Renders a panel holding all of the drawn plots. Good news is that in general only a few things really require
any modification if new types of plots are to be rendered, basically just the zoomed in spectrum rendering.
GWM -- Feb 2022
*/
#include "SpectrumPanel.h"
#include "Specter/Core/SpectrumManager.h"
#include "misc/cpp/imgui_stdlib.h"
#include "IconsFontAwesome5.h"
namespace Specter {
//Convert a StatResults struct from analysis to a std::string helper function
std::string GenerateStatString(const std::string& name, const StatResults& results, bool is2D = true)
{
SPEC_PROFILE_FUNCTION();
std::stringstream stream;
stream << "Region: " << name << "\n" << "Integral: " << results.integral << "\n";
if (results.integral == 0.0)
return stream.str();
stream << "Centroid X: " << results.cent_x << "\nStd. Dev. X: " << results.sigma_x << "\nFWHM X: " << 2.355 * results.sigma_x << "\n";
if(is2D)
stream << "Centroid Y: " << results.cent_y << "\nStd. Dev. Y: " << results.sigma_y << "\nFWHM Y: " << 2.355 * results.sigma_y << "\n";
return stream.str();
}
SpectrumPanel::SpectrumPanel() :
m_zoomedFlag(false), m_cutModeFlag(false), m_acceptCutFlag(false), m_zoomedGram(), m_totalSlots(1), m_nRegions(0)
{
m_tableSizes[0] = 1; m_tableSizes[1] = 1;
}
SpectrumPanel::~SpectrumPanel() {}
//Main render function. Handles generating subplot regions as well as the zoomed in region
bool SpectrumPanel::OnImGuiRender(const std::vector<HistogramArgs>& histoList, const std::vector<CutArgs>& cutList, const std::vector<std::string>& paramList)
{
SPEC_PROFILE_FUNCTION();
static bool acceptCutFlag = false;
m_result = false;
if (ImGui::Begin("Active View"))
{
if (histoList.size() > 0)
{
if (m_zoomedFlag && m_zoomedGram.type != SpectrumType::None)
{
RenderCutButton();
ImGui::SameLine();
if(ImGui::Button("Clear"))
{
SpectrumManager::GetInstance().ClearHistogram(m_zoomedGram.name);
}
ImGui::SameLine();
RenderRemoveRegionButton();
if (m_zoomedGram.type == SpectrumType::Histo2D || m_zoomedGram.type == SpectrumType::Summary)
{
float* scale = SpectrumManager::GetInstance().GetColorScaleRange(m_zoomedGram.name);
ImGui::DragFloatRange2("Min / Max", &(scale[0]), &(scale[1]), 0.01f);
ImPlot::ColormapScale("##HistogramScale", scale[0], scale[1], ImVec2(0, -1), ImPlotColormap_Viridis);
ImGui::SameLine();
}
if (ImPlot::BeginPlot(m_zoomedGram.name.c_str(), ImVec2(-1, -1)))
{
SpectrumManager::GetInstance().DrawHistogram(m_zoomedGram.name);
if (!m_cutModeFlag && ImPlot::IsPlotHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left))
{
m_zoomedFlag = false;
m_zoomedGram = HistogramArgs();
}
else if (m_cutModeFlag)
{
HandleCutMode();
}
if (ImPlot::IsPlotSelected()) {
auto select = ImPlot::GetPlotSelection();
if (ImGui::IsMouseClicked(ImPlot::GetInputMap().SelectCancel)) {
ImPlot::CancelPlotSelection();
m_integralRegions.emplace_back(select, "integralRegion_"+std::to_string(m_nRegions), m_zoomedGram.name);
m_nRegions++;
}
}
for (size_t i = 0; i < m_integralRegions.size(); i++)
{
auto& region = m_integralRegions[i];
if (m_zoomedGram.name == region.histogram_name)
{
ImPlot::DragRect(int(i), &region.region.X.Min, &region.region.Y.Min, &region.region.X.Max, &region.region.Y.Max, ImVec4(1, 0, 1, 1), ImPlotDragToolFlags_NoFit);
StatResults results = SpectrumManager::GetInstance().AnalyzeHistogramRegion(m_zoomedGram.name, region.region);
ImPlot::PlotText(GenerateStatString(region.name, results, m_zoomedGram.y_par != "None").c_str(), (region.region.X.Max + region.region.X.Min) * 0.5,
(region.region.Y.Min + region.region.Y.Max) * 0.5);
}
}
ImPlot::EndPlot();
}
RenderAcceptCutDialog();
}
else
{
ImGui::SliderInt2("Rows, Columns", m_tableSizes, 1, 3);
ImGui::SameLine();
if (ImGui::Button("Clear All"))
{
SpectrumManager::GetInstance().ClearHistograms();
}
m_totalSlots = m_tableSizes[0] * m_tableSizes[1];
m_selectedGrams.resize(m_totalSlots);
if (ImGui::BeginTable("Select Histograms", m_tableSizes[1]))
{
std::string label;
int this_gram;
for (int i = 0; i < m_tableSizes[0]; i++)
{
ImGui::TableNextRow();
for (int j = 0; j < m_tableSizes[1]; j++)
{
ImGui::TableNextColumn();
this_gram = i * m_tableSizes[1] + j;
label = "Histogram" + std::to_string(this_gram);
if (ImGui::BeginCombo(label.c_str(), m_selectedGrams[this_gram].name.c_str()))
{
for (auto& params : histoList)
{
if (ImGui::Selectable(params.name.c_str(), params.name == m_selectedGrams[this_gram].name))
m_selectedGrams[this_gram] = params;
}
ImGui::EndCombo();
}
}
}
ImGui::EndTable();
}
if (ImPlot::BeginSubplots("Histograms", m_tableSizes[0], m_tableSizes[1], ImVec2(-1, -1)))
{
int i = 0;
for (auto& spec : m_selectedGrams)
{
if (ImPlot::BeginPlot(spec.name.c_str()))
{
SpectrumManager::GetInstance().DrawHistogram(spec.name);
if (ImPlot::IsPlotHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left))
{
m_zoomedFlag = true;
m_zoomedGram = spec;
}
for (size_t i = 0; i < m_integralRegions.size(); i++)
{
auto& region = m_integralRegions[i];
if (spec.name == region.histogram_name)
{
ImPlot::DragRect(int(i), &region.region.X.Min, &region.region.Y.Min, &region.region.X.Max, &region.region.Y.Max, ImVec4(1, 0, 1, 1));
}
}
ImPlot::EndPlot();
}
i++;
}
ImPlot::EndSubplots();
}
}
}
}
ImGui::End();
return m_result;
}
void SpectrumPanel::HandleCutMode()
{
switch (m_zoomedGram.type)
{
case SpectrumType::Histo1D:
{
if (m_newCutX.size() == 2)
{
m_acceptCutFlag = true;
}
else if (ImPlot::IsPlotHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left))
{
m_newCutX.push_back(ImPlot::GetPlotMousePos().x);
}
ImPlot::PlotVLines(m_newCutArgs.name.c_str(), m_newCutX.data(), int(m_newCutX.size()));
break;
}
case SpectrumType::Histo2D:
{
if (m_newCutX.size() >= 2 && ImPlot::IsPlotHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left))
{
m_acceptCutFlag = true;
m_newCutX.push_back(m_newCutX[0]);
m_newCutY.push_back(m_newCutY[0]);
}
else if (ImPlot::IsPlotHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left))
{
auto point = ImPlot::GetPlotMousePos();
m_newCutX.push_back(point.x);
m_newCutY.push_back(point.y);
}
ImPlot::PlotLine(m_newCutArgs.name.c_str(), m_newCutX.data(), m_newCutY.data(), int(m_newCutX.size()));
break;
}
case SpectrumType::Summary:
{
if (m_newCutX.size() == 2)
{
m_acceptCutFlag = true;
}
else if (ImPlot::IsPlotHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left))
{
m_newCutX.push_back(ImPlot::GetPlotMousePos().x);
}
ImPlot::PlotVLines(m_newCutArgs.name.c_str(), m_newCutX.data(), int(m_newCutX.size()));
break;
}
case SpectrumType::None:
{
m_cutModeFlag = false;
break;
}
}
}
void SpectrumPanel::RenderAcceptCutDialog()
{
if (m_acceptCutFlag)
{
m_acceptCutFlag = false;
m_cutModeFlag = false;
ImGui::OpenPopup("Accept Cut");
}
if (ImGui::BeginPopupModal("Accept Cut"))
{
ImGui::Text("Save this Cut?");
if (ImGui::Button("Yes"))
{
SpectrumManager& manager = SpectrumManager::GetInstance();
switch (m_newCutArgs.type)
{
case CutType::Cut1D:
{
std::sort(m_newCutX.begin(), m_newCutX.end());
manager.AddCut(m_newCutArgs, m_newCutX[0], m_newCutX[1]);
manager.AddCutToHistogramDraw(m_newCutArgs.name, m_zoomedGram.name);
break;
}
case CutType::Cut2D:
{
manager.AddCut(m_newCutArgs, m_newCutX, m_newCutY);
manager.AddCutToHistogramDraw(m_newCutArgs.name, m_zoomedGram.name);
break;
}
case CutType::CutSummaryAny:
{
std::sort(m_newCutX.begin(), m_newCutX.end());
std::vector<std::string> subhistos = manager.GetSubHistograms(m_zoomedGram.name);
manager.AddCut(m_newCutArgs, subhistos, m_newCutX[0], m_newCutX[1]);
manager.AddCutToHistogramDraw(m_newCutArgs.name, m_zoomedGram.name);
break;
}
case CutType::CutSummaryAll:
{
std::sort(m_newCutX.begin(), m_newCutX.end());
std::vector<std::string> subhistos = manager.GetSubHistograms(m_zoomedGram.name);
manager.AddCut(m_newCutArgs, subhistos, m_newCutX[0], m_newCutX[1]);
manager.AddCutToHistogramDraw(m_newCutArgs.name, m_zoomedGram.name);
break;
}
case CutType::None:
{
SPEC_ERROR("Trying to add None type cut to manager at SpectrumPanel::RenderAcceptCutDialog!");
break;
}
}
ImGui::CloseCurrentPopup();
m_result = true;
}
ImGui::SameLine();
if (ImGui::Button("No"))
{
ImGui::CloseCurrentPopup();
m_result = false;
}
ImGui::EndPopup();
}
}
//Renders Cut button as well as dialog for creating cuts.
void SpectrumPanel::RenderCutButton()
{
if (ImGui::Button(ICON_FA_CUT " Draw Cut"))
{
m_newCutArgs = CutArgs();
m_newCutX.resize(0);
m_newCutY.resize(0);
ImGui::OpenPopup(ICON_FA_CUT " New Cut Dialog");
}
if (ImGui::BeginPopupModal(ICON_FA_CUT " New Cut Dialog"))
{
m_newCutArgs.x_par = m_zoomedGram.x_par;
m_newCutArgs.y_par = m_zoomedGram.y_par;
switch (m_zoomedGram.type)
{
case SpectrumType::Histo1D:
{
m_newCutArgs.type = CutType::Cut1D;
ImGui::BulletText("%s", ("X Parameter: " + m_newCutArgs.x_par).c_str());
break;
}
case SpectrumType::Histo2D:
{
m_newCutArgs.type = CutType::Cut2D;
ImGui::BulletText("%s", ("X Parameter: " + m_newCutArgs.x_par).c_str());
ImGui::BulletText("%s", ("Y Parameter: " + m_newCutArgs.y_par).c_str());
break;
}
case SpectrumType::Summary:
{
if (ImGui::RadioButton("CutSummaryAny", m_newCutArgs.type == CutType::CutSummaryAny))
m_newCutArgs.type = CutType::CutSummaryAny;
ImGui::SameLine();
if (ImGui::RadioButton("CutSummaryAll", m_newCutArgs.type == CutType::CutSummaryAll))
m_newCutArgs.type = CutType::CutSummaryAll;
break;
}
case SpectrumType::None: m_newCutArgs.type = CutType::None; break;
}
ImGui::InputText("Cut Name", &m_newCutArgs.name);
if (ImGui::Button("Accept & Draw"))
{
m_cutModeFlag = true;
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Cancel"))
{
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
//Simple button/dialog for removing integration regions
void SpectrumPanel::RenderRemoveRegionButton()
{
static std::string selectedRegion = "";
if (ImGui::Button("Delete Region"))
{
selectedRegion = "";
ImGui::OpenPopup("Remove Integral Region");
}
if (ImGui::BeginPopupModal("Remove Integral Region"))
{
if (ImGui::BeginCombo("Region", selectedRegion.c_str()))
{
for (auto& region : m_integralRegions)
{
if (region.histogram_name == m_zoomedGram.name)
{
if (ImGui::Selectable(region.name.c_str(), region.name == selectedRegion, ImGuiSelectableFlags_DontClosePopups))
selectedRegion = region.name;
}
}
ImGui::EndCombo();
}
if (ImGui::Button("Ok"))
{
RemoveSelectedRegion(selectedRegion);
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Cancel"))
{
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
void SpectrumPanel::RemoveSelectedRegion(const std::string& region)
{
for (size_t i=0; i<m_integralRegions.size(); i++)
{
if (m_integralRegions[i].name == region)
{
m_integralRegions.erase(m_integralRegions.begin() + i);
break;
}
}
}
}

View File

@ -0,0 +1,63 @@
/*
SpectrumPanel.h
This is the big boi. Renders a panel holding all of the drawn plots. Good news is that in general only a few things really require
any modification if new types of plots are to be rendered, basically just the zoomed in spectrum rendering.
GWM -- Feb 2022
*/
#ifndef SPECTRUM_PANEL_H
#define SPECTRUM_PANEL_H
#include "Specter/Core/Histogram.h"
#include "Specter/Core/Cut.h"
#include "imgui.h"
#include "implot.h"
namespace Specter {
//Simple struct for holding a region of interest
struct IntegrationRegion
{
IntegrationRegion(const ImPlotRect& rect, const std::string& n, const std::string& hist_n) :
region(rect), name(n), histogram_name(hist_n)
{
}
ImPlotRect region;
std::string name = "";
std::string histogram_name = "";
};
class SpectrumPanel
{
public:
SpectrumPanel();
~SpectrumPanel();
bool OnImGuiRender(const std::vector<HistogramArgs>& histoList, const std::vector<CutArgs>& cutList, const std::vector<std::string>& paramList);
inline const std::string& GetZoomedOnHistogram() { return m_zoomedGram.name; }
inline const bool IsZoomed() { return m_zoomedFlag; }
private:
void HandleCutMode();
void RenderAcceptCutDialog();
void RenderCutButton();
void RenderRemoveRegionButton();
void RemoveSelectedRegion(const std::string& region);
std::vector<HistogramArgs> m_selectedGrams;
std::vector<IntegrationRegion> m_integralRegions;
bool m_zoomedFlag;
bool m_cutModeFlag;
bool m_acceptCutFlag;
bool m_result;
HistogramArgs m_zoomedGram;
int m_tableSizes[2];
int m_totalSlots;
int m_nRegions;
CutArgs m_newCutArgs;
std::vector<double> m_newCutX;
std::vector<double> m_newCutY;
};
}
#endif

View File

@ -0,0 +1,59 @@
/*
AppEvent.h
Events related to the main application (window events mostly). Again, based on @TheCherno's work.
GWM -- Feb 2022
*/
#ifndef APP_EVENT_H
#define APP_EVENT_H
#include "Event.h"
namespace Specter {
// Window closing is pure event (no data)
class WindowCloseEvent : public Event
{
public:
WindowCloseEvent() {};
EVENT_CATEGORY_SETUP(EventCategoryApp)
EVENT_TYPE_SETUP(WindowClose)
};
class WindowResizeEvent : public Event
{
public:
WindowResizeEvent(int x, int y) :
m_xSize(x), m_ySize(y)
{
}
inline int GetXSize() { return m_xSize; }
inline int GetYSize() { return m_ySize; }
std::string ToString() const override
{
std::stringstream ss;
ss << GetName() << " to size: (" << m_xSize << ", " << m_ySize << ")";
return ss.str();
}
EVENT_CATEGORY_SETUP(EventCategoryApp)
EVENT_TYPE_SETUP(WindowResize)
private:
int m_xSize, m_ySize;
};
class AppUpdateEvent : public Event
{
public:
AppUpdateEvent() = default;
EVENT_CATEGORY_SETUP(EventCategoryApp)
EVENT_TYPE_SETUP(AppUpdate)
};
}
#endif

View File

@ -0,0 +1,89 @@
/*
Event.h
Event system for Specter. Based entirely upon the work of @TheCherno in his game engine series, with additons specific to
Specter. Abstract Event class and an EventDispatcher. EventDispatcher links a function to the event for handling. See Application::OnEvent for
an example of use.
GWM -- Feb 2022
*/
#ifndef EVENT_H
#define EVENT_H
#include "Specter/Core/SpecCore.h"
namespace Specter {
enum class EventType
{
None=0,
WindowClose, WindowResize, WindowFocus, WindowLostFocus, WindowMoved,
KeyPressed, KeyReleased, KeyTyped,
MouseButtonPressed, MouseButtonReleased, MouseScrolled, MouseMoved,
AppUpdate,
PhysicsStart, PhysicsStop, PhysicsParam
};
enum EventCategory
{
EventCategoryNone=0,
EventCategoryApp=BIT(0),
EventCategoryInput=BIT(1),
EventCategoryKey=BIT(2),
EventCategoryMouse=BIT(3),
EventCategoryWindow=BIT(4),
EventCategoryPhysics=BIT(5),
};
//Some function generation automation to reduce code written for all events
#define EVENT_CATEGORY_SETUP(cat) virtual int GetCategoryFlags() const override { return cat; }
#define EVENT_TYPE_SETUP(type) static EventType GetStaticType() { return EventType::type; } \
virtual EventType GetEventType() const override { return GetStaticType(); } \
virtual const char* GetName() const override { return #type; }
class Event
{
friend class EventDispatcher;
public:
virtual EventType GetEventType() const = 0;
virtual const char* GetName() const = 0;
virtual int GetCategoryFlags() const = 0;
virtual std::string ToString() const { return GetName(); }
inline bool IsCategory(EventCategory cat) const { return GetCategoryFlags() & cat; }
bool handledFlag = false;
};
class EventDispatcher
{
public:
EventDispatcher(Event& e) :
m_event(e)
{
}
template<typename T, typename F>
bool Dispatch(const F& function)
{
if(m_event.GetEventType() == T::GetStaticType())
{
m_event.handledFlag = function(static_cast<T&>(m_event));
return true;
}
else
{
return false;
}
}
private:
Event& m_event;
};
//For easy printing
inline std::ostream& operator<<(std::ostream& os, const Event& e)
{
return os << e.ToString();
}
}
#endif

View File

@ -0,0 +1,95 @@
/*
KeyEvent.h
Events related to key presses. Again, based on @TheCherno's work.
GWM -- Feb 2022
*/
#ifndef KEY_EVENT_H
#define KEY_EVENT_H
#include "Event.h"
namespace Specter {
//Since so many key related events, have a base
class KeyEvent : public Event
{
public:
inline int GetKeycode() const { return m_keycode; }
EVENT_CATEGORY_SETUP(EventCategoryKey | EventCategoryInput)
protected:
KeyEvent(int code) :
m_keycode(code)
{
}
int m_keycode;
};
class KeyPressedEvent : public KeyEvent
{
public:
KeyPressedEvent(int code, int count) :
KeyEvent(code), m_repeatCount(count)
{
}
EVENT_TYPE_SETUP(KeyPressed)
inline int GetRepeatCount() const { return m_repeatCount; }
std::string ToString() const override
{
std::stringstream stream;
stream << GetName() << " with code "<<m_keycode << " pressed "<<m_repeatCount<<" times.";
return stream.str();
}
private:
int m_repeatCount;
};
class KeyReleasedEvent : public KeyEvent
{
public:
KeyReleasedEvent(int code) :
KeyEvent(code)
{
};
EVENT_TYPE_SETUP(KeyReleased)
std::string ToString() const override
{
std::stringstream ss;
ss << GetName() << " with code " << m_keycode;
return ss.str();
}
};
class KeyTypedEvent : public KeyEvent
{
public:
KeyTypedEvent(int code) :
KeyEvent(code)
{
};
EVENT_TYPE_SETUP(KeyTyped)
unsigned int GetCharacter() { return (unsigned int)m_keycode; }
std::string ToString() const override
{
std::stringstream ss;
ss << GetName() << " with code " << m_keycode;
return ss.str();
}
};
}
#endif

View File

@ -0,0 +1,109 @@
/*
KeyEvent.h
Events related to mouse. Again, based on @TheCherno's work.
GWM -- Feb 2022
*/
#ifndef MOUSE_EVENT_H
#define MOUSE_EVENT_H
#include "Event.h"
namespace Specter {
class MouseMovedEvent : public Event
{
public:
MouseMovedEvent(float x, float y) :
m_xPos(x), m_yPos(y)
{
}
inline float GetXPosition() { return m_xPos; }
inline float GetYPosition() { return m_yPos; }
std::string ToString() const override
{
std::stringstream ss;
ss << GetName() << " to position (" << m_xPos << ", " << m_yPos << ")";
return ss.str();
}
EVENT_CATEGORY_SETUP(EventCategoryMouse | EventCategoryInput)
EVENT_TYPE_SETUP(MouseMoved)
private:
float m_xPos, m_yPos;
};
class MouseScrolledEvent : public Event
{
public:
MouseScrolledEvent(float x, float y) :
m_xOffset(x), m_yOffset(y)
{
}
inline float GetXOffset() { return m_xOffset; }
inline float GetYOffset() { return m_yOffset; }
std::string ToString() const override
{
std::stringstream ss;
ss << GetName() << " to offset (" << m_xOffset << ", " << m_yOffset << ")";
return ss.str();
}
EVENT_CATEGORY_SETUP(EventCategoryMouse | EventCategoryInput)
EVENT_TYPE_SETUP(MouseScrolled)
private:
float m_xOffset, m_yOffset;
};
class MouseButtonPressedEvent : public Event
{
public:
MouseButtonPressedEvent(int code) :
m_buttonCode(code)
{
}
inline int GetButtonCode() { return m_buttonCode; }
std::string ToString() const override
{
std::stringstream ss;
ss << GetName() << " with button code " << m_buttonCode;
return ss.str();
}
EVENT_CATEGORY_SETUP(EventCategoryMouse | EventCategoryInput)
EVENT_TYPE_SETUP(MouseButtonPressed)
private:
int m_buttonCode;
};
class MouseButtonReleasedEvent : public Event
{
public:
MouseButtonReleasedEvent(int code) :
m_buttonCode(code)
{
}
inline int GetButtonCode() { return m_buttonCode; }
std::string ToString() const override
{
std::stringstream ss;
ss << GetName() << " with button code " << m_buttonCode;
return ss.str();
}
EVENT_CATEGORY_SETUP(EventCategoryMouse | EventCategoryInput)
EVENT_TYPE_SETUP(MouseButtonReleased)
private:
int m_buttonCode;
};
}
#endif

View File

@ -0,0 +1,85 @@
/*
KeyEvent.h
Events related to physics processes. Again, based on @TheCherno's work.
GWM -- Feb 2022
Update to reflect new CAEN binary data format with headers to indicate data contents.
GWM -- May 2022
*/
#ifndef PHYSICS_EVENT_H
#define PHYSICS_EVENT_H
#include "Event.h"
#include "Specter/Physics/DataSource.h"
namespace Specter {
//When we start physics, need info for what kind of source we make
class PhysicsStartEvent : public Event
{
public:
//Bitflags is a final option for random crap needed for a source. Currently used for compass online to indicate header state.
PhysicsStartEvent(const std::string& loc, DataSource::SourceType type, uint64_t window, const std::string& port = "51489", bool sortFlag=false, uint16_t bitflags = 0,
int channels_per_board=16) :
m_sourceLocation(loc), m_port(port), m_sourceType(type), m_coincidenceWindow(window), m_sortFlag(sortFlag), m_bitflags(bitflags), m_channels_per_board(channels_per_board)
{}
inline const std::string GetSourceLocation() const { return m_sourceLocation; }
inline const std::string GetSourcePort() const { return m_port; }
inline const DataSource::SourceType GetSourceType() const { return m_sourceType; }
inline const uint64_t GetCoincidenceWindow() const { return m_coincidenceWindow; }
inline const bool GetSortFlag() const { return m_sortFlag; }
inline const int GetChannelsPerBoard() const { return m_channels_per_board; }
inline const uint16_t GetBitFlags() const { return m_bitflags; }
std::string ToString() const override
{
return "Starting PhysicsEventBuilder with DataSource of type {0} at location {1}" + m_sourceLocation + ConvertDataSourceTypeToString(m_sourceType);
}
EVENT_CATEGORY_SETUP(EventCategoryPhysics);
EVENT_TYPE_SETUP(PhysicsStart);
private:
std::string m_sourceLocation;
std::string m_port;
DataSource::SourceType m_sourceType;
uint64_t m_coincidenceWindow;
bool m_sortFlag;
uint16_t m_bitflags;
int m_channels_per_board;
};
class PhysicsStopEvent : public Event
{
public:
PhysicsStopEvent() {}
std::string ToString() const override
{
return "Stopping PhysicsEventBuilder";
}
EVENT_CATEGORY_SETUP(EventCategoryPhysics);
EVENT_TYPE_SETUP(PhysicsStop);
};
//Unused. Exists as a potential path of upgrade
class PhysicsParamEvent : public Event
{
public:
PhysicsParamEvent() {}
std::string ToString() const override
{
return "Updating Parameter lists!";
}
EVENT_CATEGORY_SETUP(EventCategoryPhysics);
EVENT_TYPE_SETUP(PhysicsParam);
};
}
#endif

View File

@ -0,0 +1,8 @@
/*
ImGuiBuild.cpp
Here we link to the approprate ImGui backend implementaions.
If we want cases other than OpenGL, use switches on Metal,
DirectX, etc.
*/
#include "backends/imgui_impl_opengl3.cpp"
#include "backends/imgui_impl_glfw.cpp"

View File

@ -0,0 +1,8 @@
/*
ImGuiExtensions.cpp
Compile this to enable std library extensions for ImGui,
in particular using std::string with ImGui::InputText
GWM -- Feb 2022
*/
#include "misc/cpp/imgui_stdlib.cpp"

View File

@ -0,0 +1,135 @@
/*
ImGuiLayer.h
The layer containing all of the ImGui related setup and calls. Based on the work by @TheCherno in his game engine series.
Should always exist as an overlay in the Application LayerStack. Note that it currently is OpenGL specific based on
ImGui implementation/backends.
GWM -- Feb 2022
*/
#include "ImGuiLayer.h"
#include "Specter/Core/Application.h"
#include "Specter/Core/SpecCore.h"
#include "imgui.h"
#include "implot.h"
#include "backends/imgui_impl_opengl3.h"
#include "backends/imgui_impl_glfw.h"
#include <GLFW/glfw3.h>
#include <glad/glad.h>
#include "IconsFontAwesome5.h"
namespace Specter {
ImGuiLayer::ImGuiLayer() :
Layer("ImGuiLayer")
{
}
ImGuiLayer::~ImGuiLayer()
{
}
void ImGuiLayer::OnAttach()
{
SPEC_PROFILE_FUNCTION();
IMGUI_CHECKVERSION();
SPEC_INFO("Creating ImGui Context...");
ImGui::CreateContext();
ImPlot::CreateContext();
ImGuiIO& io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
//Viewports are real wonky on Linux and sometimes on MacOS
//Can currently cause assertion failure on checking number of monitors in ImGui sanity checks.
io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
ImGui::StyleColorsDark(); //Hacker mode
ImPlot::StyleColorsDark();
ImGuiStyle& style = ImGui::GetStyle();
if(io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
{
style.WindowRounding = 0.0f;
style.Colors[ImGuiCol_WindowBg].w = 1.0f;
}
//Setup our fonts. We have Roboto for text and FontAwesome for our icons.
//Note the .ttf files are found in NavProject, or in the bin dir. This is because
//the actual program (NavProject) is launched from either the bin/ ... /NaProject or the NavProject directory
//io.Fonts->AddFontDefault();
ImFontConfig latin_config;
latin_config.RasterizerMultiply = 1.3f;
ImFontConfig config;
config.MergeMode = true;
//config.GlyphMinAdvanceX = 13.0f; // Use if you want to make the icon monospaced
config.PixelSnapH = true;
static const ImWchar icon_ranges[] = { ICON_MIN_FA, ICON_MAX_FA, 0 };
io.Fonts->AddFontFromFileTTF("Assets/fonts/Roboto-Regular.ttf", 16.0f, &latin_config, io.Fonts->GetGlyphRangesDefault());
io.Fonts->AddFontFromFileTTF("Assets/fonts/fa-solid-900.ttf", 16.0f, &config, icon_ranges);
//ImPlot styling
ImPlot::GetStyle().FillAlpha = 0.75;
Application& app = Application::Get();
GLFWwindow* window = static_cast<GLFWwindow*>(app.GetWindow().GetNativeWindow());
ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui_ImplOpenGL3_Init("#version 410"); //GLSL version
SPEC_INFO("ImGui Finished initializing.");
}
void ImGuiLayer::OnDetach()
{
SPEC_PROFILE_FUNCTION();
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImPlot::DestroyContext();
ImGui::DestroyContext();
}
void ImGuiLayer::Begin()
{
SPEC_PROFILE_FUNCTION();
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
}
void ImGuiLayer::End()
{
SPEC_PROFILE_FUNCTION();
Application& app = Application::Get();
ImGuiIO& io = ImGui::GetIO();
io.DisplaySize = ImVec2((float)app.GetWindow().GetWidth(), (float)app.GetWindow().GetHeight());
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
if(io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
{
GLFWwindow* backup_current_context = glfwGetCurrentContext();
ImGui::UpdatePlatformWindows();
ImGui::RenderPlatformWindowsDefault();
glfwMakeContextCurrent(backup_current_context);
}
}
void ImGuiLayer::OnImGuiRender()
{
//Demo's used to figure out how to do things.
//Should not be on for actual NavProject for
//real use
//static bool show = true;
//ImGui::ShowDemoWindow(&show);
//ImPlot::ShowDemoWindow();
}
}

View File

@ -0,0 +1,36 @@
/*
ImGuiLayer.h
The layer containing all of the ImGui related setup and calls. Based on the work by @TheCherno in his game engine series.
Should always exist as an overlay in the Application LayerStack. Note that it currently is OpenGL specific based on
ImGui implementation/backends.
GWM -- Feb 2022
*/
#ifndef IMGUI_LAYER_H
#define IMGUI_LAYER_H
#include "Specter/Core/SpecCore.h"
#include "Specter/Core/Layer.h"
namespace Specter {
class ImGuiLayer : public Layer
{
public:
ImGuiLayer();
~ImGuiLayer();
virtual void OnAttach() override;
virtual void OnDetach() override;
virtual void OnImGuiRender() override;
void Begin();
void End();
private:
float m_time;
};
}
#endif

View File

@ -0,0 +1,35 @@
/*
AnalysisStack.cpp
Container for the analyses in the PhysicsLayer. Really just a specialized wrapper around std::vector.
GWM -- Feb 2022
*/
#include "AnalysisStack.h"
namespace Specter {
AnalysisStack::AnalysisStack() {}
AnalysisStack::~AnalysisStack()
{
for(AnalysisStage* stage : m_stack)
delete stage;
}
void AnalysisStack::PushStage(AnalysisStage* stage)
{
m_stack.emplace(m_stack.begin()+m_insertIndex, stage);
m_insertIndex++;
}
void AnalysisStack::PopStage(AnalysisStage* stage)
{
auto iter = std::find(m_stack.begin(), m_stack.end(), stage);
if(iter != m_stack.end())
{
m_stack.erase(iter);
m_insertIndex--;
}
}
}

View File

@ -0,0 +1,35 @@
/*
AnalysisStack.h
Container for the analyses in the PhysicsLayer. Really just a specialized wrapper around std::vector.
GWM -- Feb 2022
*/
#ifndef ANALYSIS_STACK_H
#define ANALYSIS_STACK_H
#include "AnalysisStage.h"
#include "Specter/Core/SpecCore.h"
namespace Specter {
class AnalysisStack
{
public:
AnalysisStack();
~AnalysisStack();
void PushStage(AnalysisStage* stage);
void PopStage(AnalysisStage* stage);
std::vector<AnalysisStage*>::iterator begin() { return m_stack.begin(); }
std::vector<AnalysisStage*>::iterator end() { return m_stack.end(); }
private:
std::vector<AnalysisStage*> m_stack; //The analysis stack owns the analysis stages
unsigned int m_insertIndex=0;
};
}
#endif

View File

@ -0,0 +1,19 @@
/*
AnalysisStage.h
Represents a step in a chain of analyses that are run on a NavEvent. These stages are where NavParameters are set and validated. Users should use this base class
to create their own analyses and inject them into their project. See template NavProject for an example of use.
GWM -- Feb 2022
*/
#include "AnalysisStage.h"
namespace Specter {
AnalysisStage::AnalysisStage(const std::string& name) :
m_name(name)
{
}
AnalysisStage::~AnalysisStage() {}
}

View File

@ -0,0 +1,32 @@
/*
AnalysisStage.h
Represents a step in a chain of analyses that are run on a NavEvent. These stages are where Parameters are set and validated. Users should use this base class
to create their own analyses and inject them into their project. See template NavProject for an example of use.
GWM -- Feb 2022
*/
#ifndef ANALYSIS_STAGE_H
#define ANALYSIS_STAGE_H
#include "Specter/Core/SpecCore.h"
#include "Specter/Core/Parameter.h"
#include "SpecData.h"
namespace Specter {
class AnalysisStage
{
public:
AnalysisStage(const std::string& name="AnalysisStage");
virtual ~AnalysisStage();
virtual void AnalyzePhysicsEvent(const SpecEvent& event) {};
inline std::string GetName() { return m_name; }
private:
std::string m_name;
};
}
#endif

View File

@ -0,0 +1,210 @@
/*
CompassFile.cpp
Wrapper class around a shared pointer to an ifstream. Here the shared pointer is used
to overcome limitations of the ifstream class, namely that it is written such that ifstream
cannot be modified by move semantics. Contains all information needed to parse a single binary
CompassFile. Currently has a class wide defined buffer size; may want to make this user input
in the future.
Written by G.W. McCann Oct. 2020
Modified for Specter; not really any significant changes. Just some simple changes, removal of unused data.
GWM -- Feb 2022
Update to reflect new CAEN binary data format with headers to indicate data contents.
GWM -- May 2022
*/
#include "CompassFile.h"
namespace Specter {
CompassFile::CompassFile() :
m_filename(""), m_bufferIter(nullptr), m_bufferEnd(nullptr), m_smap(nullptr), m_hitUsedFlag(true), m_file(std::make_shared<std::ifstream>()), m_eofFlag(false)
{
}
CompassFile::CompassFile(const std::string& filename) :
m_filename(""), m_bufferIter(nullptr), m_bufferEnd(nullptr), m_smap(nullptr), m_hitUsedFlag(true), m_file(std::make_shared<std::ifstream>()), m_eofFlag(false)
{
Open(filename);
}
CompassFile::CompassFile(const std::string& filename, int bsize) :
m_filename(""), m_bufferIter(nullptr), m_bufferEnd(nullptr), m_smap(nullptr), m_hitUsedFlag(true),
m_bufsize(bsize), m_file(std::make_shared<std::ifstream>()), m_eofFlag(false)
{
Open(filename);
}
CompassFile::~CompassFile()
{
Close();
}
void CompassFile::Open(const std::string& filename)
{
SPEC_PROFILE_FUNCTION();
m_eofFlag = false;
m_hitUsedFlag = true;
m_filename = filename;
m_file->open(m_filename, std::ios::binary | std::ios::in);
m_file->seekg(0, std::ios_base::end);
m_size = (unsigned int)m_file->tellg();
if(m_size == 0)
{
m_eofFlag = true;
}
else
{
m_file->seekg(0, std::ios_base::beg);
ReadHeader();
m_nHits = m_size / m_hitsize;
m_buffersize = m_hitsize * m_bufsize;
m_hitBuffer.resize(m_buffersize);
}
}
void CompassFile::Close()
{
if(IsOpen())
m_file->close();
}
void CompassFile::ReadHeader()
{
if(!IsOpen())
{
SPEC_WARN("Unable to get hit size from file {0}, sending invalid value.", m_filename);
return;
}
char* header = new char[2];
m_file->read(header, 2);
m_header = *((uint16_t*)header);
m_hitsize = 16; //default hitsize 16 bytes
if (Compass_IsEnergy(m_header))
m_hitsize += 2;
if (Compass_IsEnergyShort(m_header))
m_hitsize += 2;
if (Compass_IsEnergyCalibrated(m_header))
m_hitsize += 8;
if (Compass_IsWaves(m_header))
{
m_hitsize += 5;
char* firstHit = new char[m_hitsize]; //Read chunk of first hit
m_file->read(firstHit, m_hitsize);
firstHit += m_hitsize - 4; //Move to the Nsamples value
uint32_t nsamples = *((uint32_t*)firstHit);
m_hitsize += nsamples * 2; //Each sample is two bytes
m_file->seekg(0, std::ios_base::beg);
m_file->read(header, 2);
delete[] firstHit;
}
delete[] header;
}
/*
GetNextHit() is the function which... gets the next hit
Has to check if the buffer needs refilled/filled for the first time
Upon pulling a hit, sets the UsedFlag to false, letting the next level know
that the hit should be free game.
If the file cannot be opened, signals as though file is EOF
*/
bool CompassFile::GetNextHit()
{
SPEC_PROFILE_FUNCTION();
if(!IsOpen()) return true;
if((m_bufferIter == nullptr || m_bufferIter == m_bufferEnd) && !IsEOF())
{
GetNextBuffer();
}
if(!IsEOF())
{
ParseNextHit();
m_hitUsedFlag = false;
}
return m_eofFlag;
}
/*
GetNextBuffer() ... self-explanatory name
Note tht this is where the EOF flag is set. The EOF is only singaled
after the LAST buffer is completely read (i.e literally no more data). ifstream sets its eof
bit upon pulling the last buffer, but this class waits until that entire
last buffer is read to singal EOF (the true end of file).
*/
void CompassFile::GetNextBuffer()
{
SPEC_PROFILE_FUNCTION();
if(m_file->eof())
{
m_eofFlag = true;
return;
}
m_file->read(m_hitBuffer.data(), m_hitBuffer.size());
m_bufferIter = m_hitBuffer.data();
m_bufferEnd = m_bufferIter + m_file->gcount(); //one past the last datum
}
void CompassFile::ParseNextHit()
{
SPEC_PROFILE_FUNCTION();
m_currentHit.board = *((uint16_t*)m_bufferIter);
m_bufferIter += 2;
m_currentHit.channel = *((uint16_t*)m_bufferIter);
m_bufferIter += 2;
m_currentHit.timestamp = *((uint64_t*)m_bufferIter);
m_bufferIter += 8;
if (Compass_IsEnergy(m_header))
{
m_currentHit.energy = *((uint16_t*)m_bufferIter);
m_bufferIter += 2;
}
if (Compass_IsEnergyCalibrated(m_header))
{
m_currentHit.energyCalibrated = *((uint64_t*)m_bufferIter);
m_bufferIter += 8;
}
if (Compass_IsEnergyShort(m_header))
{
m_currentHit.energyShort = *((uint16_t*)m_bufferIter);
m_bufferIter += 2;
}
m_currentHit.flags = *((uint32_t*)m_bufferIter);
m_bufferIter += 4;
if (Compass_IsWaves(m_header))
{
m_currentHit.waveCode = *((uint8_t*)m_bufferIter);
m_bufferIter += 1;
m_currentHit.Ns = *((uint32_t*)m_bufferIter);
m_bufferIter += 4;
if (m_currentHit.samples.size() != m_currentHit.Ns)
m_currentHit.samples.resize(m_currentHit.Ns);
for (size_t i = 0; i < m_currentHit.samples.size(); i++)
{
m_currentHit.samples[i] = *((uint16_t*)m_bufferIter);
m_bufferIter += 2;
}
}
if(m_smap != nullptr)
{ //memory safety
int gchan = m_currentHit.channel + m_currentHit.board*16;
m_currentHit.timestamp += m_smap->GetShift(gchan);
}
}
}

View File

@ -0,0 +1,83 @@
/*
CompassFile.h
Wrapper class around a shared pointer to an ifstream. Here the shared pointer is used
to overcome limitations of the ifstream class, namely that it is written such that ifstream
cannot be modified by move semantics. Contains all information needed to parse a single binary
CompassFile. Currently has a class wide defined buffer size; may want to make this user input
in the future.
Written by G.W. McCann Oct. 2020
Modified for Specter; not really any significant changes. Just some simple changes, removal of unused data.
GWM -- Feb 2022
Update to reflect new CAEN binary data format with headers to indicate data contents.
GWM -- May 2022
*/
#ifndef COMPASSFILE_H
#define COMPASSFILE_H
#include "Specter/Core/SpecCore.h"
#include "CompassHit.h"
#include "Specter/Physics/ShiftMap.h"
namespace Specter {
class CompassFile
{
public:
CompassFile();
CompassFile(const std::string& filename);
CompassFile(const std::string& filename, int bsize);
~CompassFile();
void Open(const std::string& filename);
void Close();
bool GetNextHit();
inline bool IsOpen() const { return m_file->is_open(); };
inline CompassHit GetCurrentHit() const { return m_currentHit; }
inline std::string GetName() const { return m_filename; }
inline bool CheckHitHasBeenUsed() const { return m_hitUsedFlag; } //query to find out if we've used the current hit
inline void SetHitHasBeenUsed() { m_hitUsedFlag = true; } //flip the flag to indicate the current hit has been used
inline bool IsEOF() const { return m_eofFlag; } //see if we've read all available data
inline bool* GetUsedFlagPtr() { return &m_hitUsedFlag; }
inline void AttachShiftMap(ShiftMap* map) { m_smap = map; }
inline unsigned int GetSize() const { return m_size; }
inline unsigned int GetNumberOfHits() const { return m_nHits; }
private:
void ReadHeader();
void ParseNextHit();
void GetNextBuffer();
using Buffer = std::vector<char>;
using FilePointer = std::shared_ptr<std::ifstream>; //to make this class copy/movable
std::string m_filename;
Buffer m_hitBuffer;
char* m_bufferIter;
char* m_bufferEnd;
ShiftMap* m_smap; //NOT owned by CompassFile. DO NOT delete
bool m_hitUsedFlag;
int m_bufsize = 200000; //size of the buffer in hits
int m_hitsize; //size of a CompassHit in bytes (without alignment padding)
uint16_t m_header;
int m_buffersize;
CompassHit m_currentHit;
FilePointer m_file;
bool m_eofFlag;
unsigned int m_size; //size of the file in bytes
unsigned int m_nHits; //number of hits in the file (m_size/m_hitsize)
};
}
#endif

View File

@ -0,0 +1,24 @@
#include "CompassHit.h"
namespace Specter {
bool Compass_IsEnergy(uint16_t header)
{
return (header & CompassHeaders::Energy) != 0;
}
bool Compass_IsEnergyShort(uint16_t header)
{
return (header & CompassHeaders::EnergyShort) != 0;
}
bool Compass_IsEnergyCalibrated(uint16_t header)
{
return (header & CompassHeaders::EnergyCalibrated) != 0;
}
bool Compass_IsWaves(uint16_t header)
{
return (header & CompassHeaders::EnergyCalibrated) != 0;
}
}

View File

@ -0,0 +1,47 @@
/*
CompassHit.h
Simple struct representing data from the CAEN CoMPASS DAQ. Note here I do not have any of the non-standard data available (calibrated energy, waveform data, etc.)
GWM -- Feb 2022
Update to reflect new CAEN binary data format with headers to indicate data contents.
GWM -- May 2022
*/
#ifndef COMPASS_HIT_H
#define COMPASS_HIT_H
#include "Specter/Core/SpecCore.h"
namespace Specter {
struct CompassHit
{
uint16_t board = 0;
uint16_t channel = 0;
uint64_t timestamp = 0;
uint16_t energy = 0;
uint16_t energyShort = 0;
uint64_t energyCalibrated = 0;
uint32_t flags = 0;
uint8_t waveCode = 0;
uint32_t Ns = 0;
std::vector<uint16_t> samples;
};
//New to CoMPASS Data Format: Headers indicating what data is present.
enum CompassHeaders
{
Energy = 0x0001,
EnergyShort = 0x0002,
EnergyCalibrated = 0x0004,
Waves = 0x0008
};
bool Compass_IsEnergy(uint16_t header);
bool Compass_IsEnergyShort(uint16_t header);
bool Compass_IsEnergyCalibrated(uint16_t header);
bool Compass_IsWaves(uint16_t header);
}
#endif

View File

@ -0,0 +1,149 @@
/*
CompassOnlineSource.cpp
A data source for online CAEN CoMPASS Data. Uses TCPClient to connect to the CoMPASS server. Data is then converted
from the CAEN CoMPASS format to the native NavData format. Note that here we use syncrhonous since we
need to know if the buffer is/was filled, however we use non-blocking since we don't want the entire process to hang on attempting a connection or waiting
for data to come over the pipe. We handle the case of an un-filled buffer internally.
IMPORTANT
Specter wants a unqiue ID on each hit. To do this we use the idiom:
id = board_number * nchannels_per_board + channel_number
This requires two things: that the class variable m_nchannels_per_board be set to match your physical digitizers, and that ALL of your
digitizers have the SAME number of channels. By default CompassRun assumes 16 channels per board, as this is what is used with the SE-SPS setup at FoxLab.
If you use a different set of boards, CHANGE THIS VALUE! If you use mixed boards, you will need to invent a new id scheme altogether.
ADDITIONALLY
CoMPASS servers provide no stream side information on the state of a transfer (verified via communication w/ CAEN). That is: there are no headers or enders on the data transfers.
This forces us to use the size of a single CoMPASS datum (CompassHit) to determine the state of a transfer. If the read buffer size is not a whole multiple of CompassHits, the data
was determined to be fragmented. This has a huge drawback: in general there is no guarantee that the first byte of the transferred data is the first byte of a CompassHit. This means that
Specter MUST be connected to the CoMPASS server BEFORE starting the aquisition. Otherwise the program could endup in a state of scrambled unpacking (also verified w/ CAEN).
Maybe we can get them to change this? Headers reeaaally should exist for transfers like this.
GWM -- April 2022
*/
#include "CompassOnlineSource.h"
namespace Specter {
CompassOnlineSource::CompassOnlineSource(const std::string& hostname, const std::string& port, uint16_t header, int channels_per_board) :
DataSource(), m_bufferIter(nullptr), m_bufferEnd(nullptr), m_header(header), m_nchannels_per_board(channels_per_board)
{
InitConnection(hostname, port);
}
CompassOnlineSource::~CompassOnlineSource() {}
void CompassOnlineSource::InitConnection(const std::string& hostname, const std::string& port)
{
SPEC_PROFILE_FUNCTION();
m_datasize = 16; //base size of CoMPASS data
if (Compass_IsEnergy(m_header))
m_datasize += 2;
if (Compass_IsEnergyShort(m_header))
m_datasize += 2;
if (Compass_IsEnergyCalibrated(m_header))
m_datasize += 8;
if (Compass_IsWaves(m_header))
SPEC_ERROR("Specter does not support reading CoMPASS wave data for an online source!");
m_validFlag = false;
m_connection.Connect(hostname, port);
if (m_connection.IsOpen())
{
m_validFlag = true;
}
}
const SpecData& CompassOnlineSource::GetData()
{
SPEC_PROFILE_FUNCTION();
size_t range = m_bufferEnd - m_bufferIter; //how much buffer we have left
if (!IsValid())
{
SPEC_ERROR("Attempting to access invalid source at CompassOnlineSource!");
m_datum = SpecData();
return m_datum;
}
else if (m_bufferIter == nullptr || range < m_datasize || m_bufferIter == m_bufferEnd) //If no buffer/buffer completely used/buffer fragmented fill
{
FillBuffer();
}
if (m_bufferIter != m_bufferEnd && range >= m_datasize)//If buffer and enough data for a hit, get it
GetHit();
else
{
m_datum = SpecData();
return m_datum;
}
m_datum.longEnergy = m_currentHit.energy;
m_datum.shortEnergy = m_currentHit.energyShort;
m_datum.calEnergy = m_currentHit.energyCalibrated;
m_datum.timestamp = m_currentHit.timestamp;
m_datum.id = m_currentHit.board * m_nchannels_per_board + m_currentHit.channel;
return m_datum;
}
void CompassOnlineSource::FillBuffer()
{
SPEC_PROFILE_FUNCTION();
if (!m_connection.IsOpen()) //Make sure connection is still cool
{
m_validFlag = false;
return;
}
std::vector<char> recieved = m_connection.Read();
//If we didn't finish the last buffer toss all of the stuff we used and then append the recieved data
//Otherwise, copy over the recieved buffer. Note lack of vector::resize, vector::reserve. Intentional for performance.
//The amount of copying/resizing is best handled by the std (according to multiple references)
if (m_bufferIter != m_bufferEnd)
{
size_t pos = m_bufferEnd - m_bufferIter;
m_currentBuffer.erase(m_currentBuffer.begin(), m_currentBuffer.begin() + (m_currentBuffer.size() - pos)); //remove used bytes
m_currentBuffer.insert(m_currentBuffer.end(), recieved.begin(), recieved.end());
}
else
m_currentBuffer = recieved;
m_bufferIter = m_currentBuffer.data();
m_bufferEnd = m_currentBuffer.data() + m_currentBuffer.size();
}
void CompassOnlineSource::ReadHeader()
{
}
void CompassOnlineSource::GetHit()
{
SPEC_PROFILE_FUNCTION();
m_currentHit.board = *((uint16_t*)m_bufferIter);
m_bufferIter += 2;
m_currentHit.channel = *((uint16_t*)m_bufferIter);
m_bufferIter += 2;
m_currentHit.timestamp = *((uint64_t*)m_bufferIter);
m_bufferIter += 8;
if (Compass_IsEnergy(m_header))
{
m_currentHit.energy = *((uint16_t*)m_bufferIter);
m_bufferIter += 2;
}
if (Compass_IsEnergyCalibrated(m_header))
{
m_currentHit.energyCalibrated = *((uint16_t*)m_bufferIter);
m_bufferIter += 8;
}
if (Compass_IsEnergyShort(m_header))
{
m_currentHit.energyShort = *((uint16_t*)m_bufferIter);
m_bufferIter += 2;
}
m_currentHit.flags = *((uint32_t*)m_bufferIter);
m_bufferIter += 4;
}
}

View File

@ -0,0 +1,68 @@
/*
CompassOnlineSource.h
A data source for online CAEN CoMPASS Data. Uses TCPClient to connect to the CoMPASS server. Data is then converted
from the CAEN CoMPASS format to the native NavData format. Note that here we use syncrhonous since we
need to know if the buffer is/was filled, however we use non-blocking since we don't want the entire process to hang on attempting a connection or waiting
for data to come over the pipe. We handle the case of an un-filled buffer internally.
IMPORTANT
Specter wants a unqiue ID on each hit. To do this we use the idiom:
id = board_number * nchannels_per_board + channel_number
This requires two things: that the class variable m_nchannels_per_board be set to match your physical digitizers, and that ALL of your
digitizers have the SAME number of channels. By default CompassRun assumes 16 channels per board, as this is what is used with the SE-SPS setup at FoxLab.
If you use a different set of boards, CHANGE THIS VALUE! If you use mixed boards, you will need to invent a new id scheme altogether.
ADDITIONALLY
CoMPASS servers provide no stream side information on the state of a transfer (verified via communication w/ CAEN). That is: there are no headers or enders on the data transfers.
This forces us to use the size of a single CoMPASS datum (CompassHit) to determine the state of a transfer. If the read buffer size is not a whole multiple of CompassHits, the data
was determined to be fragmented. This has a huge drawback: in general there is no guarantee that the first byte of the transferred data is the first byte of a CompassHit. This means that
Specter MUST be connected to the CoMPASS server BEFORE starting the aquisition. Otherwise the program could endup in a state of scrambled unpacking (also verified w/ CAEN).
Maybe we can get them to change this? Headers reeaaally should exist for transfers like this.
GWM -- April 2022
Update to reflect new CAEN binary data format with headers to indicate data contents. Note that as prev. mentioned, no headers, so cannot rely on data stream to indicate state. State must be selected
by user at UI when creating source. Cannot support waves atm. No way to predict size of first event to calibrate the number of samples for the stream (or guarantee that they will be constant for duration
of Specter's runtime). Best to use the CoMPASSPlot for waves.
GWM -- May 2022
*/
#ifndef COMPASS_ONLINE_SOURCE_H
#define COMPASS_ONLINE_SOURCE_H
#include "Specter/Physics/DataSource.h"
#include "Specter/Utils/TCPClient.h"
#include "CompassHit.h"
namespace Specter {
class CompassOnlineSource : public DataSource
{
public:
CompassOnlineSource(const std::string& hostname, const std::string& port, uint16_t header, int channels_per_board=16);
virtual ~CompassOnlineSource() override;
virtual const SpecData& GetData() override;
private:
void InitConnection(const std::string& hostname, const std::string& port);
void FillBuffer();
void GetHit();
void ReadHeader();
std::vector<char> m_currentBuffer;
uint16_t m_header;
int m_datasize; //size of CoMPASS hit in bytes, set by header arg
const int m_nchannels_per_board = 16; //IMPORTANT: Used for ID'ing channels uniquely. If you use boards with 32 or 8 or 64 channels you must change this! If you mix boards with
//different numbers of channels, you will have to find a different id solution.
char* m_bufferIter;
char* m_bufferEnd;
CompassHit m_currentHit;
TCPClient m_connection;
};
}
#endif

View File

@ -0,0 +1,156 @@
/*
CompassRun.cpp
Class designed as abstraction of a collection of binary files that represent the total data in a single
Compass data run. It handles the user input (shift maps, file collection etc.) and creates a list of
CompassFiles from which to draw data. It then draws data from these files, organizes them in time,
and writes to a ROOT file for further processing.
Written by G.W. McCann Oct. 2020
Updated to also handle scaler data. -- GWM Oct. 2020
Modifed and updated for use in Specter. Obviously stripped out any ROOT code. Also, now uses the very nice std::filesystem
library to handle filepathing. Also, removed scalers (for now).
GWM -- Feb 2022
Update to reflect new CAEN binary data format with headers to indicate data contents.
GWM -- May 2022
*/
#include "CompassRun.h"
namespace Specter {
CompassRun::CompassRun() :
DataSource(), m_directory(""), m_startIndex(0), m_nchannels_per_board(16)
{
}
CompassRun::CompassRun(const std::string& dir, int channels_per_board) :
DataSource(), m_directory(dir), m_startIndex(0), m_nchannels_per_board(channels_per_board)
{
CollectFiles();
}
CompassRun::~CompassRun() {}
void CompassRun::CollectFiles()
{
SPEC_PROFILE_FUNCTION();
int nfiles=0;
for(auto& item : std::filesystem::directory_iterator(m_directory))
{
if(item.path().extension() == m_extension)
nfiles++;
}
m_datafiles.clear();
m_datafiles.reserve(nfiles);
for(auto& item : std::filesystem::directory_iterator(m_directory))
{
if(item.path().extension() == m_extension)
{
m_datafiles.emplace_back(item.path().string());
}
}
long total_hits=0;
for(auto& file : m_datafiles)
{
if(!file.IsOpen())
{
SPEC_ERROR("Unable to open file with name {0}", file.GetName());
m_validFlag = false;
return;
}
if(m_smap.IsValid())
file.AttachShiftMap(&m_smap);
total_hits += file.GetNumberOfHits();
}
if(m_datafiles.size() == 0)
{
SPEC_WARN("Unable to find any files with extension {0} in directory {1}. CompassRun killed.", m_extension, m_directory);
m_validFlag = false;
}
else
{
SPEC_INFO("Succesfully opened {0} files with {1} total hits", nfiles, total_hits);
m_validFlag = true;
}
}
/*
GetHitsFromFiles() is the function which actually retrieves and sorts the data from the individual
files. There are several tricks which allow this to happen. First is that, after sorting, it is impossible
to determine which file the data originally came from (short of parsing the name of the file against board/channel).
However, we need to let the file know that we want it to pull the next hit. To do this, a pointer to the UsedFlag of the file
is retrieved along with the data. This flag is flipped so that on the next hit cycle a new hit is pulled. Second is the use
of a rolling start index. Once a file has gone EOF, we no longer need it. If this is the first file in the list, we can just skip
that index all together. In this way, the loop can go from N times to N-1 times.
*/
bool CompassRun::GetHitsFromFiles()
{
SPEC_PROFILE_FUNCTION();
std::pair<CompassHit, bool*> earliestHit = std::make_pair(CompassHit(), nullptr);
for(unsigned int i=m_startIndex; i<m_datafiles.size(); i++)
{
if(m_datafiles[i].CheckHitHasBeenUsed())
m_datafiles[i].GetNextHit();
if(m_datafiles[i].IsEOF())
{
if(i == m_startIndex)
m_startIndex++;
continue;
}
else if(i == m_startIndex)
{
earliestHit = std::make_pair(m_datafiles[i].GetCurrentHit(), m_datafiles[i].GetUsedFlagPtr());
}
else if(m_datafiles[i].GetCurrentHit().timestamp < earliestHit.first.timestamp)
{
earliestHit = std::make_pair(m_datafiles[i].GetCurrentHit(), m_datafiles[i].GetUsedFlagPtr());
}
}
if(earliestHit.second == nullptr)
return false; //Make sure that there actually was a hit
m_hit = earliestHit.first;
*earliestHit.second = true;
return true;
}
const SpecData& CompassRun::GetData()
{
SPEC_PROFILE_FUNCTION();
if(!IsValid())
{
SPEC_ERROR("Trying to access CompassRun data when invalid, bug detected!");
m_datum = SpecData();
return m_datum;
}
if (!GetHitsFromFiles())
{
m_validFlag = false;
m_datum = SpecData();
}
else
{
//Convert data from CoMPASS format to universal Specter format.
m_datum.longEnergy = m_hit.energy;
m_datum.shortEnergy = m_hit.energyShort;
m_datum.calEnergy = m_hit.energyCalibrated;
m_datum.timestamp = m_hit.timestamp;
m_datum.id = m_hit.board * m_nchannels_per_board + m_hit.channel;
}
return m_datum;
}
}

View File

@ -0,0 +1,66 @@
/*
CompassRun.h
Class designed as abstraction of a collection of binary files that represent the total data in a single
Compass data run. It handles the user input (shift maps, file collection etc.) and creates a list of
CompassFiles from which to draw data. It then draws data from these files, organizes them in time,
and writes to a ROOT file for further processing.
Written by G.W. McCann Oct. 2020
Modifed and updated for use in Specter. Obviously stripped out any ROOT code. Also, now uses the very nice std::filesystem
library to handle filepathing. One change of great import: Specter wants a unqiue ID on each hit. To do this we use the idiom:
id = board_number * nchannels_per_board + channel_number
This requires two things: that the class variable m_nchannels_per_board be set to match your physical digitizers, and that ALL of your
digitizers have the SAME number of channels. By default CompassRun assumes 16 channels per board, as this is what is used with the SE-SPS setup at FoxLab.
If you use a different set of boards, CHANGE THIS VALUE! If you use mixed boards, you will need to invent a new id scheme altogether.
GWM -- Feb 2022
Update to reflect new CAEN binary data format with headers to indicate data contents.
GWM -- May 2022
*/
#ifndef COMPASSRUN_H
#define COMPASSRUN_H
#include "Specter/Core/SpecCore.h"
#include "Specter/Physics/DataSource.h"
#include "CompassFile.h"
#include "Specter/Physics/ShiftMap.h"
#include <filesystem>
namespace Specter {
class CompassRun : public DataSource
{
public:
CompassRun();
CompassRun(const std::string& dir, int channels_per_board=16);
virtual ~CompassRun();
virtual const SpecData& GetData() override;
inline void SetDirectory(const std::string& dir) { m_directory = dir; CollectFiles(); }
inline void SetShiftMap(const std::string& filename) { m_smap.SetFile(filename); }
private:
void CollectFiles();
bool GetHitsFromFiles();
std::filesystem::path m_directory;
const std::string m_extension = ".bin";
std::vector<CompassFile> m_datafiles;
unsigned int m_startIndex; //this is the file we start looking at; increases as we finish files.
int m_nchannels_per_board; //IMPORTANT: Used for ID'ing channels uniquely. If you use boards with 32 or 8 or 64 channels you must change this! If you mix boards with
//different numbers of channels, you will have to find a different id solution.
ShiftMap m_smap;
CompassHit m_hit;
unsigned int m_totalHits;
};
}
#endif

View File

@ -0,0 +1,39 @@
/*
DataSource.cpp
Abstract data source class. In Specter a DataSource can be either an online (live) source or an offline (file) source. By default,
Specter has classes to handle CAEN CoMPASS data sources (files and online); other sources may be implemented as more use cases for Specter become
apparent.
GWM -- Feb 2022
*/
#include "DataSource.h"
#include "Caen/CompassRun.h"
#include "Caen/CompassOnlineSource.h"
namespace Specter {
//loc=either an ip address or a file location, port=address port, or unused in case of file
DataSource* CreateDataSource(const std::string& location, const std::string& port, uint16_t header, int channels_per_board, DataSource::SourceType type)
{
switch(type)
{
case DataSource::SourceType::CompassOffline : return new CompassRun(location, channels_per_board);
case DataSource::SourceType::CompassOnline : return new CompassOnlineSource(location, port, header, channels_per_board);
case DataSource::SourceType::None : return nullptr;
}
SPEC_WARN("Invalid DataSourceType at CreateDataSource!");
return nullptr;
}
std::string ConvertDataSourceTypeToString(DataSource::SourceType type)
{
switch(type)
{
case DataSource::SourceType::None: return "None";
case DataSource::SourceType::CompassOnline : return "CompassOnline";
case DataSource::SourceType::CompassOffline : return "CompassOffline";
}
return "None";
}
}

View File

@ -0,0 +1,46 @@
/*
DataSource.h
Abstract data source class. In Specter a DataSource can be either an online (live) source or an offline (file) source. By default,
Specter has classes to handle CAEN CoMPASS data sources (files and online); other sources may be implemented as more use cases for Specter become
apparent.
GWM -- Feb 2022
*/
#ifndef DATA_SOURCE_H
#define DATA_SOURCE_H
#include "Specter/Core/SpecCore.h"
#include "SpecData.h"
namespace Specter {
class DataSource
{
public:
enum class SourceType //Need to know what kind of sources are available.
{
None,
CompassOnline,
CompassOffline
};
DataSource() :
m_validFlag(false)
{
}
virtual ~DataSource() {};
virtual const SpecData& GetData() = 0;
inline bool IsValid() { return m_validFlag; }
protected:
bool m_validFlag;
SpecData m_datum;
};
DataSource* CreateDataSource(const std::string& location, const std::string& port, uint16_t bitflags, int channels_per_board, DataSource::SourceType type);
std::string ConvertDataSourceTypeToString(DataSource::SourceType type);
}
#endif

View File

@ -0,0 +1,65 @@
/*
PhysicsEventBuilder.h
Class for taking in raw NavData and converting into a NavEvent. NavEvent is just a std::vector of NavData, where
the held NavData all falls within a time window called the coincidence window. As soon as a NavData is given that falls outside
of this window, the current event is shifted to the ready event and the AddDatumToEvent function returns true. The ready event can
then be retrieved. The hit that triggered the end of event then is used to start the new event. The current pattern is strongly associated
with digital electronics concepts for nuclear data aquisition systems.
GWM -- Feb 2022
*/
#include "PhysicsEventBuilder.h"
namespace Specter {
PhysicsEventBuilder::PhysicsEventBuilder(uint64_t windowSize) :
m_sortFlag(false), m_coincWindow(windowSize), m_bufferIndex(0)
{
}
PhysicsEventBuilder::~PhysicsEventBuilder()
{
}
bool PhysicsEventBuilder::AddDatum(const SpecData& datum)
{
SPEC_PROFILE_FUNCTION();
if (datum.timestamp == 0) //Ignore empty data (need a valid timestamp)
return false;
m_dataBuffer[m_bufferIndex] = datum;
m_bufferIndex++;
if (m_bufferIndex < s_maxDataBuffer) //If we haven't filled the buffer keep going
return false;
else if (m_sortFlag)
std::sort(m_dataBuffer.begin(), m_dataBuffer.end(), [](SpecData& i, SpecData& j) { return i.timestamp < j.timestamp; });
//Generate our ready events
m_readyEvents.clear();
uint64_t eventStartTime = m_dataBuffer[0].timestamp;
SpecEvent event;
event.push_back(m_dataBuffer[0]);
for (auto& data : m_dataBuffer)
{
if (data.timestamp - eventStartTime < m_coincWindow) //are we within active window
{
event.push_back(data);
}
else // found one that falls outside
{
m_readyEvents.push_back(event);
event.clear();
eventStartTime = data.timestamp;
event.push_back(data);
}
}
m_bufferIndex = 0;
return true;
}
const std::vector<SpecEvent>& PhysicsEventBuilder::GetReadyEvents() const
{
return m_readyEvents;
}
}

View File

@ -0,0 +1,45 @@
/*
PhysicsEventBuilder.h
Class for taking in raw NavData and converting into a NavEvent. NavEvent is just a std::vector of NavData, where
the held NavData all falls within a time window called the coincidence window. As soon as a NavData is given that falls outside
of this window, the current event is shifted to the ready event and the AddDatumToEvent function returns true. The ready event can
then be retrieved. The hit that triggered the end of event then is used to start the new event. The current pattern is strongly associated
with digital electronics concepts for nuclear data aquisition systems.
GWM -- Feb 2022
*/
#ifndef PHYSICS_EVENT_BUILDER_H
#define PHYSICS_EVENT_BUILDER_H
#include "SpecData.h"
namespace Specter {
class PhysicsEventBuilder
{
public:
PhysicsEventBuilder(uint64_t windowSize);
~PhysicsEventBuilder();
inline void SetCoincidenceWindow(uint64_t windowSize) { m_coincWindow = windowSize; }
inline void SetSortFlag(bool flag) { m_sortFlag = flag; }
inline void ClearAll() // reset all internal structures
{
m_bufferIndex = 0;
m_readyEvents.clear();
}
bool AddDatum(const SpecData& datum);
const std::vector<SpecEvent>& GetReadyEvents() const;
private:
bool m_sortFlag;
static constexpr int s_maxDataBuffer = 1000;
std::array<SpecData, s_maxDataBuffer> m_dataBuffer;
int m_bufferIndex;
std::vector<SpecEvent> m_readyEvents;
uint64_t m_coincWindow;
};
}
#endif

View File

@ -0,0 +1,188 @@
/*
PhysicsLayer.cpp
The layer of the application representing all physics. This includes the data source and the thread upon which data is piped, event built, and analyzed.
PhysicsLayer receives a PhysicsStartEvent or PhysicsStopEvent to handle changes to the state of the secondary thread. As it handles the thread, it must have synchronization.
This is handled on two levels. The first is a mutex that synchronizes access to the DataSource. The second is an atomic boolean flag which is used to control the state of the physics thread.
PhysicsLayer also owns the AnalysisStack for the application.
GWM -- Feb 2022
*/
#include "PhysicsLayer.h"
#include "Specter/Core/SpectrumManager.h"
#include "SpecData.h"
namespace Specter {
PhysicsLayer::PhysicsLayer() :
m_activeFlag(false), m_source(nullptr), m_eventBuilder(0), m_physThread(nullptr)
{
}
PhysicsLayer::~PhysicsLayer()
{
SPEC_PROFILE_FUNCTION();
if (m_activeFlag)
{
DetachDataSource();
DestroyPhysThread();
}
}
void PhysicsLayer::OnAttach()
{
/* For debugging
NavParameter par("joseph");
par.SetValue(8);
NAV_INFO("Does the par exist? {0}", ParameterMap::GetInstance().IsParameterValid("joseph"));
NAV_INFO("What is its value? {0}", ParameterMap::GetInstance().GetParameterValue("joseph"));
*/
}
void PhysicsLayer::OnDetach()
{
}
void PhysicsLayer::OnEvent(Event& event)
{
SPEC_PROFILE_FUNCTION();
EventDispatcher dispatch(event);
dispatch.Dispatch<PhysicsStartEvent>(BIND_EVENT_FUNCTION(PhysicsLayer::OnPhysicsStartEvent));
dispatch.Dispatch<PhysicsStopEvent>(BIND_EVENT_FUNCTION(PhysicsLayer::OnPhysicsStopEvent));
}
bool PhysicsLayer::OnPhysicsStartEvent(PhysicsStartEvent& event)
{
SPEC_PROFILE_FUNCTION();
//If the Physics thread is active, kill it
if(m_activeFlag)
{
DetachDataSource();
DestroyPhysThread();
}
AttachDataSource(event);
//If we succesfully attached, fire up a new phys thread
if(m_activeFlag)
{
SPEC_INFO("Starting new analysis thread...");
m_physThread = new std::thread(&PhysicsLayer::RunSource, std::ref(*this));
}
return true;
}
bool PhysicsLayer::OnPhysicsStopEvent(PhysicsStopEvent& event)
{
SPEC_PROFILE_FUNCTION();
if (m_activeFlag)
{
DetachDataSource();
DestroyPhysThread();
}
return true;
}
void PhysicsLayer::PushStage(AnalysisStage* stage)
{
m_physStack.PushStage(stage);
}
void PhysicsLayer::OnUpdate(Timestep& step) {}
/*Threaded functions*/
void PhysicsLayer::DestroyPhysThread()
{
SPEC_PROFILE_FUNCTION();
SPEC_INFO("Destroying the analysis thread...");
//Join the thread back to the parent (finish up the thread)
if(m_physThread != nullptr && m_physThread->joinable())
{
m_physThread->join();
}
//Free the thread memory
if(m_physThread != nullptr)
{
delete m_physThread;
m_physThread = nullptr;
}
SPEC_INFO("Thread destroyed.");
}
void PhysicsLayer::AttachDataSource(PhysicsStartEvent& event)
{
SPEC_PROFILE_FUNCTION();
std::scoped_lock<std::mutex> guard(m_sourceMutex); //Shouldn't matter for this, but safety first
m_source.reset(CreateDataSource(event.GetSourceLocation(), event.GetSourcePort(), event.GetBitFlags(), event.GetChannelsPerBoard(), event.GetSourceType()));
m_eventBuilder.SetCoincidenceWindow(event.GetCoincidenceWindow());
m_eventBuilder.SetSortFlag(event.GetSortFlag());
m_eventBuilder.ClearAll(); //Protect against stopping mid-event
if (m_source->IsValid())
{
SPEC_INFO("Attach successful. Enabling data pull...");
m_activeFlag = true;
}
else
{
SPEC_ERROR("DataSource attach failed... check for error conditions.");
m_source.reset(nullptr);
}
}
void PhysicsLayer::DetachDataSource()
{
SPEC_PROFILE_FUNCTION();
std::scoped_lock<std::mutex> guard(m_sourceMutex);
SPEC_INFO("Detaching physics data source...");
m_activeFlag = false;
m_source.reset(nullptr);
SPEC_INFO("Detach succesful.");
}
void PhysicsLayer::RunSource()
{
SPEC_PROFILE_FUNCTION();
SpectrumManager& manager = SpectrumManager::GetInstance();
std::vector<SpecEvent> events;
SpecData datum;
while(m_activeFlag)
{
//Scope to encapsulate access to the data source
{
std::scoped_lock<std::mutex> guard(m_sourceMutex);
if (m_source == nullptr || !m_source->IsValid())
{
return;
}
/*
Looks funny, but two conditions lead to !IsValid(). Either source prev. shutdown,
OR we reached end of source, indicated after prev. data grab
*/
datum = m_source->GetData();
if(!m_source->IsValid())
{
SPEC_INFO("End of data source.");
return;
}
}
//Pass data from source to event builder. True from AddDatum indicates events are ready
if (m_eventBuilder.AddDatum(datum))
{
events = m_eventBuilder.GetReadyEvents();
for (auto& event : events)
{
for (auto& stage : m_physStack)
stage->AnalyzePhysicsEvent(event);
//Now that the analysis stack has filled all our NavParameters with data, update the histogram counts
manager.UpdateHistograms();
//Invalidate all parameters to get ready for next event
manager.InvalidateParameters();
}
}
}
}
}

View File

@ -0,0 +1,64 @@
/*
PhysicsLayer.h
The layer of the application representing all physics. This includes the data source and the thread upon which data is piped, event built, and analyzed.
PhysicsLayer receives a PhysicsStartEvent or PhysicsStopEvent to handle changes to the state of the secondary thread. As it handles the thread, it must have synchronization.
This is handled on two levels. The first is a mutex that synchronizes access to the DataSource. The second is an atomic boolean flag which is used to control the state of the physics thread.
PhysicsLayer also owns the AnalysisStack for the application.
GWM -- Feb 2022
*/
#ifndef PHYSICS_LAYER_H
#define PHYSICS_LAYER_H
#include "Specter/Core/SpecCore.h"
#include "Specter/Core/Layer.h"
#include "Specter/Events/PhysicsEvent.h"
#include "AnalysisStack.h"
#include "AnalysisStage.h"
#include "DataSource.h"
#include "PhysicsEventBuilder.h"
#include <thread>
#include <mutex>
#include <atomic>
namespace Specter {
class PhysicsLayer : public Layer
{
public:
PhysicsLayer();
virtual ~PhysicsLayer();
virtual void OnAttach() override;
virtual void OnUpdate(Timestep& step) override;
virtual void OnDetach() override;
virtual void OnImGuiRender() override {};
virtual void OnEvent(Event& event) override;
bool OnPhysicsStartEvent(PhysicsStartEvent& event);
bool OnPhysicsStopEvent(PhysicsStopEvent& event);
void PushStage(AnalysisStage* stage);
private:
void DestroyPhysThread();
void AttachDataSource(PhysicsStartEvent& event);
void DetachDataSource();
void RunSource();
AnalysisStack m_physStack;
std::atomic<bool> m_activeFlag; //safe read/write across thread, but more expensive
std::mutex m_sourceMutex;
std::unique_ptr<DataSource> m_source;
PhysicsEventBuilder m_eventBuilder;
std::thread* m_physThread;
};
}
#endif

View File

@ -0,0 +1,85 @@
/*
ShiftMap.h
New class to act a go-between for timestamp shifts to channels. Takes in a
formated file containing data for shifts and then stores them in an unordered_map.
Key is a global compass channel (board#*16 + channel). Shifts in ps.
Written by G.W. McCann Oct. 2020
Not currently implemented for Specter though it could still be useful. Leave this here as a maybe upgrade path.
GWM -- Feb 2022
*/
#include "ShiftMap.h"
namespace Specter {
ShiftMap::ShiftMap() :
m_filename(""), m_validFlag(false)
{
}
ShiftMap::ShiftMap(const std::string& filename) :
m_filename(filename), m_validFlag(false)
{
ParseFile();
}
ShiftMap::~ShiftMap() {}
void ShiftMap::SetFile(const std::string& filename)
{
m_filename = filename;
ParseFile();
}
uint64_t ShiftMap::GetShift(int gchan)
{
if(!m_validFlag)
return 0;
auto iter = m_map.find(gchan);
if(iter == m_map.end())
return 0;
else
return iter->second;
}
void ShiftMap::ParseFile()
{
m_validFlag = false;
std::ifstream input(m_filename);
if(!input.is_open())
return;
int board, channel, gchan;
uint64_t shift;
std::string junk, temp;
std::getline(input, junk);
std::getline(input, junk);
while(input>>board)
{
input>>temp;
input>>shift;
if(temp == "all") //keyword to set all channels in this board to same shift
{
for(int i=0; i<16; i++)
{
gchan = board*16 + i;
m_map[gchan] = shift;
}
}
else
{
channel = stoi(temp);
gchan = channel + board*16;
m_map[gchan] = shift;
}
}
m_validFlag = true;
}
}

View File

@ -0,0 +1,43 @@
/*
ShiftMap.h
New class to act a go-between for timestamp shifts to channels. Takes in a
formated file containing data for shifts and then stores them in an unordered_map.
Key is a global compass channel (board#*16 + channel). Shifts in ps.
Written by G.W. McCann Oct. 2020
Not currently implemented for Specter though it could still be useful. Leave this here as a maybe upgrade path.
GWM -- Feb 2022
*/
#ifndef SHIFTMAP_H
#define SHIFTMAP_H
#include "Specter/Core/SpecCore.h"
namespace Specter {
class ShiftMap
{
public:
ShiftMap();
ShiftMap(const std::string& filename);
~ShiftMap();
void SetFile(const std::string& filename);
inline bool IsValid() { return m_validFlag; }
inline std::string GetFilename() { return m_filename; }
uint64_t GetShift(int gchan);
private:
void ParseFile();
std::string m_filename;
bool m_validFlag;
std::unordered_map<int, uint64_t> m_map;
};
}
#endif

View File

@ -0,0 +1,31 @@
/*
SpecData.h
Simple data structures for the event building process in Specter. Way to create uniform data from different sources. Note that the current paradigm
is very heavily tied to digital data aquisition systems. Any attempt to use Specter for analog systems would require a deep overhaul of the event processing.
Most likely link to something like nscldaq.
GWM -- Feb 2022
Update to reflect new CAEN binary data format with headers to indicate data contents.
GWM -- May 2022
*/
#ifndef SPECDATA_H
#define SPECDATA_H
namespace Specter {
struct SpecData
{
uint32_t longEnergy;
uint32_t shortEnergy;
uint64_t calEnergy;
uint64_t timestamp;
uint32_t id;
};
using SpecEvent = std::vector<SpecData>;
}
#endif

View File

@ -0,0 +1,22 @@
/*
GraphicsContext.h
Abstraction of graphical rendering context. Exists to allow Specter the felxibility to be backend api agnostic, though currently
only OpenGL is implemented. Entirely based upon the work done by @TheCherno in his game engine series. See his content for more details.
GWM -- Feb 2022
*/
#ifndef GRAPHICS_CONTEXT_H
#define GRAPHICS_CONTEXT_H
namespace Specter {
class GraphicsContext
{
public:
virtual void Init() = 0;
virtual void SwapBuffers() = 0;
};
}
#endif

View File

@ -0,0 +1,15 @@
/*
RenderCommand.cpp
Render call abstraction. Exists to allow Specter the felxibility to be backend api agnostic, though currently
only OpenGL is implemented. Entirely based upon the work done by @TheCherno in his game engine series. See his content for more details.
GWM -- Feb 2022
*/
#include "RenderCommand.h"
#include "Platform/OpenGL/OpenGLRendererAPI.h"
namespace Specter {
RendererAPI* RenderCommand::s_api = new OpenGLRendererAPI();
}

View File

@ -0,0 +1,29 @@
/*
RenderCommand.h
Render call abstraction. Exists to allow Specter the felxibility to be backend api agnostic, though currently
only OpenGL is implemented. Entirely based upon the work done by @TheCherno in his game engine series. See his content for more details.
GWM -- Feb 2022
*/
#ifndef RENDER_COMMAND_H
#define RENDER_COMMAND_H
#include "Specter/Core/SpecCore.h"
#include "RendererAPI.h"
namespace Specter {
class RenderCommand
{
public:
inline static void SetClearColor(const glm::vec4& color_array) { s_api->SetClearColor(color_array); }
inline static void Clear() { s_api->Clear(); }
inline static float GetFrameTime() { return s_api->GetFrameTime(); }
private:
static RendererAPI* s_api;
};
}
#endif

View File

@ -0,0 +1,14 @@
/*
RenderAPI.h
Render API abstraction. Exists to allow Specter the felxibility to be backend api agnostic, though currently
only OpenGL is implemented. Entirely based upon the work done by @TheCherno in his game engine series. See his content for more details.
GWM -- Feb 2022
*/
#include "RendererAPI.h"
namespace Specter {
RendererAPI::API RendererAPI::s_api = RendererAPI::API::OpenGL;
}

View File

@ -0,0 +1,37 @@
/*
RenderAPI.h
Render API abstraction. Exists to allow Specter the felxibility to be backend api agnostic, though currently
only OpenGL is implemented. Entirely based upon the work done by @TheCherno in his game engine series. See his content for more details.
GWM -- Feb 2022
*/
#ifndef RENDERER_API_H
#define RENDERER_API_H
#include "Specter/Core/SpecCore.h"
#include "glm/vec4.hpp"
namespace Specter {
class RendererAPI
{
public:
enum class API
{
None = 0,
OpenGL = 1 //Currently only have OpenGL
};
virtual void Clear() = 0;
virtual void SetClearColor(const glm::vec4& color) = 0;
virtual float GetFrameTime() = 0;
inline static API GetAPI() { return s_api; }
private:
static API s_api;
};
}
#endif

View File

@ -0,0 +1,260 @@
/*
Instrumentor.h
Instrumentation classes for generating JSON trace files for use in Google Chrome's tracer. Based entirely upon the work done by @TheCherno in his game engine series. See his Hazel
repository for more details.
To activate the profiling, switch the SPEC_PROFILE macro to a value greater than 0. By default the instrumentaion is disabled (duh). Should only be used for development cases.
GWM -- May 2022
*/
#ifndef INSTRUMENTOR_H
#define INSTRUMENTOR_H
#include <algorithm>
#include <chrono>
#include <fstream>
#include <iomanip>
#include <string>
#include <thread>
#include <mutex>
#include <sstream>
#include "Specter/Core/Logger.h"
#include "Timer.h"
namespace Specter {
using FloatingPointMicroseconds = std::chrono::duration<double, std::micro>;
struct ProfileResult
{
std::string name;
FloatingPointMicroseconds start;
std::chrono::microseconds elapsedTime;
std::thread::id threadID;
};
struct InstrumentationSession
{
std::string name;
};
class Instrumentor
{
public:
Instrumentor(const Instrumentor&) = delete;
Instrumentor(Instrumentor&&) = delete;
void BeginSession(const std::string& name, const std::string filename="inst_results.json")
{
std::scoped_lock<std::mutex> guard(m_instMutex);
if (m_session)
{
/*
* Can only ever have one session rolling. If for whatever reason attempt to open second, handle gracefully
*/
if (Logger::GetLogger()) //Ensure logger exists
{
SPEC_ERROR("Attempting to create new instr. session ({0}) while session ({1}) is running! Setting to session {0}", name, m_session->name);
}
InternalEndSession();
}
m_outputFile.open(filename);
if (m_outputFile.is_open())
{
m_session = new InstrumentationSession({ name });
WriteHeader();
}
else if(Logger::GetLogger())
{
SPEC_ERROR("Unable to open instr. output file named {0}!", filename);
}
}
void EndSession()
{
std::scoped_lock<std::mutex> guard(m_instMutex);
InternalEndSession();
}
void WriteProfile(const ProfileResult& result)
{
std::scoped_lock<std::mutex> guard(m_instMutex);
std::stringstream json_string;
json_string << std::setprecision(3) << std::fixed;
json_string << ",{";
json_string << "\"cat\":\"function\",";
json_string << "\"dur\":" << (result.elapsedTime.count()) << ',';
json_string << "\"name\":\"" << result.name << "\",";
json_string << "\"ph\":\"X\",";
json_string << "\"pid\":0,";
json_string << "\"tid\":" << result.threadID << ",";
json_string << "\"ts\":" << result.start.count();
json_string << "}";
if (m_session)
{
m_outputFile << json_string.str();
m_outputFile.flush();
}
}
static Instrumentor& Get()
{
static Instrumentor s_instance;
return s_instance;
}
private:
Instrumentor() :
m_session(nullptr)
{
}
~Instrumentor()
{
EndSession();
}
void InternalEndSession()
{
if (m_session)
{
WriteFooter();
m_outputFile.close();
delete m_session;
m_session = nullptr;
}
}
void WriteHeader()
{
m_outputFile << "{\"otherData\": {},\"traceEvents\":[{}";
m_outputFile.flush();
}
void WriteFooter()
{
m_outputFile << "]}";
m_outputFile.flush();
}
std::mutex m_instMutex;
InstrumentationSession* m_session;
std::ofstream m_outputFile;
};
class InstrumentationTimer
{
public:
InstrumentationTimer(const char* name) :
m_name(name), m_stopped(false)
{
m_startTime = Clock::now();
}
~InstrumentationTimer()
{
if (!m_stopped)
Stop();
}
void Stop()
{
auto stopTime = Clock::now();
FloatingPointMicroseconds start = FloatingPointMicroseconds((m_startTime).time_since_epoch());
auto elapsedTime = std::chrono::time_point_cast<std::chrono::microseconds>(stopTime).time_since_epoch() - std::chrono::time_point_cast<std::chrono::microseconds>(m_startTime).time_since_epoch();
m_stopped = true;
Instrumentor::Get().WriteProfile({ m_name, start, elapsedTime, std::this_thread::get_id() });
}
private:
using Time = std::chrono::steady_clock::time_point;
using Clock = std::chrono::steady_clock;
const char* m_name;
Time m_startTime;
bool m_stopped;
};
namespace InstrumentorUtils {
template<size_t N>
struct ChangeResult
{
char data[N];
};
template<size_t N, size_t M>
constexpr auto CleanupOutputString(const char(&expr)[N], const char(&remove)[M])
{
ChangeResult<N> result = {};
size_t srcIndex = 0;
size_t destIndex = 0;
while (srcIndex < N)
{
size_t matchIndex = 0;
while (matchIndex < M - 1 && srcIndex + matchIndex < N - 1 && expr[srcIndex + matchIndex] == remove[matchIndex])
matchIndex++;
if (matchIndex == M - 1)
srcIndex += matchIndex;
result.data[destIndex++] = expr[srcIndex] == '"' ? '\'' : expr[srcIndex];
srcIndex++;
}
return result;
}
}
}
//Macros for ease of use
#define SPEC_PROFILE 0
#if SPEC_PROFILE
//Attempt to resolve function signature with precompiler
#if defined(__GNUC__) || (defined(__MWERKS__) && (__MWERKS__ >= 0x3000)) || (defined(__ICC) && (__ICC >= 600)) || defined(__ghs__)
#define SPEC_FUNC_SIG __PRETTY_FUNCTION__
#elif defined(__DMC__) && (__DMC__ >= 0x810)
#define SPEC_FUNC_SIG __PRETTY_FUNCTION__
#elif (defined(__FUNCSIG__) || (_MSC_VER))
#define SPEC_FUNC_SIG __FUNCSIG__
#elif (defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 600)) || (defined(__IBMCPP__) && (__IBMCPP__ >= 500))
#define SPEC_FUNC_SIG __FUNCTION__
#elif defined(__BORLANDC__) && (__BORLANDC__ >= 0x550)
#define SPEC_FUNC_SIG __FUNC__
#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901)
#define SPEC_FUNC_SIG __func__
#elif defined(__cplusplus) && (__cplusplus >= 201103)
#define SPEC_FUNC_SIG __func__
#else
#define SPEC_FUNC_SIG "SPEC_FUNC_SIG unknown!"
#endif
#define SPEC_PROFILE_BEGIN_SESSION(name, filename) ::Specter::Instrumentor::Get().BeginSession(name, filename);
#define SPEC_PROFILE_END_SESSION() ::Specter::Instrumentor::Get().EndSession();
#define SPEC_PROFILE_SCOPE_LINE2(name, line) constexpr auto fixedName##line = ::Specter::InstrumentorUtils::CleanupOutputString(name, "__cdecl ");\
::Specter::InstrumentationTimer timer##line(fixedName##line.data)
#define SPEC_PROFILE_SCOPE_LINE(name, line) SPEC_PROFILE_SCOPE_LINE2(name, line)
#define SPEC_PROFILE_SCOPE(name) SPEC_PROFILE_SCOPE_LINE(name, __LINE__)
#define SPEC_PROFILE_FUNCTION() SPEC_PROFILE_SCOPE(SPEC_FUNC_SIG)
#else
#define SPEC_PROFILE_BEGIN_SESSION(name, filename)
#define SPEC_PROFILE_END_SESSION()
#define SPEC_PROFILE_SCOPE_LINE2(name, line)
#define SPEC_PROFILE_SCOPE_LINE(name, line)
#define SPEC_PROFILE_SCOPE(name)
#define SPEC_PROFILE_FUNCTION()
#endif
#endif

View File

@ -0,0 +1,85 @@
/*
TCPClient.h
Specter's TCP client rep using asio. Contains very basic ability to read and write from a tcp socket in a non-blocking, synchronous method.
See asio docs for more details and examples on how to use.
GWM -- April 2022
Note: the write functionality has not been verified. Should be fine, but test before using.
*/
#include "TCPClient.h"
namespace Specter {
TCPClient::TCPClient() :
m_socket(m_context)
{
m_readBuffer.resize(s_readBufferSize);
}
TCPClient::TCPClient(const std::string& host, const std::string& port) :
m_socket(m_context)
{
m_readBuffer.resize(s_readBufferSize);
Connect(host, port);
}
TCPClient::~TCPClient()
{
Close();
}
void TCPClient::Connect(const std::string& host, const std::string& port)
{
try
{
asio::ip::tcp::resolver resolver(m_context);
auto end_points = resolver.resolve(host, port);
asio::connect(m_socket, end_points);
m_socket.non_blocking(true); //Set the connection as non-blocking
}
catch (asio::system_error& e)
{
SPEC_ERROR("Unable to connect to remote port for TCPClient! Error Code: {0}", e.what());
}
}
std::vector<char> TCPClient::Read()
{
asio::error_code code;
size_t length = m_socket.read_some(asio::buffer(m_readBuffer, m_readBuffer.size()), code);
if (code == asio::error::eof)
{
SPEC_WARN("Server has closed connection. Closing the TCPClient");
Close();
}
else if (code && code != asio::error::would_block)
{
SPEC_ERROR("TCPClient::Read recieved unexpected error from host. Error message: {0}", code.message());
SPEC_WARN("Closing the socket.");
Close();
}
return std::vector<char>(m_readBuffer.begin(), m_readBuffer.begin()+length);
}
//untested, not currently used.
size_t TCPClient::Write(const std::vector<char>& data)
{
asio::error_code code;
m_writeBuffer = data;
size_t length = m_socket.write_some(asio::buffer(m_writeBuffer, m_writeBuffer.size()), code);
if (code == asio::error::eof)
{
SPEC_WARN("Server has closed connection. Closing the TCPClient.");
Close();
}
else if(code && code != asio::error::would_block)
{
SPEC_ERROR("TCPClient::Write recieved unexpected error from host. Error message: {0}", code.message());
SPEC_WARN("Closing the socket");
Close();
}
return length;
}
}

View File

@ -0,0 +1,41 @@
/*
TCPClient.h
Specter's TCP client rep using asio. Contains very basic ability to read and write from a tcp socket in a non-blocking, synchronous method.
See asio docs for more details and examples on how to use.
GWM -- April 2022
Note: the write functionality has not been verified. Should be fine, but test before using.
*/
#ifndef TCPCLIENT_H
#define TCPCLIENT_H
#include <asio.hpp>
namespace Specter {
class TCPClient
{
public:
TCPClient();
TCPClient(const std::string& host, const std::string& port);
~TCPClient();
void Connect(const std::string& host, const std::string& port);
std::vector<char> Read();
size_t Write(const std::vector<char>& data);
inline void Close() { if(IsOpen()) m_socket.close(); }
inline bool IsOpen() { return m_socket.is_open(); }
private:
std::vector<char> m_readBuffer;
std::vector<char> m_writeBuffer;
static constexpr size_t s_readBufferSize = 24000; // reserve some space for the read buffer
asio::io_context m_context;
asio::ip::tcp::socket m_socket;
};
}
#endif

View File

@ -0,0 +1,323 @@
/*
TestServerLayer.h
This set of classes represents a test layer containing a TCP server and client connection class. This is only to be used as a way to debug
online data sources. You can have this server post data to a port on a localhost and then have the analysis thread connect to that port and analyze the
imitated data. Default setup is for an OnlineCompassSource, but of course this could be easily modified for your test of choice. For more details,
see the asio Tutorials, specifically the asynchronous daytime server.
Here we use async methods, as we do not want the whole project to be hanging on creating a succesfull client/server connection. (Also cause it was fun
to learn)
GWM -- April 2022
NOTE: There are NO security features on this server/connection. Please use with care on a protected network/firewalled machine.
*/
#include "TestServerLayer.h"
namespace Specter {
/* TCPConnection */
TCPConnection::TCPConnection(asio::io_context& context) :
m_socket(context), m_buffer(36)
{
}
//This function is kinda misnamed in our scheme. Should be Write to make more clear.
void TCPConnection::Start()
{
CreateBinaryBuffer(); //Generate Buffer
//CreateBinaryBufferFragmented(); //Generate fragmented buffer
//Actually write the buffer to the socket. Use std::bind to set a callback function for error handling or any other server side actions
asio::async_write(m_socket, asio::buffer(m_buffer, m_buffer.size()),
std::bind(&TCPConnection::HandleWrite, this, std::placeholders::_1, std::placeholders::_2));
}
//Server-side connection actions upon attempting write, only on for debugging
void TCPConnection::HandleWrite(const asio::error_code& ec, size_t bytes)
{
/*
static int i = 0;
if(!ec)
SPEC_INFO("Number written: {0}", ++i);
*/
//SPEC_INFO("Writer result: Asio Error -- {0} Amount transferred={1}", ec.message(), bytes);
}
/*
Create C-style binary buffer from the data struct. This is to mimic the raw data source,
which will have no padding, or any other normal struct related features (and note that the intrisic
ordering from compass ensures that padding would be placed)
*/
void TCPConnection::CreateBinaryBuffer()
{
m_hit.board = 8;
m_hit.channel = 1;
m_hit.timestamp = m_hit.timestamp + s_timestep;
m_hit.energy = 512;
m_hit.energyShort = 0;
m_hit.flags = 0;
m_hit.Ns = 0;
char* data_pointer;
int buffer_position = 0;
data_pointer = (char*)&m_hit.board;
for (int i = 0; i < 2; i++)
{
m_buffer[buffer_position] = *(data_pointer + i);
buffer_position++;
}
data_pointer = (char*)&m_hit.channel;
for (int i = 0; i < 2; i++)
{
m_buffer[buffer_position] = *(data_pointer + i);
buffer_position++;
}
data_pointer = (char*)&m_hit.timestamp;
for (int i = 0; i < 8; i++)
{
m_buffer[buffer_position] = *(data_pointer + i);
buffer_position++;
}
data_pointer = (char*)&m_hit.energy;
for (int i = 0; i < 2; i++)
{
m_buffer[buffer_position] = *(data_pointer + i);
buffer_position++;
}
data_pointer = (char*)&m_hit.energyShort;
for (int i = 0; i < 2; i++)
{
m_buffer[buffer_position] = *(data_pointer + i);
buffer_position++;
}
data_pointer = (char*)&m_hit.flags;
for (int i = 0; i < 4; i++)
{
m_buffer[buffer_position] = *(data_pointer + i);
buffer_position++;
}
data_pointer = (char*)&m_hit.Ns;
for (int i = 0; i < 4; i++)
{
m_buffer[buffer_position] = *(data_pointer + i);
buffer_position++;
}
}
/*
Create C-style binary buffer from the data struct. This is to mimic the raw data source,
which will have no padding, or any other normal struct related features (and note that the intrisic
ordering from compass ensures that padding would be placed). Here we also fragment a hit (split over 2 buffers)
to test ability to handle real life conditions
Note -- as implemented highlights one issue with CAEN CoMPASS sources. No headers are left in the stream,
so one must be CERTAIN to attach Specter to the socket before running, otherwise fragments could lead to
scrambled unpacking order. (i.e. sometimes this will work for the test and sometimes it won't)
*/
void TCPConnection::CreateBinaryBufferFragmented()
{
static std::atomic<bool> even = true;
m_hit.board = 8;
m_hit.channel = 1;
m_hit.timestamp = m_hit.timestamp + s_timestep;
m_hit.energy = 512;
m_hit.energyShort = 0;
m_hit.flags = 0;
m_hit.Ns = 0;
char* data_pointer;
int buffer_position = 0;
if (even)
{
data_pointer = (char*)&m_hit.board;
for (int i = 0; i < 2; i++)
{
m_buffer[buffer_position] = *(data_pointer + i);
buffer_position++;
}
data_pointer = (char*)&m_hit.channel;
for (int i = 0; i < 2; i++)
{
m_buffer[buffer_position] = *(data_pointer + i);
buffer_position++;
}
data_pointer = (char*)&m_hit.timestamp;
for (int i = 0; i < 8; i++)
{
m_buffer[buffer_position] = *(data_pointer + i);
buffer_position++;
}
data_pointer = (char*)&m_hit.energy;
for (int i = 0; i < 2; i++)
{
m_buffer[buffer_position] = *(data_pointer + i);
buffer_position++;
}
data_pointer = (char*)&m_hit.energyShort;
for (int i = 0; i < 2; i++)
{
m_buffer[buffer_position] = *(data_pointer + i);
buffer_position++;
}
data_pointer = (char*)&m_hit.flags;
for (int i = 0; i < 4; i++)
{
m_buffer[buffer_position] = *(data_pointer + i);
buffer_position++;
}
data_pointer = (char*)&m_hit.board;
for (int i = 0; i < 2; i++)
{
m_buffer[buffer_position] = *(data_pointer + i);
buffer_position++;
}
data_pointer = (char*)&m_hit.channel;
for (int i = 0; i < 2; i++)
{
m_buffer[buffer_position] = *(data_pointer + i);
buffer_position++;
}
m_hit.timestamp += s_timestep;
data_pointer = (char*)&m_hit.timestamp;
for (int i = 0; i < 8; i++)
{
m_buffer[buffer_position] = *(data_pointer + i);
buffer_position++;
}
even = false;
}
else
{
data_pointer = (char*)&m_hit.energy;
for (int i = 0; i < 2; i++)
{
m_buffer[buffer_position] = *(data_pointer + i);
buffer_position++;
}
data_pointer = (char*)&m_hit.energyShort;
for (int i = 0; i < 2; i++)
{
m_buffer[buffer_position] = *(data_pointer + i);
buffer_position++;
}
data_pointer = (char*)&m_hit.flags;
for (int i = 0; i < 4; i++)
{
m_buffer[buffer_position] = *(data_pointer + i);
buffer_position++;
}
data_pointer = (char*)&m_hit.board;
for (int i = 0; i < 2; i++)
{
m_buffer[buffer_position] = *(data_pointer + i);
buffer_position++;
}
data_pointer = (char*)&m_hit.channel;
for (int i = 0; i < 2; i++)
{
m_buffer[buffer_position] = *(data_pointer + i);
buffer_position++;
}
data_pointer = (char*)&m_hit.timestamp;
for (int i = 0; i < 8; i++)
{
m_buffer[buffer_position] = *(data_pointer + i);
buffer_position++;
}
data_pointer = (char*)&m_hit.energy;
for (int i = 0; i < 2; i++)
{
m_buffer[buffer_position] = *(data_pointer + i);
buffer_position++;
}
data_pointer = (char*)&m_hit.energyShort;
for (int i = 0; i < 2; i++)
{
m_buffer[buffer_position] = *(data_pointer + i);
buffer_position++;
}
data_pointer = (char*)&m_hit.flags;
for (int i = 0; i < 4; i++)
{
m_buffer[buffer_position] = *(data_pointer + i);
buffer_position++;
}
}
}
/* TCPServer */
TCPServer::TCPServer(asio::io_context& context) :
m_contextRef(context), m_acceptor(context, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), 51489)), m_isAccepted(false)
{
}
//Attempt to create a connection
std::shared_ptr<TCPConnection> TCPServer::StartAccept()
{
std::shared_ptr<TCPConnection> new_connection = TCPConnection::CreateConnection(m_contextRef);
m_acceptor.async_accept(new_connection->Socket(), std::bind(&TCPServer::HandleAccept, this, new_connection, std::placeholders::_1));
return new_connection;
}
//If connection successful, attempt to write as a test.
void TCPServer::HandleAccept(std::shared_ptr<TCPConnection> connection, const asio::error_code& ec)
{
if (!ec)
{
m_isAccepted = true;
connection->Start();
}
else
{
m_isAccepted = false;
SPEC_INFO("TCPServer HandleAccept found Error: {0}", ec.message());
}
StartAccept();
}
/* TestServerLayer */
TestServerLayer::TestServerLayer() :
Layer("TestServer"), m_server(nullptr), m_connection(nullptr)
{
}
TestServerLayer::~TestServerLayer()
{
if (m_server)
delete m_server;
}
//Create a server, get the connection, poll actions
void TestServerLayer::OnAttach()
{
try
{
m_server = new TCPServer(m_context);
m_connection = m_server->StartAccept();
m_context.poll();
}
catch (std::exception& e)
{
SPEC_INFO("ServerLayer Error: {0}", e.what());
}
}
void TestServerLayer::OnDetach()
{
}
//Tell to write, then poll actions
void TestServerLayer::OnUpdate(Timestep& step)
{
m_connection->Start();
m_context.poll();
}
}

View File

@ -0,0 +1,78 @@
/*
TestServerLayer.h
This set of classes represents a test layer containing a TCP server and client connection class. This is only to be used as a way to debug
online data sources. You can have this server post data to a port on a localhost and then have the analysis thread connect to that port and analyze the
imitated data. Default setup is for an OnlineCompassSource, but of course this could be easily modified for your test of choice. For more details,
see the asio Tutorials, specifically the asynchronous daytime server.
Here we use async methods, as we do not want the whole project to be hanging on creating a succesfull client/server connection. (Also cause it was fun
to learn)
GWM -- April 2022
NOTE: There are NO security features on this server/connection. Please use with care on a protected network/firewalled machine.
*/
#ifndef TEST_SERVER_LAYER_H
#define TEST_SERVER_LAYER_H
#include "Specter/Core/Layer.h"
#include "Specter/Physics/Caen/CompassHit.h"
#include "asio.hpp"
namespace Specter {
/*Server-side TCP Connection to the open port*/
class TCPConnection
{
public:
TCPConnection(asio::io_context& context);
inline static std::shared_ptr<TCPConnection> CreateConnection(asio::io_context& context) { return std::make_shared<TCPConnection>(context); }
inline asio::ip::tcp::socket& Socket() { return m_socket; }
void Start();
private:
void HandleWrite(const asio::error_code& ec, size_t bytes);
void CreateBinaryBuffer();
void CreateBinaryBufferFragmented();
asio::ip::tcp::socket m_socket;
std::vector<char> m_buffer;
CompassHit m_hit;
static constexpr uint64_t s_timestep = 2000000;
};
/*Server itself*/
class TCPServer
{
public:
TCPServer(asio::io_context& context);
inline bool IsAccepted() { return m_isAccepted; }
std::shared_ptr<TCPConnection> StartAccept();
private:
void HandleAccept(std::shared_ptr<TCPConnection> connection, const asio::error_code& error);
asio::io_context& m_contextRef;
asio::ip::tcp::acceptor m_acceptor;
bool m_isAccepted;
};
class TestServerLayer : public Layer
{
public:
TestServerLayer();
virtual ~TestServerLayer();
virtual void OnAttach() override;
virtual void OnDetach() override;
virtual void OnUpdate(Timestep& step) override;
private:
asio::io_context m_context;
TCPServer* m_server;
std::shared_ptr<TCPConnection> m_connection;
};
}
#endif

View File

@ -0,0 +1,46 @@
#ifndef TIMER_H
#define TIMER_H
#include <chrono>
namespace Specter {
class Timer
{
public:
Timer(const char* name) :
m_name(name), m_stopped(false)
{
m_startTime = Clock::now();
}
~Timer()
{
if (!m_stopped)
Stop();
}
void Stop()
{
auto stopTime = Clock::now();
int64_t start = std::chrono::time_point_cast<std::chrono::microseconds>(m_startTime).time_since_epoch().count();
int64_t stop = std::chrono::time_point_cast<std::chrono::microseconds>(stopTime).time_since_epoch().count();
float duration = (stop - start)*0.001f;
m_stopped = true;
SPEC_INFO("{1} -- Duration: {0} ms", m_name, duration);
}
private:
using Time = std::chrono::steady_clock::time_point;
using Clock = std::chrono::steady_clock;
const char* m_name;
Time m_startTime;
bool m_stopped;
};
}
#endif

1
Specter/src/specpch.cpp Normal file
View File

@ -0,0 +1 @@
#include "specpch.h"

22
Specter/src/specpch.h Normal file
View File

@ -0,0 +1,22 @@
#ifndef SPECPCH_H
#define SPECPCH_H
#include <iostream>
#include <fstream>
#include <memory>
#include <utility>
#include <algorithm>
#include <functional>
#include <string>
#include <sstream>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <cstdint>
#include "Specter/Core/Logger.h"
#include "Specter/Utils/Instrumentor.h"
#endif