6 #include "PixTestXray.hh"
8 #include "TStopwatch.h"
22 fParSource("nada"), fParMaskFileName("default"), fParTriggerFrequency(0), fParRunSeconds(0), fParStepSeconds(0),
23 fParVthrCompMin(0), fParVthrCompMax(0), fParFillTree(false), fParDelayTBM(false), fParSaveMaskedPixels(0), fSourceChanged(false) {
26 LOG(logDEBUG) <<
"PixTestXray ctor(PixSetup &a, string, TGTab *)";
29 fPhCal.setPHParameters(fPixSetup->getConfigParameters()->getGainPedestalParameters());
30 fPhCalOK = fPhCal.initialized();
36 PixTestXray::PixTestXray() :
PixTest() {
37 LOG(logDEBUG) <<
"PixTestXray ctor()";
45 std::transform(parName.begin(), parName.end(), parName.begin(), ::tolower);
46 for (
unsigned int i = 0; i <
fParameters.size(); ++i) {
49 if (!parName.compare(
"savemaskfile")) {
52 fParSaveMaskedPixels = !(atoi(sval.c_str())==0);
55 if (!parName.compare(
"maskfilename")) {
56 fParMaskFileName = sval;
59 if (!parName.compare(
"source")) {
61 fSourceChanged =
true;
64 if (!parName.compare(
"vthrcompmin")) {
65 fParVthrCompMin = atoi(sval.c_str());
68 if (!parName.compare(
"vthrcompmax")) {
69 fParVthrCompMax = atoi(sval.c_str());
72 if (!parName.compare(
"trgfrequency(khz)")) {
73 fParTriggerFrequency = atoi(sval.c_str());
76 if (!parName.compare(
"runseconds")) {
77 fParRunSeconds = atoi(sval.c_str());
80 if (!parName.compare(
"stepseconds")) {
81 fParStepSeconds = atoi(sval.c_str());
84 if (!parName.compare(
"delaytbm")) {
87 fParDelayTBM = !(atoi(sval.c_str())==0);
90 if (!parName.compare(
"filltree")) {
93 fParFillTree = !(atoi(sval.c_str())==0);
105 std::transform(command.begin(), command.end(), command.begin(), ::tolower);
106 LOG(logDEBUG) <<
"running command: " << command;
108 if (!command.compare(
"stop")){
112 if (!command.compare(
"maskhotpixels")) {
113 doRunMaskHotPixels();
117 if (!command.compare(
"ratescan")) {
122 if (!command.compare(
"phrun")) {
127 LOG(logDEBUG) <<
"did not find command ->" << command <<
"<-";
131 void PixTestXray::init() {
132 LOG(logDEBUG) <<
"PixTestXray::init()";
134 fDirectory = gFile->GetDirectory(fName.c_str());
139 fSourceChanged =
false;
145 fTestTip = string(
"Xray vcal calibration test")
147 fSummaryTip = string(
"to be implemented")
149 fStopTip = string(
"Stop 'phrun' and save data.")
155 vector<TH2D*> PixTestXray::bookHotPixelMap() {
158 unsigned nrocs = rocIds.size();
160 for (
unsigned int iroc = 0; iroc < nrocs; ++iroc){
161 TH2D *h2 =
bookTH2D(Form(
"hotPixelMap_C%d", rocIds[iroc]), Form(
"hotPixelMap_C%d", rocIds[iroc]),
162 52, 0., 52., 80, 0., 80.);
165 fHotPixelMap.push_back(h2);
169 copy(fHotPixelMap.begin(), fHotPixelMap.end(), back_inserter(
fHistList));
175 void PixTestXray::bookHist(
string name) {
180 unsigned nrocs = rocIds.size();
185 for (
unsigned int iroc = 0; iroc < nrocs; ++iroc){
186 h1 =
bookTH1D(Form(
"hits_%s_C%d", name.c_str(), rocIds[iroc]), Form(
"hits_%s_C%d", name.c_str(), rocIds[iroc]),
193 h1 =
bookTH1D(Form(
"mpix_%s_C%d", name.c_str(), rocIds[iroc]), Form(
"mpix_%s_C%d", name.c_str(), rocIds[iroc]),
197 setTitles(h1,
"VthrComp",
"maskedpixels");
200 h2 =
bookTH2D(Form(
"hitMap_%s_C%d", name.c_str(), rocIds[iroc]), Form(
"hitMap_%s_C%d", name.c_str(), rocIds[iroc]),
201 52, 0., 52., 80, 0., 80.);
204 fHitMap.push_back(h2);
207 copy(fHits.begin(), fHits.end(), back_inserter(
fHistList));
208 copy(fMpix.begin(), fMpix.end(), back_inserter(
fHistList));
209 copy(fHitMap.begin(), fHitMap.end(), back_inserter(
fHistList));
214 PixTestXray::~PixTestXray() {
215 LOG(logDEBUG) <<
"PixTestXray dtor";
217 if (fTree && fParFillTree) fTree->Write();
224 bigBanner(Form(
"PixTestXray::doTest()"));
228 LOG(logINFO) <<
"PixTestXray::doTest() done ";
234 void PixTestXray::doPhRun() {
236 banner(Form(
"PixTestXray::doPhRun() fParRunSeconds = %d", fParRunSeconds));
244 for (
unsigned int i = 0; i < fHotPixels.size(); ++i) {
245 vector<pair<int, int> > hot = fHotPixels[i];
246 for (
unsigned int ipix = 0; ipix < hot.size(); ++ipix) {
247 LOG(logINFO) <<
"ROC " <<
getIdFromIdx(i) <<
" masking hot pixel " << hot[ipix].first <<
"/" << hot[ipix].second;
256 int totalPeriod =
prepareDaq(fParTriggerFrequency, 50);
259 LOG(logINFO) <<
"set TBM register delays = 0x40";
263 fEventsMax = 1000 * fParTriggerFrequency * fParRunSeconds;
265 if (fQ.size() > 0 && fSourceChanged) {
266 LOG(logDEBUG) <<
"booking new histograms as source name has changed";
272 fHitsVsEvents.clear();
273 fHitsVsColumn.clear();
274 fHitsVsEvtCol.clear();
277 if (0 == fQ.size()) {
278 fSourceChanged =
false;
284 for (
unsigned int iroc = 0; iroc < rocIds.size(); ++iroc){
285 h2 =
bookTH2D(Form(
"hMap_%s_C%d", fParSource.c_str(), rocIds[iroc]),
286 Form(
"hMap_%s_C%d", fParSource.c_str(), rocIds[iroc]),
287 52, 0., 52., 80, 0., 80.);
295 p2 =
bookTProfile2D(Form(
"qMap_%s_C%d", fParSource.c_str(), rocIds[iroc]),
296 Form(
"qMap_%s_C%d", fParSource.c_str(), rocIds[iroc]),
297 52, 0., 52., 80, 0., 80.);
304 p2 =
bookTProfile2D(Form(
"phMap_%s_C%d", fParSource.c_str(), rocIds[iroc]),
305 Form(
"phMap_%s_C%d", fParSource.c_str(), rocIds[iroc]),
306 52, 0., 52., 80, 0., 80.);
311 fPHmap.push_back(p2);
313 h1 =
bookTH1D(Form(
"q_%s_C%d", fParSource.c_str(), rocIds[iroc]),
314 Form(
"q_%s_C%d", fParSource.c_str(), rocIds[iroc]),
318 setTitles(h1,
"Q [Vcal]",
"Entries/bin");
321 h1 =
bookTH1D(Form(
"ph_%s_C%d", fParSource.c_str(), rocIds[iroc]),
322 Form(
"ph_%s_C%d", fParSource.c_str(), rocIds[iroc]),
326 setTitles(h1,
"PH [ADC]",
"Entries/bin");
329 h1 =
bookTH1D(Form(
"hitsVsEvents_%s_C%d", fParSource.c_str(), rocIds[iroc]),
330 Form(
"hitsVsEvents_%s_C%d", fParSource.c_str(), rocIds[iroc]), 1000, 0., fEventsMax);
334 fHitsVsEvents.push_back(h1);
336 h1 =
bookTH1D(Form(
"hitsVsColumn_%s_C%d", fParSource.c_str(), rocIds[iroc]),
337 Form(
"hitsVsColumn_%s_C%d", fParSource.c_str(), rocIds[iroc]),
342 fHitsVsColumn.push_back(h1);
344 h2 =
bookTH2D(Form(
"hitsVsEvtCol_%s_C%d", fParSource.c_str(), rocIds[iroc]),
345 Form(
"hitsVsEvtCol_%s_C%d", fParSource.c_str(), rocIds[iroc]),
346 1000, 0., fEventsMax, 52, 0., 52.);
351 fHitsVsEvtCol.push_back(h2);
354 h3 =
bookTH1D(Form(
"ntrig_%s", fParSource.c_str()),
355 Form(
"ntrig_%s", fParSource.c_str()), 1, 0., 1.);
357 fTriggers.push_back(h3);
360 copy(fHmap.begin(), fHmap.end(), back_inserter(
fHistList));
361 copy(fQmap.begin(), fQmap.end(), back_inserter(
fHistList));
362 copy(fQ.begin(), fQ.end(), back_inserter(
fHistList));
363 copy(fPHmap.begin(), fPHmap.end(), back_inserter(
fHistList));
364 copy(fPH.begin(), fPH.end(), back_inserter(
fHistList));
365 copy(fTriggers.begin(), fTriggers.end(), back_inserter(
fHistList));
366 copy(fHitsVsEvents.begin(), fHitsVsEvents.end(), back_inserter(
fHistList));
367 copy(fHitsVsColumn.begin(), fHitsVsColumn.end(), back_inserter(
fHistList));
368 copy(fHitsVsEvtCol.begin(), fHitsVsEvtCol.end(), back_inserter(
fHistList));
375 LOG(logINFO) <<
"PixTestXray::doPhRun start TriggerLoop with trigger frequency " << fParTriggerFrequency
376 <<
" kHz, period " << finalPeriod
377 <<
" and duration " << fParRunSeconds <<
" seconds, "
378 <<
" fEventsMax = " << fEventsMax ;
385 gSystem->ProcessEvents();
387 seconds = t.RealTime();
388 LOG(logINFO) <<
"run duration " << seconds <<
" seconds, buffer almost full ("
389 << (int)perFull <<
"%), pausing triggers.";
392 LOG(logINFO) <<
"Resuming triggers.";
398 seconds = t.RealTime();
400 if (static_cast<int>(seconds >= fParRunSeconds)) {
401 LOG(logINFO) <<
"data taking finished, elapsed time: " << seconds <<
" seconds.";
417 LOG(logINFO) <<
"PixTestXray::doPhRun() done";
423 void PixTestXray::doRateScan() {
425 banner(Form(
"PixTestXray::doRateScan() fParStepSeconds = %d, vthrcom = %d .. %d", fParStepSeconds, fParVthrCompMin, fParVthrCompMax));
430 fPg_setup =
fPixSetup->getConfigParameters()->getTbPgSettings();
436 if (0 == fHits.size()) bookHist(
"xrayVthrCompScan");
443 int totalPeriod =
prepareDaq(fParTriggerFrequency, 50);
446 for (fVthrComp = fParVthrCompMin; fVthrComp <= fParVthrCompMax; ++fVthrComp) {
447 for (
unsigned i = 0; i < fHitMap.size(); ++i) {
455 LOG(logINFO)<<
"Starting Loop with VthrComp = " << fVthrComp;
460 LOG(logINFO) <<
"PixTestXray::doRateScan start TriggerLoop with period " << finalPeriod <<
" and duration " << fParStepSeconds <<
" seconds";
463 gSystem->ProcessEvents();
465 LOG(logINFO) <<
"Buffer almost full, pausing triggers.";
468 LOG(logINFO) <<
"Resuming triggers.";
472 if (static_cast<int>(t.RealTime()) >= fParStepSeconds) {
473 LOG(logINFO) <<
"Elapsed time: " << t.RealTime() <<
" seconds.";
498 TFile *f = TFile::Open(
"testROC/pxar_Mo_Vcal_30_80_10s.root");
500 TH1D *h1 = ((TH1D*)f->Get(
"Xray/hits_xrayVthrCompScan_C0_V0"));
501 TH1D *h2 = (TH1D*)h1->Clone(
"local");
505 copy(fHits.begin(), fHits.end(), back_inserter(
fHistList));
515 unsigned nrocs = rocIds.size();
516 double lo(-1.), hi(-1.);
517 for (
unsigned int i = 0; i < nrocs; ++i) {
518 TF1 *f1 =
fPIF->xrayScan(fHits[i]);
519 f1->GetRange(lo, hi);
520 fHits[i]->Fit(f1,
"lr",
"", lo, hi);
521 double thr = f1->GetParameter(0);
522 if (thr < 0 || thr > 255.) thr = 0.;
523 uint8_t ithr =
static_cast<uint8_t
>(thr);
524 LOG(logINFO) <<
"ROC " <<
static_cast<int>(rocIds[i]) <<
" with VthrComp threshold = " << thr <<
" -> " << static_cast<int>(ithr);
534 vector<TH1*> thr0 =
scurveMaps(
"vcal",
"xrayScan", 5, 0, 255, -1, 9);
540 string hname(
""), scurvesMeanString(
""), scurvesRmsString(
"");
541 for (
unsigned int i = 0; i < thr0.size(); ++i) {
542 hname = thr0[i]->GetName();
544 if (string::npos == hname.find(
"dist_thr_"))
continue;
545 scurvesMeanString += Form(
"%6.2f ", thr0[i]->GetMean());
546 scurvesRmsString += Form(
"%6.2f ", thr0[i]->GetRMS());
549 LOG(logINFO) <<
"PixTestXray::doTest() done";
550 LOG(logINFO) <<
"vcal mean: " << scurvesMeanString;
551 LOG(logINFO) <<
"vcal RMS: " << scurvesRmsString;
553 LOG(logINFO) <<
"PixTestXray::doRateScan() done";
558 void PixTestXray::readDataOld() {
561 vector<pxar::Event> daqdat;
565 for(std::vector<pxar::Event>::iterator it = daqdat.begin(); it != daqdat.end(); ++it) {
566 pixCnt += it->pixels.size();
568 for (
unsigned int ipix = 0; ipix < it->pixels.size(); ++ipix) {
569 fHitMap[
getIdxFromId(it->pixels[ipix].roc())]->Fill(it->pixels[ipix].column(), it->pixels[ipix].row());
572 LOG(logDEBUG) <<
"Processing Data: " << daqdat.size() <<
" events with " << pixCnt <<
" pixels";
576 void PixTestXray::analyzeData() {
579 unsigned nrocs = rocIds.size();
582 for (
unsigned int i = 0; i < nrocs; ++i) {
583 double cut = meanHit(fHitMap[i]);
584 cut = noiseLevel(fHitMap[i]);
585 cnt = countHitsAndMaskPixels(fHitMap[i], cut, rocIds[i]);
586 fHits[i]->SetBinContent(fVthrComp+1, cnt);
588 fMpix[i]->SetBinContent(fVthrComp+1, mpix);
596 double PixTestXray::meanHit(TH2D *h2) {
598 TH1D *h1 =
new TH1D(
"h1",
"h1", 1000, 0., 1000.);
600 for (
int ix = 0; ix < h2->GetNbinsX(); ++ix) {
601 for (
int iy = 0; iy < h2->GetNbinsY(); ++iy) {
602 h1->Fill(h2->GetBinContent(ix+1, iy+1));
606 double mean = h1->GetMean();
608 LOG(logDEBUG) <<
"hist " << h2->GetName() <<
" mean hits = " << mean;
613 double PixTestXray::noiseLevel(TH2D *h2) {
615 TH1D *h1 =
new TH1D(
"h1",
"h1", 1000, 0., 1000.);
617 for (
int ix = 0; ix < h2->GetNbinsX(); ++ix) {
618 for (
int iy = 0; iy < h2->GetNbinsY(); ++iy) {
619 h1->Fill(h2->GetBinContent(ix+1, iy+1));
625 for (
int ix = 1; ix < h1->GetNbinsX(); ++ix) {
626 if (h1->GetBinContent(ix+1) < 1) {
633 for (
int ix = 1; ix < h1->GetNbinsX(); ++ix) {
634 if (h1->GetBinContent(ix+1) > 1) {
641 LOG(logINFO) <<
"hist " << h2->GetName()
642 <<
" (maximum: " << h2->GetMaximum() <<
") "
643 <<
" noise level = " << noise <<
" last bin above 1: " << lastbin;
649 int PixTestXray::countHitsAndMaskPixels(TH2D *h2,
double noiseLevel,
int iroc) {
653 for (
int ix = 0; ix < h2->GetNbinsX(); ++ix) {
654 for (
int iy = 0; iy < h2->GetNbinsY(); ++iy) {
655 entries = h2->GetBinContent(ix+1, iy+1);
656 if (entries > noiseLevel) {
658 LOG(logINFO) <<
"ROC " << iroc <<
" masking pixel " << ix <<
"/" << iy
659 <<
" with #hits = " << entries <<
" (cut: " << noiseLevel <<
")";
661 cnt +=
static_cast<int>(entries);
670 void PixTestXray::readData() {
675 for (std::vector<pxar::Event>::iterator it = daqdat.begin(); it != daqdat.end(); ++it) {
676 pixCnt += it->pixels.size();
680 fTreeEvent.header = it->header;
682 fTreeEvent.trailer = it->trailer;
687 for (
unsigned int ipix = 0; ipix < it->pixels.size(); ++ipix) {
690 q = fPhCal.vcal(it->pixels[ipix].roc(),
691 it->pixels[ipix].column(),
692 it->pixels[ipix].row(),
693 it->pixels[ipix].value());
697 fHitMap[idx]->Fill(it->pixels[ipix].column(), it->pixels[ipix].row());
699 fQmap[idx]->Fill(it->pixels[ipix].column(), it->pixels[ipix].row(), q);
701 fPHmap[idx]->Fill(it->pixels[ipix].column(), it->pixels[ipix].row(), it->pixels[ipix].value());
702 fPH[idx]->Fill(it->pixels[ipix].value());
704 if (fParFillTree && ipix < 20000) {
706 fTreeEvent.proc[ipix] = it->pixels[ipix].roc();
707 fTreeEvent.pcol[ipix] = it->pixels[ipix].column();
708 fTreeEvent.prow[ipix] = it->pixels[ipix].row();
709 fTreeEvent.pval[ipix] = it->pixels[ipix].value();
710 fTreeEvent.pq[ipix] = q;
714 if (fParFillTree) fTree->Fill();
717 LOG(logDEBUG) <<
"Processing Data: " << daqdat.size() <<
" events with " << pixCnt <<
" pixels";
722 void PixTestXray::processData(uint16_t numevents) {
726 static long int evtCnt(-1);
728 LOG(logDEBUG) <<
"Getting Event Buffer";
729 vector<pxar::Event> daqdat;
732 for (
unsigned int i = 0; i < numevents ; i++) {
735 if(evt.pixels.size() > 0)
736 daqdat.push_back(evt);
743 LOG(logDEBUG) <<
"Processing Data: " << daqdat.size() <<
" events.";
747 for (std::vector<pxar::Event>::iterator it = daqdat.begin(); it != daqdat.end(); ++it) {
749 pixCnt += it->pixels.size();
754 fTreeEvent.header = it->header;
756 fTreeEvent.trailer = it->trailer;
759 for (
unsigned int ipix = 0; ipix < it->pixels.size(); ++ipix) {
762 fHitsVsEvents[idx]->Fill(evtCnt);
763 fHitsVsColumn[idx]->Fill(it->pixels[ipix].column());
764 fHitsVsEvtCol[idx]->Fill(evtCnt, it->pixels[ipix].column());
767 q = fPhCal.vcal(it->pixels[ipix].roc(),
768 it->pixels[ipix].column(),
769 it->pixels[ipix].row(),
770 it->pixels[ipix].value());
774 fHmap[idx]->Fill(it->pixels[ipix].column(), it->pixels[ipix].row());
776 fQmap[idx]->Fill(it->pixels[ipix].column(), it->pixels[ipix].row(), q);
778 fPHmap[idx]->Fill(it->pixels[ipix].column(), it->pixels[ipix].row(), it->pixels[ipix].value());
779 fPH[idx]->Fill(it->pixels[ipix].value());
781 if (fParFillTree && ipix < 20000) {
783 fTreeEvent.proc[ipix] = it->pixels[ipix].roc();
784 fTreeEvent.pcol[ipix] = it->pixels[ipix].column();
785 fTreeEvent.prow[ipix] = it->pixels[ipix].row();
786 fTreeEvent.pval[ipix] = it->pixels[ipix].value();
787 fTreeEvent.pq[ipix] = q;
791 if (fParFillTree) fTree->Fill();
794 LOG(logDEBUG) << Form(
" # events read: %6ld, pixels seen in all events: %3d", daqdat.size(), pixCnt);
796 fHmap[0]->Draw(
"colz");
797 fTriggers[0]->SetBinContent(1, fTriggers[0]->GetBinContent(1) + daqdat.size());
803 void PixTestXray::doRunMaskHotPixels() {
807 bookHist(
"hotpixels");
810 for (
unsigned int i = 0; i < v.size(); ++i) v[i]->Reset();
813 if (fParSaveMaskedPixels) {
814 if (fParMaskFileName ==
"default") {
815 fPixSetup->getConfigParameters()->writeMaskFile(fHotPixels);
817 fPixSetup->getConfigParameters()->writeMaskFile(fHotPixels, fParMaskFileName);
831 void PixTestXray::doStop(){
834 LOG(logINFO) <<
"Stop pressed. Ending test.";
uint16_t prepareDaq(int triggerFreq, uint8_t trgTkDel)
set up DAQ (including call to setTriggerFrequency)
static void replaceAll(std::string &str, const std::string &from, const std::string &to)
in str, replace all occurences of from to to
size_t getNMaskedPixels(uint8_t rocid)
void setPatternGenerator(std::vector< std::pair< std::string, uint8_t > > pg_setup)
virtual bool setParameter(std::string parName, std::string sval)
set the string value of a parameter
std::map< TH1 *, std::string > fHistOptions
options can be stored with each histogram
void daqTriggerLoopHalt()
bool setDAC(std::string dacName, uint8_t dacValue, uint8_t rocI2C)
std::string fStopTip
information for this test
PixSetup * fPixSetup
all necessary stuff in one place
std::vector< TH1 * > mapsWithString(std::vector< TH1 * >, std::string name)
return a list of TH* that have 'name' as part to their histogram name
std::vector< std::pair< std::string, std::string > > fParameters
the parameters of this test
int getIdxFromId(int id)
provide the mapping between ROC index and ID
void maskAllPixels(bool mask, uint8_t rocid)
void maskHotPixels(std::vector< TH2D * >)
determine hot pixels with high occupancy
TDirectory * fDirectory
where the root histograms will end up
uint16_t daqTriggerLoop(uint16_t period=1000)
void bookTree()
book a minimal tree with pixel events
void doTest()
function connected to "DoTest" button of PixTab
std::vector< uint8_t > getEnabledRocIDs()
void finalCleanup()
functions for DAQ
void testAllPixels(bool enable)
std::list< TH1 * >::iterator fDisplayedHist
pointer to the histogram currently displayed
TH1D * bookTH1D(std::string sname, std::string title, int nbins, double xmin, double xmax)
book a TH1D, adding version information to the name and title
void cacheDacs(bool verbose=false)
cache all DACs
bool setTbmReg(std::string regName, uint8_t regValue, uint8_t tbmid)
std::list< TH1 * > fHistList
list of histograms available in PixTab::next and PixTab::previous
void setTitles(TH1 *h, const char *sx, const char *sy, float size=0.05, float xoff=1.1, float yoff=1.1, float lsize=0.05, int font=42)
utility to set histogram titles
PixInitFunc * fPIF
function instantiation and automatic initialization
void runCommand(std::string command)
allow execution of any button in the test
TH2D * bookTH2D(std::string sname, std::string title, int nbinsx, double xmin, double xmax, int nbinsy, double ymin, double max)
book a TH2D, adding version information to the name and title
void maskPixel(uint8_t column, uint8_t row, bool mask)
void update()
signal to PixTab to update the canvas
pxar::pxarCore * fApi
pointer to the API
std::vector< Event > daqGetEventBuffer()
TProfile2D * bookTProfile2D(std::string sname, std::string title, int nbinsx, double xmin, double xmax, int nbinsy, double ymin, double max, std::string option="")
book a TProfile2D, adding version information to the name and title
void setToolTips()
implement this to provide updated tool tips if the user changes test parameters
std::vector< TH1 * > scurveMaps(std::string dac, std::string name, int ntrig=10, int daclo=0, int dachi=255, int dacsperstep=-1, int result=15, int ihit=1, int flag=FLAG_FORCE_MASKED)
void init()
sets all test parameters
void maskPixels()
mask all pixels mentioned in the mask file
int getIdFromIdx(int idx)
provide the mapping between ROC ID and index