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:
parent
a4c731f100
commit
a5a2b4a786
2500
SpecProject/Assets/amdc2016_mass.txt
Normal file
2500
SpecProject/Assets/amdc2016_mass.txt
Normal file
File diff suppressed because it is too large
Load Diff
34
SpecProject/Assets/fonts/LICENSE_FontAwesome.txt
Normal file
34
SpecProject/Assets/fonts/LICENSE_FontAwesome.txt
Normal 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.**
|
202
SpecProject/Assets/fonts/LICENSE_Roboto.txt
Normal file
202
SpecProject/Assets/fonts/LICENSE_Roboto.txt
Normal 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.
|
BIN
SpecProject/Assets/fonts/Roboto-Regular.ttf
Normal file
BIN
SpecProject/Assets/fonts/Roboto-Regular.ttf
Normal file
Binary file not shown.
BIN
SpecProject/Assets/fonts/fa-regular-400.ttf
Normal file
BIN
SpecProject/Assets/fonts/fa-regular-400.ttf
Normal file
Binary file not shown.
BIN
SpecProject/Assets/fonts/fa-solid-900.ttf
Normal file
BIN
SpecProject/Assets/fonts/fa-solid-900.ttf
Normal file
Binary file not shown.
69
SpecProject/src/MassMap.cpp
Normal file
69
SpecProject/src/MassMap.cpp
Normal 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
31
SpecProject/src/MassMap.h
Normal 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
|
135
SpecProject/src/SPSAnalysisStage.cpp
Normal file
135
SpecProject/src/SPSAnalysisStage.cpp
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
41
SpecProject/src/SPSAnalysisStage.h
Normal file
41
SpecProject/src/SPSAnalysisStage.h
Normal 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
140
SpecProject/src/SPSInputLayer.cpp
Normal file
140
SpecProject/src/SPSInputLayer.cpp
Normal 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()));
|
||||||
|
}
|
||||||
|
}
|
68
SpecProject/src/SPSInputLayer.h
Normal file
68
SpecProject/src/SPSInputLayer.h
Normal 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
44
SpecProject/src/main.cpp
Normal 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();
|
||||||
|
}
|
41
Specter/src/Platform/OpenGL/OpenGLContext.cpp
Normal file
41
Specter/src/Platform/OpenGL/OpenGLContext.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
31
Specter/src/Platform/OpenGL/OpenGLContext.h
Normal file
31
Specter/src/Platform/OpenGL/OpenGLContext.h
Normal 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
|
29
Specter/src/Platform/OpenGL/OpenGLRendererAPI.cpp
Normal file
29
Specter/src/Platform/OpenGL/OpenGLRendererAPI.cpp
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
26
Specter/src/Platform/OpenGL/OpenGLRendererAPI.h
Normal file
26
Specter/src/Platform/OpenGL/OpenGLRendererAPI.h
Normal 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
|
180
Specter/src/Platform/OpenGL/OpenGLWindow.cpp
Normal file
180
Specter/src/Platform/OpenGL/OpenGLWindow.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
56
Specter/src/Platform/OpenGL/OpenGLWindow.h
Normal file
56
Specter/src/Platform/OpenGL/OpenGLWindow.h
Normal 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
38
Specter/src/Specter.h
Normal 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
|
105
Specter/src/Specter/Core/Application.cpp
Normal file
105
Specter/src/Specter/Core/Application.cpp
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
68
Specter/src/Specter/Core/Application.h
Normal file
68
Specter/src/Specter/Core/Application.h
Normal 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
|
133
Specter/src/Specter/Core/Cut.cpp
Normal file
133
Specter/src/Specter/Core/Cut.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
126
Specter/src/Specter/Core/Cut.h
Normal file
126
Specter/src/Specter/Core/Cut.h
Normal 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
|
42
Specter/src/Specter/Core/Graph.cpp
Normal file
42
Specter/src/Specter/Core/Graph.cpp
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
39
Specter/src/Specter/Core/Graph.h
Normal file
39
Specter/src/Specter/Core/Graph.h
Normal 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
|
421
Specter/src/Specter/Core/Histogram.cpp
Normal file
421
Specter/src/Specter/Core/Histogram.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
184
Specter/src/Specter/Core/Histogram.h
Normal file
184
Specter/src/Specter/Core/Histogram.h
Normal 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
|
17
Specter/src/Specter/Core/Layer.cpp
Normal file
17
Specter/src/Specter/Core/Layer.cpp
Normal 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() {}
|
||||||
|
}
|
36
Specter/src/Specter/Core/Layer.h
Normal file
36
Specter/src/Specter/Core/Layer.h
Normal 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
|
49
Specter/src/Specter/Core/LayerStack.cpp
Normal file
49
Specter/src/Specter/Core/LayerStack.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
39
Specter/src/Specter/Core/LayerStack.h
Normal file
39
Specter/src/Specter/Core/LayerStack.h
Normal 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
|
26
Specter/src/Specter/Core/Logger.cpp
Normal file
26
Specter/src/Specter/Core/Logger.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
42
Specter/src/Specter/Core/Logger.h
Normal file
42
Specter/src/Specter/Core/Logger.h
Normal 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
|
96
Specter/src/Specter/Core/Parameter.cpp
Normal file
96
Specter/src/Specter/Core/Parameter.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
106
Specter/src/Specter/Core/Parameter.h
Normal file
106
Specter/src/Specter/Core/Parameter.h
Normal 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
|
26
Specter/src/Specter/Core/SpecCore.h
Normal file
26
Specter/src/Specter/Core/SpecCore.h
Normal 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
|
631
Specter/src/Specter/Core/SpectrumManager.cpp
Normal file
631
Specter/src/Specter/Core/SpectrumManager.cpp
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
126
Specter/src/Specter/Core/SpectrumManager.h
Normal file
126
Specter/src/Specter/Core/SpectrumManager.h
Normal 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
|
455
Specter/src/Specter/Core/SpectrumSerializer.cpp
Normal file
455
Specter/src/Specter/Core/SpectrumSerializer.cpp
Normal 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
39
Specter/src/Specter/Core/SpectrumSerializer.h
Normal file
39
Specter/src/Specter/Core/SpectrumSerializer.h
Normal 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
|
25
Specter/src/Specter/Core/Timestep.h
Normal file
25
Specter/src/Specter/Core/Timestep.h
Normal 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
|
53
Specter/src/Specter/Core/Window.h
Normal file
53
Specter/src/Specter/Core/Window.h
Normal 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
|
416
Specter/src/Specter/Editor/EditorLayer.cpp
Normal file
416
Specter/src/Specter/Editor/EditorLayer.cpp
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
81
Specter/src/Specter/Editor/EditorLayer.h
Normal file
81
Specter/src/Specter/Editor/EditorLayer.h
Normal 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
|
272
Specter/src/Specter/Editor/FileDialog.cpp
Normal file
272
Specter/src/Specter/Editor/FileDialog.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
58
Specter/src/Specter/Editor/FileDialog.h
Normal file
58
Specter/src/Specter/Editor/FileDialog.h
Normal 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
|
121
Specter/src/Specter/Editor/SourceDialog.cpp
Normal file
121
Specter/src/Specter/Editor/SourceDialog.cpp
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
38
Specter/src/Specter/Editor/SourceDialog.h
Normal file
38
Specter/src/Specter/Editor/SourceDialog.h
Normal 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
|
280
Specter/src/Specter/Editor/SpectrumDialog.cpp
Normal file
280
Specter/src/Specter/Editor/SpectrumDialog.cpp
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
45
Specter/src/Specter/Editor/SpectrumDialog.h
Normal file
45
Specter/src/Specter/Editor/SpectrumDialog.h
Normal 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
|
393
Specter/src/Specter/Editor/SpectrumPanel.cpp
Normal file
393
Specter/src/Specter/Editor/SpectrumPanel.cpp
Normal 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), ®ion.region.X.Min, ®ion.region.Y.Min, ®ion.region.X.Max, ®ion.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), ®ion.region.X.Min, ®ion.region.Y.Min, ®ion.region.X.Max, ®ion.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
63
Specter/src/Specter/Editor/SpectrumPanel.h
Normal file
63
Specter/src/Specter/Editor/SpectrumPanel.h
Normal 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
|
59
Specter/src/Specter/Events/AppEvent.h
Normal file
59
Specter/src/Specter/Events/AppEvent.h
Normal 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
|
89
Specter/src/Specter/Events/Event.h
Normal file
89
Specter/src/Specter/Events/Event.h
Normal 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
|
95
Specter/src/Specter/Events/KeyEvent.h
Normal file
95
Specter/src/Specter/Events/KeyEvent.h
Normal 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
|
109
Specter/src/Specter/Events/MouseEvent.h
Normal file
109
Specter/src/Specter/Events/MouseEvent.h
Normal 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
|
85
Specter/src/Specter/Events/PhysicsEvent.h
Normal file
85
Specter/src/Specter/Events/PhysicsEvent.h
Normal 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
|
8
Specter/src/Specter/ImGui/ImGuiBuild.cpp
Normal file
8
Specter/src/Specter/ImGui/ImGuiBuild.cpp
Normal 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"
|
8
Specter/src/Specter/ImGui/ImGuiExtensions.cpp
Normal file
8
Specter/src/Specter/ImGui/ImGuiExtensions.cpp
Normal 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"
|
135
Specter/src/Specter/ImGui/ImGuiLayer.cpp
Normal file
135
Specter/src/Specter/ImGui/ImGuiLayer.cpp
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
36
Specter/src/Specter/ImGui/ImGuiLayer.h
Normal file
36
Specter/src/Specter/ImGui/ImGuiLayer.h
Normal 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
|
35
Specter/src/Specter/Physics/AnalysisStack.cpp
Normal file
35
Specter/src/Specter/Physics/AnalysisStack.cpp
Normal 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--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
35
Specter/src/Specter/Physics/AnalysisStack.h
Normal file
35
Specter/src/Specter/Physics/AnalysisStack.h
Normal 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
|
19
Specter/src/Specter/Physics/AnalysisStage.cpp
Normal file
19
Specter/src/Specter/Physics/AnalysisStage.cpp
Normal 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() {}
|
||||||
|
|
||||||
|
}
|
32
Specter/src/Specter/Physics/AnalysisStage.h
Normal file
32
Specter/src/Specter/Physics/AnalysisStage.h
Normal 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
|
210
Specter/src/Specter/Physics/Caen/CompassFile.cpp
Normal file
210
Specter/src/Specter/Physics/Caen/CompassFile.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
83
Specter/src/Specter/Physics/Caen/CompassFile.h
Normal file
83
Specter/src/Specter/Physics/Caen/CompassFile.h
Normal 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
|
24
Specter/src/Specter/Physics/Caen/CompassHit.cpp
Normal file
24
Specter/src/Specter/Physics/Caen/CompassHit.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
47
Specter/src/Specter/Physics/Caen/CompassHit.h
Normal file
47
Specter/src/Specter/Physics/Caen/CompassHit.h
Normal 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
|
149
Specter/src/Specter/Physics/Caen/CompassOnlineSource.cpp
Normal file
149
Specter/src/Specter/Physics/Caen/CompassOnlineSource.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
68
Specter/src/Specter/Physics/Caen/CompassOnlineSource.h
Normal file
68
Specter/src/Specter/Physics/Caen/CompassOnlineSource.h
Normal 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
|
156
Specter/src/Specter/Physics/Caen/CompassRun.cpp
Normal file
156
Specter/src/Specter/Physics/Caen/CompassRun.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
66
Specter/src/Specter/Physics/Caen/CompassRun.h
Normal file
66
Specter/src/Specter/Physics/Caen/CompassRun.h
Normal 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
|
39
Specter/src/Specter/Physics/DataSource.cpp
Normal file
39
Specter/src/Specter/Physics/DataSource.cpp
Normal 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";
|
||||||
|
}
|
||||||
|
}
|
46
Specter/src/Specter/Physics/DataSource.h
Normal file
46
Specter/src/Specter/Physics/DataSource.h
Normal 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
|
65
Specter/src/Specter/Physics/PhysicsEventBuilder.cpp
Normal file
65
Specter/src/Specter/Physics/PhysicsEventBuilder.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
45
Specter/src/Specter/Physics/PhysicsEventBuilder.h
Normal file
45
Specter/src/Specter/Physics/PhysicsEventBuilder.h
Normal 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
|
188
Specter/src/Specter/Physics/PhysicsLayer.cpp
Normal file
188
Specter/src/Specter/Physics/PhysicsLayer.cpp
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
64
Specter/src/Specter/Physics/PhysicsLayer.h
Normal file
64
Specter/src/Specter/Physics/PhysicsLayer.h
Normal 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
|
85
Specter/src/Specter/Physics/ShiftMap.cpp
Normal file
85
Specter/src/Specter/Physics/ShiftMap.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
43
Specter/src/Specter/Physics/ShiftMap.h
Normal file
43
Specter/src/Specter/Physics/ShiftMap.h
Normal 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
|
31
Specter/src/Specter/Physics/SpecData.h
Normal file
31
Specter/src/Specter/Physics/SpecData.h
Normal 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
|
22
Specter/src/Specter/Renderer/GraphicsContext.h
Normal file
22
Specter/src/Specter/Renderer/GraphicsContext.h
Normal 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
|
15
Specter/src/Specter/Renderer/RenderCommand.cpp
Normal file
15
Specter/src/Specter/Renderer/RenderCommand.cpp
Normal 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();
|
||||||
|
|
||||||
|
}
|
29
Specter/src/Specter/Renderer/RenderCommand.h
Normal file
29
Specter/src/Specter/Renderer/RenderCommand.h
Normal 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
|
14
Specter/src/Specter/Renderer/RendererAPI.cpp
Normal file
14
Specter/src/Specter/Renderer/RendererAPI.cpp
Normal 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;
|
||||||
|
|
||||||
|
}
|
37
Specter/src/Specter/Renderer/RendererAPI.h
Normal file
37
Specter/src/Specter/Renderer/RendererAPI.h
Normal 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
|
260
Specter/src/Specter/Utils/Instrumentor.h
Normal file
260
Specter/src/Specter/Utils/Instrumentor.h
Normal 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
|
85
Specter/src/Specter/Utils/TCPClient.cpp
Normal file
85
Specter/src/Specter/Utils/TCPClient.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
41
Specter/src/Specter/Utils/TCPClient.h
Normal file
41
Specter/src/Specter/Utils/TCPClient.h
Normal 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
|
323
Specter/src/Specter/Utils/TestServerLayer.cpp
Normal file
323
Specter/src/Specter/Utils/TestServerLayer.cpp
Normal 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
78
Specter/src/Specter/Utils/TestServerLayer.h
Normal file
78
Specter/src/Specter/Utils/TestServerLayer.h
Normal 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
|
46
Specter/src/Specter/Utils/Timer.h
Normal file
46
Specter/src/Specter/Utils/Timer.h
Normal 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
1
Specter/src/specpch.cpp
Normal file
|
@ -0,0 +1 @@
|
||||||
|
#include "specpch.h"
|
22
Specter/src/specpch.h
Normal file
22
Specter/src/specpch.h
Normal 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
|
Loading…
Reference in New Issue
Block a user