Commit fd2eefc9 authored by Vratislav Chudoba's avatar Vratislav Chudoba

New architecture of libriaries to be used for callibration.

parent 08178659
/*
* AculCalParameters.cpp
*
* Created on: Oct 20, 2016
* Author: vratik
*/
#include "AculCalPars.h"
AculCalPars::AculCalPars() {
// TODO Auto-generated constructor stub
Reset();
}
AculCalPars::AculCalPars(const char* parFile) {
// TODO Auto-generated constructor stub
SetParFile(parFile);
Init();
}
AculCalPars::~AculCalPars() {
// TODO Auto-generated destructor stub
}
void AculCalPars::Init() {
SetPars();
}
void AculCalPars::SetParFile(const char* parfile) {
fParFileName = parfile;
return;
}
/*
* AculCalParameters.h
*
* Created on: Oct 20, 2016
* Author: vratik
*/
#ifndef ACULCALIB_ACULCALPARS_H_
#define ACULCALIB_ACULCALPARS_H_
#include <iostream>
#include <fstream>
#include <vector>
//#include "TObject.h"
#include "TString.h"
#include "TArrayD.h"
#include "TArrayI.h"
//todo ommit this constant
#define NOCALFILES 5
using std::cout;
using std::endl;
using std::vector;
class AculCalPars {
protected:
//general
TString fParFileName;
//CsI parameters
// Int_t fNoCrystals;
//
// TString fDetName;
//
// Int_t fNoFiles;
// vector<TString> fFileName;
//
// Int_t fEnergyPoints;
// TArrayD fE;
//
//Si parameteres
//first
//second
//others
public:
AculCalPars();
AculCalPars(const char* parFile);
virtual ~AculCalPars();
ClassDef(AculCalPars,1);
virtual void Init();
//getters
const char* GetParFileName() {return fParFileName.Data();}
// Int_t GetNoCrystals() {return fNoCrystals;}
// const char* GetDetName() {return fDetName.Data();}
// const char* GetParticleName() {return fPartName.Data();}
virtual Int_t GetNoRawFiles() {return 0;};
virtual const char* GetFileName(Int_t i) {return 0;};
// const char* GetCutName(Int_t i);
// Int_t GetNoEPoints() {return fEnergyPoints;}
// Double_t GetCalEnergy(Int_t i);
virtual const char* GetCutsFileName() {return 0;}
Int_t GetNoCuts() {return 0;}
// Int_t GetMinChannel(Int_t energy, Int_t crystal);
// Int_t GetMaxChannel(Int_t energy, Int_t crystal);
void SetParFileName(const char* parFile) {fParFileName = parFile;}
void SetParFile(const char* parfile);
virtual void PrintParameters(const char* option = "") {};
virtual void Reset() {};
protected:
virtual void SetPars() {};
};
#endif /* ACULCALIB_ACULCALPARS_H_ */
/*
* AculCalParsScint.cpp
*
* Created on: Oct 21, 2016
* Author: vratik
*/
#include "AculCalParsScint.h"
#include "TFile.h"
AculCalParsScint::AculCalParsScint() {
// TODO Auto-generated constructor stub
Reset();
}
AculCalParsScint::AculCalParsScint(const char* parFile) {
// TODO Auto-generated constructor stub
SetParFile(parFile);
Init();
}
AculCalParsScint::~AculCalParsScint() {
// TODO Auto-generated destructor stub
}
void AculCalParsScint::Init() {
SetPars();
LoadCuts();
}
void AculCalParsScint::SetPars() {
std::ifstream infile(fParFileName.Data());
if ( !infile.is_open() ) {
printf("AculCalParsScint::ReadParFile: File %s was not open and parameters were not set.\n", fParFileName.Data());
Reset();
return;
}
fEnergyPoints = 0;
TString line;
TString word;
Int_t i, min, max;
Int_t lineLength = 400;
Char_t det[lineLength];
Char_t part[lineLength];
Char_t fname[lineLength];
Char_t cname[lineLength];
double en; //energy
while (!infile.eof()) {
line.ReadLine(infile);
if ( line.BeginsWith("#") || line.BeginsWith("//") ) continue;
if ( line.BeginsWith("energies") ) {
sscanf(line.Data(), "%*s %d", &i);
continue;
}
if ( line.BeginsWith("crystals") ) {
sscanf(line.Data(), "%*s %d", &i);
fNoCrystals = i;
continue;
}
if ( line.BeginsWith("files") ) {
sscanf(line.Data(), "%*s %d", &i);
fNoFiles = i;
for (Int_t j = 0; j < fNoFiles; j++) {
line.ReadLine(infile);
sscanf(line, "%s", fname);
word = fname;
fFileName.push_back(fname);
}
continue;
}
if ( line.BeginsWith("cutFile") ) {
sscanf(line.Data(), "%*s %s %d", fname, &fNoCuts);
fCutsFileName = fname;
for (Int_t j = 0; j < fNoCuts; j++) {
line.ReadLine(infile);
sscanf(line, "%s", cname);
fCutName.push_back(cname);
}
continue;
}
if ( line.BeginsWith("detector") ) {
sscanf(line.Data(), "%*s %s %s", det, part);
fDetName = det;
fPartName = part;
continue;
}
if ( line.BeginsWith("energy") ) {
sscanf(line.Data(), "%*s %lf", &en);
fE.push_back(en);
fEnergyPoints++;
vector<Int_t> newM;
fPeakMin.push_back(newM);
fPeakMax.push_back(newM);
continue;
}
sscanf(line.Data(), "%d %d %d", &i, &min, &max);
fPeakMin.at(fEnergyPoints-1).push_back(min);
fPeakMax.at(fEnergyPoints-1).push_back(max);
}//while
infile.close();
return;
}
void AculCalParsScint::PrintParameters(const char* option) {
TString opt = option;
cout << "Parameters read from file \"" << fParFileName.Data() << "\"." << endl;
cout << "\tCalibration of detector \"" << fDetName << "\" with " << fNoCrystals << " crystals." << endl;
// cout << "\tNumber of crystals: " << fNoCrystals << endl;
cout << "\tParticle: " << fPartName << endl;
cout << "\tInput files with raw data (" << fNoFiles << "):" << endl;
for (Int_t i = 0; i < (Int_t)fFileName.size(); i++) {
// cout << i << endl;
cout << "\t " << fFileName[i] << endl;
}
cout << "\tInput energies (" << fNoFiles << "):" << endl;
for (Int_t i = 0; i < (Int_t)fE.size(); i++) {
// cout << i << endl;
cout << "\t " << fE[i] << " MeV" << endl;
}
cout << "\tInput file with " << fNoCuts << " cuts: \"" << fCutsFileName << "\"" << endl;
if (opt.Contains("all")) {
for (Int_t i = 0; i < (Int_t)fCutName.size(); i++) {
// cout << i << endl;
cout << "\t cut: \"" << fCutName[i] << "\"" << endl;
}
}
if (!opt.Contains("all")) return;
cout << "\tPeak limits for particular channels and energies:" << endl;
for (Int_t k = 0; k < (Int_t)fPeakMin.size(); k++) {
cout << "\t Set number: " << k << "; energy: " << fE[k] << " MeV" << endl;
for (Int_t i = 0; i < (Int_t)fPeakMin[k].size(); i++) {
// cout << i << endl;
cout << "\t\t " << fPeakMin[k][i] << "\t" << fPeakMax[k][i] << endl;
}
}
return;
}
void AculCalParsScint::Reset() {
fParFileName = "";
fDetName = "";
fPartName = "";
fNoFiles = 0;
fEnergyPoints = 0;
fE.clear();
fCutsFileName = "";
fNoCuts = 0; //number of cuts
fFileName.clear();
fCutName.clear();
fPeakMin.clear();
fPeakMax.clear();
return;
}
const char* AculCalParsScint::GetFileName(Int_t i) {
if ( i > (Int_t)fFileName.size()-1 ) {
cerr << "\"AculCalParsScint::GetFileName\" index i cannot be higher than " << fFileName.size() - 1 << endl;
return 0;
}
return fFileName[i].Data();
}
const char* AculCalParsScint::GetCutName(Int_t i) {
if ( i > (Int_t)fCutName.size()-1 ) {
cerr << "\"AculCalParsScint::GetCutName\" index i cannot be higher than " << fCutName.size() - 1 << endl;
return 0;
}
return fCutName[i].Data();
}
Double_t AculCalParsScint::GetCalEnergy(Int_t i) {
if ( i > (Int_t)fE.size()-1 ) {
cerr << "\"AculCalParsScint::GetCalEnergy\" index i cannot be higher than " << fE.size() - 1 << endl;
return 0;
}
return fE[i];
}
Int_t AculCalParsScint::GetMinChannel(Int_t energy, Int_t crystal) {
if ( energy > (Int_t)fPeakMin.size()-1 ) {
cerr << "\"AculCalParsScint::GetMinChannel\" index \"energy\" cannot be higher than " << fPeakMin.size() - 1 << endl;
return 0;
}
vector<Int_t> v = fPeakMin[energy];
if ( crystal > (Int_t)v.size()-1 ) {
cerr << "\"AculCalParsScint::GetMinChannel\" index \"crystal\" cannot be higher than " << v.size() - 1 << endl;
return 0;
}
return fPeakMin[energy][crystal];
}
Int_t AculCalParsScint::GetMaxChannel(Int_t energy, Int_t crystal) {
if ( energy > (Int_t)fPeakMax.size()-1 ) {
cerr << "\"AculCalParsScint::GetMinChannel\" index \"energy\" cannot be higher than " << fPeakMax.size() - 1 << endl;
return 0;
}
vector<Int_t> v = fPeakMax[energy];
if ( crystal > (Int_t)v.size()-1 ) {
cerr << "\"AculCalParsScint::GetMinChannel\" index \"crystal\" cannot be higher than " << v.size() - 1 << endl;
return 0;
}
return fPeakMax[energy][crystal];
}
void AculCalParsScint::LoadCuts() {
if (fCutsFileName.Length() == 0) {
printf("AculCalParsScint::LoadCuts: Name of file (*.root) with cuts was not provided.\n");
printf("AculCalParsScint::LoadCuts: No cuts has been loaded.\n");
return;
}
TFile cutFile(fCutsFileName.Data(), "READ");
if(!cutFile.IsOpen()) {
cerr << "\"AculCalParsScint::LoadCuts\" File " << fCutsFileName.Data()
<< " was not open and no cuts were loaded." << endl;
return;
}
for (Int_t i = 0; i < (Int_t)fCutName.size(); i++) {
TCutG *currentCut = (TCutG*)cutFile.Get(fCutName[i]);
if (currentCut) {
fCuts.push_back(*currentCut);
}
}
}
/*
* AculCalParsScint.h
*
* Created on: Oct 21, 2016
* Author: vratik
*/
#ifndef ACULCALIB_ACULCALPARSSCINT_H_
#define ACULCALIB_ACULCALPARSSCINT_H_
#include "AculCalPars.h"
#include "TCutG.h"
using std::cerr;
class AculCalParsScint: public AculCalPars {
private:
Int_t fNoCrystals;
TString fDetName;
TString fPartName;
Int_t fNoFiles;
vector<TString> fFileName;
vector<TString> fCutName;
vector<TCutG> fCuts;
Int_t fEnergyPoints;
vector<Double_t> fE;
TString fCutsFileName;
Int_t fNoCuts; //number of cuts
vector< vector<Int_t> > fPeakMin;
vector< vector<Int_t> > fPeakMax;
public:
AculCalParsScint();
AculCalParsScint(const char* parFile);
virtual ~AculCalParsScint();
ClassDef(AculCalParsScint, 1);
virtual void Init();
void PrintParameters(const char* option = "");
// If option contains "all", all parameters will be printed out.
// By default important parameters only will be printed out.
void Reset();
//getters
Int_t GetNoCrystals() {return fNoCrystals;}
const char* GetDetName() {return fDetName.Data();}
const char* GetParticleName() {return fPartName.Data();}
Int_t GetNoRawFiles() {return fNoFiles;}
const char* GetFileName(Int_t i);
const char* GetCutName(Int_t i);
Int_t GetNoEPoints() {return fEnergyPoints;}
Double_t GetCalEnergy(Int_t i);
const char* GetCutsFileName() {return fCutsFileName.Data();}
Int_t GetNoCuts() {return fNoCuts;}
Int_t GetMinChannel(Int_t energy, Int_t crystal);
Int_t GetMaxChannel(Int_t energy, Int_t crystal);
//private:
void LoadCuts();
protected:
void SetPars();
};
#endif /* ACULCALIB_ACULCALPARSSCINT_H_ */
#include "AculCalib.h"
ClassImp(AculCalib);
AculCalib::AculCalib() {
printf("AculCalib::Default constructor called.\n");
Init();
}
AculCalib::AculCalib(const char* parfile) {
printf("AculCalib::Constructor called.\n");
SetParFile(parfile);
Init();
}
AculCalib::~AculCalib() {
printf("AculCalib::Destructor called.\n");
delete fPars;
}
void AculCalib::Init() {
fPars = 0;
}
void AculCalib::SetParFile(const char* parfile) {
fParFileName = parfile;
return;
}
void AculCalib::PrintParameters(const char* option) {
if (!fPars) {
cerr << "\"AculCalib::PrintParameters\" parameters were not initialized." << endl;
return;
}
fPars->PrintParameters(option);
}
void AculCalib::PrintCalibParameters() {
cout << "i\ta\t\b" << endl;
for (Int_t i = 0; i<=(Int_t)fA.size(); i++) {
cout << i << "\t" << fA[i] << "\t" << fB[i] << endl;
}
}
Double_t AculCalib::GetA(Int_t i) {
if (i >= (Int_t)fA.size()) //if i >= number of array element
{
return 0.;
}
return fA[i];
}
Double_t AculCalib::GetB(Int_t i) {
if (i >= (Int_t)fB.size()) //if i >= number of array element
{
return 0.;
}
return fB[i];
}
#ifndef ACULCALIB_ACULCALIB_H_
#define ACULCALIB_ACULCALIB_H_
//#include "TObject.h"
//#include "TROOT.h"
#include <iostream>
#include <fstream>
#include "TArrayD.h"
#include "TString.h"
#include "./AculCalPars.h"
using std::cout;
using std::endl;
using std::cerr;
class AculCalib {
protected:
//essential
vector<Double_t> fA;
vector<Double_t> fB;
TString fParFileName;
AculCalPars *fPars;
public:
//essential
AculCalib();
AculCalib(const char* parfile);
virtual ~AculCalib();
ClassDef(AculCalib,1);
virtual void Init();
void SetParFile(const char* parfile);
void PrintParameters(const char* option = "");
void PrintCalibParameters();
Double_t GetA(Int_t i);
Double_t GetB(Int_t i);
protected:
//essential
};
#endif /* ACULCALIB_ACULCALIB_H_ */
################################################################################
# AculData input with some variables
################################################################################
ACULCALIBLIBS := -lCore -lCint -lRIO -lTree -lNet -lThread -lHist -lMatrix -lMathCore -lGpad -lGraf -lSpectrum #-lTELoss
# Add inputs and outputs from these tool invocations to the build variables
ACULCALIB_HEADERS += \
$(ACULCALIB)/AculCalib.h \
$(ACULCALIB)/AculCalibScint.h \
$(ACULCALIB)/AculCalPars.h \
$(ACULCALIB)/AculCalParsScint.h \
$(ACULCALIB)/linkdef.h
ACULCALIBCPP_SRCS += \
$(ACULCALIB)/AculCalib.cpp \
$(ACULCALIB)/AculCalibScint.cpp \
$(ACULCALIB)/AculCalPars.cpp \
$(ACULCALIB)/AculCalParsScint.cpp \
$(ACULCALIB)/AculCalibCint.cpp
ACULCALIBOBJS += \
$(ACULCALIB)/AculCalib.o \
$(ACULCALIB)/AculCalibScint.o \
$(ACULCALIB)/AculCalPars.o \
$(ACULCALIB)/AculCalParsScint.o \
$(ACULCALIB)/AculCalibCint.o
ACULCALIBCPP_DEPS += \
$(ACULCALIB)/AculCalib.d \
$(ACULCALIB)/AculCalibScint.d \
$(ACULCALIB)/AculCalPars.d \
$(ACULCALIB)/AculCalParsScint.d \
$(ACULCALIB)/AculCalibCint.d
\ No newline at end of file
#include "AculCalibScint.h"
#include "AculCalParsScint.h"
ClassImp(AculCalibScint);
AculCalibScint::AculCalibScint() {
printf("AculCalibScint::Default constructor called.\n");
fInFiles = 0;
fTrees = 0;
fCutFile = 0;
}
AculCalibScint::AculCalibScint(const char* parfile) {
printf("AculCalibScint::Constructor called.\n");
SetParFile(parfile);
Init();
// cout << "nofiles: " << nofiles << endl;
// OpenTrees();
// LoadCuts();
// fr.Print();
// fr.At(0);
}
AculCalibScint::~AculCalibScint() {
printf("AculCalibScint::Destructor called.\n");
}
void AculCalibScint::Init() {
fPars = new AculCalParsScint(fParFileName.Data());
OpenFiles();
LoadTrees();
// SetPars();
}
void AculCalibScint::PrintTrees() {
if (!fTrees) {
cerr << "\"AculCalibScint::PrintTrees\" Probably no tree was open." << endl;
return;
}
// TTree *curTree = 0;
cout << " AculCalibScint::PrintTrees:" << endl;
for (Int_t i = 0; i < fPars->GetNoRawFiles(); i++) {
// curTree = fTrees[i];
if (fTrees[i]) {
printf("\tTree No. %d; File: %s; Name: %s\n", i, fTrees[i]->GetDirectory()->GetName(), fTrees[i]->GetName());
} else {
printf("\tTree No. %d was not loaded. Maximal number of trees is %d\n", i, NOCALFILES);
}
}
return;
}
void AculCalibScint::OpenFiles() {
if (!fPars) {
cerr << "\"AculCalibScint::OpenFiles\" parameters were not initialized." << endl;
return;
}
fPars->GetNoRawFiles();
fInFiles = new TFile* [fPars->GetNoRawFiles()];
for (Int_t i = 0; i < fPars->GetNoRawFiles(); i++) {
fInFiles[i] = new TFile(fPars->GetFileName(i), "READ");
// cout << "\"AculCalibScint::OpenFiles\" File \"" << fInFiles[i]->GetName() << "\" was opened." << endl;
}
}
void AculCalibScint::LoadTrees() {
if (!fInFiles) {
cerr << "\"AculCalibScint::LoadTrees\" Input files were not open." << endl;
return;
}
fTrees = new TTree* [fPars->GetNoRawFiles()];
for (Int_t i = 0; i < fPars->GetNoRawFiles(); i++) {
fTrees[i] = (TTree*)fInFiles[i]->Get("AnalysisxTree");
// cout << "\"AculCalibScint::LoadTrees\" Tree \"" << fTrees[i]->GetName()
// << "\" from file \"" << fInFiles[i]->GetName() << "\" was loaded." << endl;
}
}
void AculCalibScint::DrawVariable(const char* variable, Int_t tree, TCanvas *canvas, Int_t lowRange, Int_t upRange) {
// if (!canvas) TCanvas *c = new TCanvas();
if (!canvas) return;
canvas->Clear();
canvas->Divide(4,4);
TString canvasTitle;
TString var;
TString con;
TTree *curTree = 0;
curTree = fTrees[tree];
if (!curTree) {
printf("AculCalibScint::DrawVariable: Tree No. %d was not found.\n", tree);
return;
}
canvasTitle.Form("variable: %s; tree: %d", variable, tree);
canvas->SetTitle(canvasTitle.Data());
for (Int_t i = 0; i < 16; i++) {
var.Form("%s[%d]", variable, i);
con.Form("%s[%d]>%d && %s[%d]<%d", variable, i, lowRange, variable, i, upRange);
canvas->cd(i+1);
curTree->Draw(var.Data(), con.Data());
canvas->Update();
}
}
#pragma once
//#include "TObject.h"
//#include "TROOT.h"
#include <iostream>
#include <fstream>
#include "TFile.h"
#include "TTree.h"
#include "TCanvas.h"
//#include "TH1I.h"
//#include "TGraphErrors.h"
//#include "TArrayD.h"
//#include "TF1.h"
#include "./AculCalib.h"
using std::cout;
using std::endl;
class AculCalibScint : public AculCalib {
private:
//
//todo delete this strange double array somewhere
TFile **fInFiles;
TTree **fTrees;
TFile *fCutFile;
// TClonesArray cutsCol;
//
// TH1I *hfull[NOCALFILES][16];
// TH1I *hcut[NOCALFILES][16];
//
// Double_t mean[NOCALFILES][16];
// Double_t meanRMS[NOCALFILES][16];
//
// TGraphErrors *gCal[16];
// TFile *fGraphs;
public:
// AculCalibScint() : a(0), b(0), c(0), p(0){};
AculCalibScint();
AculCalibScint(const char* parfile);
virtual ~AculCalibScint();
// Define the class for the cint dictionary
ClassDef (AculCalibScint,1);
virtual void Init();
// void SetParFile(const char* parfile);
void PrintTrees();
// void PrintParameters(const char* option = "");
// void PrintPeakRanges();
void DrawVariable(const char* variable, Int_t tree, TCanvas *canvas, Int_t lowRange = 0, Int_t upRange = 4096);
// void DrawBeam(TCanvas *canvas, Int_t files, const char* variable);
// void DrawdEE(const char* variable, Int_t tree, TCanvas *canvas);
// void DrawVariableCut(const char* variable, Int_t tree, TCanvas *canvas, const char* cut1, const char* cut2 = "", Int_t lowRange = 0);
//private functions:
// void LoadCuts();
private:
void OpenFiles();
void LoadTrees();
// void SetPars();
};
#ifdef __CINT__
#pragma link off all globals;
#pragma link off all classes;
#pragma link off all functions;
//#pragma link C++ class AculRaw;
//#pragma link C++ class AculConvert;
#pragma link C++ class AculCalib;
#pragma link C++ class AculCalibScint;
#pragma link C++ class AculCalPars;
#pragma link C++ class AculCalParsScint;
#endif
......@@ -26,16 +26,19 @@ PWD = $(shell pwd)
#INSTALLFOLDER = $(HOME)/AculLib
ACULDATA = $(PWD)/AculData
ACULCALIB = $(PWD)/AculCalib
TELOSS = $(PWD)/TELoss
-include $(ACULDATA)/AculData.mk
-include $(ACULCALIB)/AculCalib.mk
-include $(TELOSS)/TELoss.mk
all: libAculData.so \
libAculCalib.so \
libTELoss.so
#ROOT html documentation, it will be done as a program which will be alsa compiled by this makefile, program will be as a last condition after all of the libraries
htmldoc: libAculData.so
htmldoc: libAculData.so libAculCalib.so libTELoss.so
-$(RM) htmldoc
root -l -q html.cxx
......@@ -43,6 +46,9 @@ clean:
-$(RM) $(ACULDATAOBJS) $(ACULDATACPP_DEPS)
-$(RM) $(ACULDATA)/AculDataCint.* libAculData.so
-@echo ' '
-$(RM) $(ACULCALIBOBJS) $(ACULCALIBCPP_DEPS)
-$(RM) $(ACULCALIB)/AculCalibCint.* libAculCalib.so
-@echo ' '
-$(RM) $(TELOSSOBJS) $(TELOSSCPP_DEPS)
-$(RM) $(TELOSS)/TELossCint.* libTELoss.so
-@echo ' '
......@@ -55,6 +61,11 @@ $(ACULDATA)/AculDataCint.cpp:
-rootcint -f $(ACULDATA)/AculDataCint.cpp -c -p $(ACULDATA_HEADERS)
-@echo ' '
$(ACULCALIB)/AculCalibCint.cpp:
-@echo 'Pre-building AculCalibCint.cpp and AculCalibCint.h files'
-rootcint -f $(ACULCALIB)/AculCalibCint.cpp -c -p $(ACULCALIB_HEADERS)
-@echo ' '
$(TELOSS)/TELossCint.cpp:
-@echo 'Pre-building TELossCint.cpp and TELossCint.h files'
-rootcint -f $(TELOSS)/TELossCint.cpp -c -p $(TELOSS)/TELoss.h $(TELOSS)/linkdef.h
......@@ -68,6 +79,13 @@ libAculData.so: libTELoss.so $(ACULDATAOBJS)
@echo 'Finished building target: $@'
@echo ' '
libAculCalib.so: libTELoss.so $(ACULCALIBOBJS)
@echo 'Building target: $@'
@echo 'Invoking: GCC C++ Linker'
$(CC) -L . -L $(ROOTLIBS) -shared -o"libAculCalib.so" $(ACULCALIBOBJS) $(ACULCALIBLIBS)
@echo 'Finished building target: $@'
@echo ' '
libTELoss.so: $(TELOSSOBJS)
@echo 'Building target: $@'
@echo 'Invoking: GCC C++ Linker'
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment