Main Page | Modules | Namespace List | Class Hierarchy | Alphabetical List | Data Structures | File List | Namespace Members | Data Fields | Related Pages

TrimRangeAlgorithm.cpp

00001 #include "TrimRangeAlgorithm.h"
00002 #include "AnalysisAlgorithmMap.h"
00003 #include "AnalysisService.h"
00004 #include "Sct/SctParameters.h"
00005 #include "Sct/UnsupportedOperationError.h"
00006 #include "SctData/DefectPrototype.h"
00007 #include "SctData/TrimRangeTestResult.h"
00008 #include "SctData/FitScanResult.h"
00009 #include "SctData/FitObject.h"
00010 #include "SctData/StandardDefects.h"
00011 #include "SctData/ModuleConfiguration.h"
00012 #include "SctData/ChipConfiguration.h"
00013 #include "SctData/ResponseCurve.h"
00014 #include <boost/shared_ptr.hpp>
00015 #include <algorithm>
00016 #include <memory>
00017 #include <vector>
00018 
00019 using namespace Sct;
00020 using namespace SctData;
00021 using namespace boost;
00022 
00023 namespace SctAnalysis {
00024 
00025 bool debug = false;
00026     
00027 bool TrimRangeAlgorithm::inMap = AnalysisAlgorithmMap::instance().setAlgorithm("TrimRangeTest", auto_ptr<AnalysisAlgorithm>(new TrimRangeAlgorithm()));
00028 
00029 shared_ptr<AnalysisAlgorithm> TrimRangeAlgorithm::clone(const TestData& testData, const string& moduleName) const throw() {
00030     return shared_ptr<AnalysisAlgorithm>(new TrimRangeAlgorithm(testData, moduleName, *this));
00031 }
00032 
00033 bool TrimRangeAlgorithm::s_targetVariation = false;
00034 bool TrimRangeAlgorithm::s_rangeVariation = true;
00035 
00036 void TrimRangeAlgorithm::loadData() {
00037     loadAllFits();
00038 }
00039 
00040 bool TrimRangeAlgorithm::canAnalyze() const{
00041     return hasAllFits();
00042 }
00043 
00044 shared_ptr<TestResult> TrimRangeAlgorithm::createTestResult() const {
00045         return shared_ptr<TestResult>(new TrimRangeTestResult());    
00046 }
00047 
00048 void TrimRangeAlgorithm::analyze() {           
00049     shared_ptr<TrimRangeTestResult> result = dynamic_pointer_cast<TrimRangeTestResult> ( getTestResult() );
00050     result->setScanVariable(getFit(0)->getHeader().getVariable());
00051     const ModuleConfiguration& config = getFit(0)->getConfiguration();
00052 
00053     /*
00054        STAGE 1 : make sense of the incoming data.
00055        make a vector of all ranges, all points in those ranges and the 
00056        appropriate scan number.
00057     */
00058     std::vector<TrimRange> all_ranges = createRanges(*result);
00059 
00060     /*
00061        STAGE 2 : make graphs of trim against vt50 for each chip
00062        and each channel and each range
00063     */
00064 
00065     shared_ptr<SctData::TrimRangeTestResult::ChipTrimData> dataArray[all_ranges.size()][nChipModule];
00066 
00067     // loop over trim ranges
00068     for (unsigned irange=0; irange<all_ranges.size(); ++irange) {
00069 
00070         TrimRange& range = all_ranges[irange];
00071 
00072         // loop over chips
00073         for (unsigned ichip=0; ichip<nChipModule; ++ichip) {
00074             dataArray[irange][ichip] =
00075                 shared_ptr<TrimRangeTestResult::ChipTrimData>(new TrimRangeTestResult::ChipTrimData());
00076 
00077             // loop over trim-scan points
00078             for (unsigned ipoint=0; ipoint<range.points.size(); ++ipoint) {
00079 
00080                 TrimPoint& this_point = range.points[ipoint];
00081                 shared_ptr<const FitScanResult> fitted=getFit(this_point.scan);
00082                 const DefectList& fitDefects=fitted->getDefects();
00083 
00084                 // loop over channels in that chip.
00085                 for (unsigned iChanInChip=0; iChanInChip<nChannelChip; ++iChanInChip) {
00086                     unsigned ichannel=iChanInChip + ichip*nChannelChip;
00087 
00088                     // get the relevant TrimData object
00089                     Stat<TrimRangeTestResult::TrimData>& trdt = dataArray[irange][ichip]
00090                             ->channelData.modifyAt(iChanInChip);
00091 
00092                     // channel is valid if there are no defects on it.
00093                     if(fitDefects.defectSeverityEncompassingElement(ModuleElement::Channel(ichannel)) >= SERIOUS) {
00094                         trdt.valid=false;
00095                         if (debug) {
00096                             cout << "Severe defect on Channel " << ichannel << endl;
00097                             //fitDefects.getDefectsEncompassingElement( ModuleElement::Channel(ichannel))->print(cout);
00098                         }
00099                     }
00100             if (config.getChipConfiguration(ichip).isMasked(iChanInChip)) {
00101             trdt.valid=false;
00102             if (debug) cout << "Masked channel: " << ichannel << endl;
00103             }
00104                     if (trdt.valid) {
00105                         float gr_mean = fitted->getChannelFit(ichannel).getParameter(1);
00106                         float gr_value = this_point.value;
00107                         trdt.value.graph.push_back(std::make_pair(gr_mean, gr_value));
00108                     } // if channel is valid
00109                 } // loop over channels
00110             } // loop over scan-points
00111         } // loop over chips
00112     } // loop over ranges
00113 
00114 
00115     /*
00116       STAGE 3:
00117       create a vector of target s starting from firstTarget in uniform steps
00118       to a total of ntargets
00119     */
00120     std::vector<float> target;
00121     for (unsigned itarg=0; itarg<nTargets(); ++itarg) {
00122         target.push_back (firstTarget() + itarg * targetStep());
00123     }
00124 
00125     shared_ptr<const TrimRangeTestResult::ChipTrim> trimArray[all_ranges.size()][nChipModule][nTargets()];
00126 
00127     /*
00128       STAGE 4a: fit the graphs 
00129       STAGE 4b: find the number of trimmable channels for each in a series of targets
00130     */
00131     for (unsigned irange=0; irange<all_ranges.size(); ++irange) {
00132         for (unsigned ichip=0; ichip<nChipModule; ++ichip) {
00133             // 4a:
00134             if (dataArray[irange][ichip].get()==0) {
00135                 throw InvariantViolatedError("TrimRangeAlgorithm: dataArray pointer should not be zero!", __FILE__, __LINE__);
00136             }
00137             doTrimDataFit( *dataArray[irange][ichip] );
00138             for (unsigned itarget=0; itarget<nTargets(); ++itarget) {
00139                 //4b:
00140         if (debug && target[itarget]> 85 && target[itarget]<95) 
00141             cout << "Chip: " << ichip << " Range: " << all_ranges[irange].range << endl;
00142                 trimArray[irange][ichip][itarget] = getChipTrim(*dataArray[irange][ichip],
00143                                                     target[itarget],
00144                                                     all_ranges[irange].range);
00145             }
00146         }
00147     }
00148 
00149     /*
00150       STAGE 5 select the best trim target which trims the maximum number of channels
00151     */
00152 
00153     unsigned best_target_for_chip[nChipModule];       // index of vector targets
00154     unsigned best_range_for_chip[nChipModule];        // index of vector all_ranges
00155 
00156     // 5a no chip-to-chip variation:
00157     if ( !allowTrimTargetVariation() && !allowTrimRangeVariation() ) {
00158     if (debug) cout << "stage 5a" << endl;
00159         unsigned max_trimmed=0;
00160         unsigned target_lo=0, target_hi=0;
00161 
00162         for (unsigned irange=0; irange<all_ranges.size(); ++irange) {
00163             for( unsigned itarget=0; itarget<nTargets(); ++itarget) {
00164                 unsigned n_trimmed=0;
00165                 /* find number of channels which are trimmable in the module */
00166                 for (unsigned ichip=0; ichip<nChipModule; ++ichip) {
00167                     n_trimmed+=trimArray[irange][ichip][itarget]->channelTrim.n();
00168 
00169                 }
00170                 if (n_trimmed>max_trimmed) {
00171                     target_lo=itarget;
00172                     best_range_for_chip[0] = irange;
00173                     max_trimmed=n_trimmed;
00174                 }
00175                 if (n_trimmed==max_trimmed && irange==best_range_for_chip[0]) {
00176                     target_hi=itarget;
00177                 }
00178             }
00179         }
00180         for (unsigned ichip=0; ichip<nChipModule; ++ichip) {
00181             best_range_for_chip[ichip]=best_range_for_chip[0];
00182             best_target_for_chip[ichip]=(target_hi+target_lo)/2;
00183         }
00184     } else if ( allowTrimTargetVariation() && !allowTrimRangeVariation() ) { // 5b chip-to-chip target variation:
00185     if (debug) cout << "stage 5b" << endl;
00186     unsigned target_lo[nChipModule];
00187     unsigned target_hi[nChipModule];
00188     unsigned max_trimmed[nChipModule];
00189     for (unsigned ichip=0; ichip<nChipModule; ++ichip) {
00190         target_lo[ichip]=target_hi[ichip]=max_trimmed[ichip]=0;
00191     }   
00192 
00193     for (unsigned irange=0; irange<all_ranges.size(); ++irange) {
00194         for( unsigned itarget=0; itarget<nTargets(); ++itarget) {
00195         unsigned n_trimmed=0;
00196         /* find number of channels which are trimmable in the module */
00197         for (unsigned ichip=0; ichip<nChipModule; ++ichip) {
00198             n_trimmed = trimArray[irange][ichip][itarget]->channelTrim.n();
00199             
00200             if (n_trimmed>max_trimmed[ichip] ) {
00201             target_lo[ichip]=itarget;
00202             best_range_for_chip[ichip] = irange;
00203             }
00204             if (n_trimmed==max_trimmed[ichip] && irange==best_range_for_chip[ichip]) {
00205             target_hi[ichip]=itarget;
00206             }
00207         }
00208         }
00209     }
00210     for (unsigned ichip=0; ichip<nChipModule; ++ichip) {
00211         best_target_for_chip[ichip]=(target_hi[ichip]+target_lo[ichip])/2;
00212     }
00213     } else if ( !allowTrimTargetVariation() && allowTrimRangeVariation() ) {           // 5c chip-to-chip range variation:
00214     if (debug) cout << "stage 5c"<<endl;
00215     unsigned target_lo=0;
00216     unsigned target_hi=0;
00217     unsigned total_channels_trimmed_max=0;
00218     unsigned total_range_lo=0;
00219 
00220     for( unsigned itarget=0; itarget<nTargets(); ++itarget) {
00221         if (debug)
00222         cout << "TARGET " << itarget << "  " << target[itarget] << "mV" << endl;
00223         unsigned total_channels_trimmed=0;
00224         unsigned range_total=0;
00225         
00226         for (unsigned ichip=0; ichip<nChipModule; ++ichip) {
00227         unsigned best_range=0;
00228         unsigned max = 0;
00229 
00230         for (unsigned irange=0; irange<all_ranges.size(); ++irange) {
00231             unsigned n_trimmed = trimArray[irange][ichip][itarget]->channelTrim.n();
00232 
00233             // if this is the lowest range with the maximum number of channels:
00234             if (n_trimmed > max ||
00235             ( all_ranges[irange].range < all_ranges[best_range].range
00236               && n_trimmed==max)) {
00237             best_range = irange;
00238             max = n_trimmed;
00239             }
00240         }
00241         // find the total number of channels trimmed
00242         total_channels_trimmed+=max;
00243         range_total+=all_ranges[best_range].range ;
00244         }
00245         if (debug)
00246         cout << "  TOTAL = " <<  total_channels_trimmed << " for " << range_total << endl;
00247         
00248         if (total_channels_trimmed > total_channels_trimmed_max ||
00249         ( total_channels_trimmed == total_channels_trimmed_max &&
00250           range_total < total_range_lo ) ) {
00251         target_lo=itarget;
00252         total_range_lo=range_total;
00253         total_channels_trimmed_max=total_channels_trimmed;
00254         if (debug)
00255             cout << " ...... modified lower target" << endl;
00256         }
00257 
00258         if (total_channels_trimmed == total_channels_trimmed_max &&
00259         total_range_lo==range_total ) {
00260         target_hi=itarget;
00261         if (debug)
00262             cout << " ...... modified upper target" << endl;
00263         }
00264 
00265     }
00266     for (unsigned ichip=0; ichip<nChipModule; ++ichip) {
00267         best_target_for_chip[ichip]=(target_hi+target_lo)/2;
00268         // know the target, get the ranges again:
00269         if (debug)
00270         cout << endl << " --> CHIP " << ichip << endl;
00271         unsigned max=0;
00272         best_range_for_chip[ichip]=0;
00273 
00274         for (unsigned irange=0; irange<all_ranges.size(); ++irange) {
00275         unsigned n_trimmed = trimArray[irange][ichip][best_target_for_chip[ichip]]->channelTrim.n();
00276         if (debug)
00277             cout << "R" << irange << " -> " << n_trimmed << endl;
00278         if (n_trimmed> max ||
00279             ( all_ranges[irange].range < all_ranges[best_range_for_chip[ichip]].range
00280               && n_trimmed==max)) {
00281             best_range_for_chip[ichip] = irange;
00282             max=n_trimmed;
00283             if (debug)
00284             cout << "best range changed to #" << irange << " = " << all_ranges[irange].range << endl;
00285         }
00286         }
00287     }
00288     } else {
00289     throw UnsupportedOperationError("Trim Range Algorithm not implemented for desired options!", __FILE__, __LINE__);
00290     }
00291     
00292     /* STAGE 6 fill output TestResult data */
00293     if (debug)
00294         cout << "stage 6"<<endl;
00295 
00296     {
00297         // get a chip configuration for an example chip:
00298         const ChipConfiguration& config=getFit(0)->getConfiguration().getChipConfiguration(0);
00299         auto_ptr<ResponseCurve> rc = ResponseCurveMap::getMap().get(config);
00300         float cal_charge_mV = getFit(0)->getConfiguration().getChipConfiguration(0).getCalCharge()*2.5 ;
00301         float cal_charge_fC = rc->getFunction()->Eval(cal_charge_mV);
00302 
00303         result->charge = cal_charge_fC;
00304         result->type = allowTrimRangeVariation() ? (unsigned)-1 : best_range_for_chip[0] ;
00305         result->algorithm = allowTrimTargetVariation() ? 1 : 0 ;
00306     }
00307 
00308     for (unsigned ichip=0; ichip<nChipModule; ++ichip) {
00309         if (debug)
00310             cout << "  CHIP " << ichip << endl;
00311         unsigned best_range  = best_range_for_chip[ichip];
00312         unsigned best_target = best_target_for_chip[ichip];
00313 
00314         if (debug)
00315             cout << "    optimum range = " <<  best_range
00316             << ", target #" << best_target
00317             << " = " << target[best_target]
00318             << ", ntrim=" << trimArray[best_range][ichip][best_target_for_chip[ichip]]->channelTrim.n() << endl;
00319 
00320         result->chipTrim[ichip] =
00321             trimArray [best_range] [ichip] [best_target];
00322         result->chipTrimData[ichip]=
00323             dataArray[best_range] [ichip];
00324 
00325         const Stats<double> offsets = dataArray[best_range][ichip]->getOffsets();
00326         const Stats<double> steps   = dataArray[best_range][ichip]->getSteps();
00327 
00328         double stepmin=0, stepmax=16;
00329         switch (all_ranges[best_range].range) {
00330         case 0 :
00331             stepmin = 1.5 ;
00332             stepmax = 5.0 ;
00333             break;
00334         case 1 :
00335             stepmin = 5.0 ;
00336             stepmax = 8.5 ;
00337             break;
00338         case 2 :
00339             stepmin = 8.5 ;
00340             stepmax = 12.0;
00341             break;
00342         case 3 :
00343             stepmin = 12.0;
00344             stepmax = 15.5;
00345             break;
00346         }
00347         // Chip Defects:
00348         if (steps.mean() < stepmin || steps.mean() > stepmax ) {
00349             result->getDefects().addDefect(Defect(StandardDefects::TR_RANGE, ModuleElement::Chip(ichip)));
00350         }
00351         // Channel Defects:
00352         for (unsigned iChanInChip=0; iChanInChip<nChannelChip ; ++iChanInChip) {
00353             // Step Defects:
00354             if (steps.getAt(iChanInChip).valid &&
00355                     ( fabs( steps.getAt(iChanInChip).value - steps.mean() )
00356                       > StandardDefects::TR_STEP.getParameter() * sqrt(steps.var() ) ) ) {
00357                 result->getDefects().addDefect(Defect(StandardDefects::TR_STEP,
00358                                                ModuleElement::Channel(ichip*nChannelChip+iChanInChip)));
00359             }
00360             // Offset Defects:
00361             if (offsets.getAt(iChanInChip).valid &&
00362                     ( fabs( offsets.getAt(iChanInChip).value - offsets.mean() )
00363                       > StandardDefects::TR_OFFSET.getParameter() * sqrt(offsets.var() ) ) ) {
00364                 result->getDefects().addDefect(Defect(StandardDefects::TR_OFFSET,
00365                                                ModuleElement::Channel(ichip*nChannelChip+iChanInChip)));
00366             }
00367             // Untrimmable Defects:
00368             if (result->chipTrim[ichip]->channelTrim.getAt(iChanInChip).valid==false) {
00369                 result->getDefects().addDefect(Defect(StandardDefects::TR_NOTRIM, 
00370                                        ModuleElement::Channel(ichip*nChannelChip + iChanInChip)));
00371             } // if valid
00372         }// Channel loop
00373     }// chip loop
00374 
00375     //Check for defects - fail if more than 15 masked channels or more than 7 consecutive masked channels
00376     //Problem if 1 masked channel
00377     const DefectList::DefectCollection& defects = result->getDefects().getAllDefects();
00378     bool passed = true;
00379     unsigned int totalChannels = 0;
00380     for (DefectList::DefectCollection::const_iterator i=defects.begin(); i!=defects.end(); ++i) {
00381     if (i->getPrototype().getSeverity() >= UNUSEABLE) {
00382         totalChannels += i->getModuleElement().getNChannels();
00383         if (i->getModuleElement().getNChannels()>7) {
00384         passed = false;
00385         break;
00386         }
00387     }
00388     }
00389     
00390     if (totalChannels > 15) passed = false;
00391     if (totalChannels > 0) result->setProblem(true);
00392     
00393     result->setPassed(passed);
00394 }
00395 
00396 
00397 /* fit the trim data with a line */
00398 void TrimRangeAlgorithm::doTrimDataFit(TrimRangeTestResult::ChipTrimData& chipData) throw() {
00399     for (unsigned iChanInChip=0; iChanInChip<nChannelChip; ++iChanInChip) {
00400         Stat<TrimRangeTestResult::TrimData>& trimdata = chipData.channelData.modifyAt(iChanInChip);
00401 
00402         trimdata.valid=false;
00403         trimdata.value.p0 = 0;
00404         trimdata.value.p1 = 0;
00405 
00406         if (trimdata.value.graph.empty())
00407             continue;
00408 
00410         float xbar=0, ybar=0, x2bar=0, xybar=0;
00411         const unsigned n=trimdata.value.graph.size();
00412 
00413         for (unsigned ipoint = 0; ipoint<n; ++ipoint) {
00414             const float& x=trimdata.value.graph[ipoint].first;
00415             const float& y=trimdata.value.graph[ipoint].second;
00416             xbar  += x;
00417             ybar  += y;
00418             x2bar += x*x;
00419             xybar += x*y;
00420         }
00421 
00422         const float det = n*x2bar - xbar*xbar;
00423         if (det>0) {
00424             trimdata.value.p0 = (x2bar*ybar - xbar*xybar) / det;
00425             trimdata.value.p1 = (n*xybar - xbar*ybar) / det;
00426             trimdata.valid=trimdata.value.p1>0;
00427         }
00428     
00429     }
00430 }
00431 
00432 /* interpreet the incoming data as a series of scans over different ranges */
00433 std::vector<TrimRangeAlgorithm::TrimRange> TrimRangeAlgorithm::createRanges(const TestResult& result) throw() {
00434     // Check all chips in each scan have the same range.
00435     // if they don't, then analyse all ranges together.
00436     std::vector<TrimRangeAlgorithm::TrimRange> all_ranges;
00437 
00438     bool inputRangeVariesChipToChip=false;
00439 
00440     for (unsigned iscan=0; iscan<result.getNScans(); ++iscan) {
00441         shared_ptr<const FitScanResult> fitted = getFit(iscan);
00442         for (unsigned ichip=1; ichip<nChipModule; ichip++) {
00443             if (fitted->getConfiguration().getChipConfiguration(ichip).getTrimRange()
00444                     != fitted->getConfiguration().getChipConfiguration(0).getTrimRange()) {
00445                 inputRangeVariesChipToChip=true;
00446             }
00447         }
00448     }
00449 
00450     for (unsigned iscan=0; iscan<result.getNScans(); ++iscan) {
00451         shared_ptr<const FitScanResult> fitted = getFit(iscan);
00452 
00453         // find the trim range for module 0
00454         int range_value=fitted->getConfiguration().getChipConfiguration(0).getTrimRange();
00455         // find the trim for channel 0
00456         int trim_value=fitted->getConfiguration().getChipConfiguration(0).getTrim(0);
00457 
00458         std::vector<TrimRange>::iterator it = all_ranges.begin();
00459         while (it!=all_ranges.end()) {
00460             if ( (*it).range == range_value ) {
00461                 break;
00462             }
00463             ++it;
00464         }
00465         if (it==all_ranges.end() ) { // new trim range?
00466             // if there is chip to chip range variation, treat it all as one range
00467             if ( !inputRangeVariesChipToChip || all_ranges.empty() ) {
00468                 all_ranges.push_back(TrimRange(range_value));
00469             }
00470             it=all_ranges.end()-1;
00471         }
00472 
00473         // store in that range the trim value and the scan identifier.
00474         (*it).points.push_back(TrimPoint(trim_value, iscan) );
00475     }
00476     // sort by range value.
00477     sort(all_ranges.begin(), all_ranges.end());
00478     return all_ranges;
00479 }
00480 
00481 
00482 shared_ptr<const TrimRangeTestResult::ChipTrim> TrimRangeAlgorithm::getChipTrim(const TrimRangeTestResult::ChipTrimData& data, const float target, const short unsigned range) throw() {
00483 
00484     shared_ptr<SctData::TrimRangeTestResult::ChipTrim> chipTrim (new SctData::TrimRangeTestResult::ChipTrim() );
00485     chipTrim->target=target;
00486     chipTrim->range =range;
00487 
00488     for (unsigned iChanInChip=0; iChanInChip<nChannelChip; ++iChanInChip) {
00489     const Stat<TrimRangeTestResult::TrimData>& d= data.channelData.getAt(iChanInChip);
00490     Stat<TrimRangeTestResult::Trim>& t= chipTrim->channelTrim.modifyAt(iChanInChip);
00491     
00492     if (!d.valid) {
00493         t.valid=false;
00494         continue;
00495     }
00496     
00497     // record the trim value.
00498     int i_trim=d.value.getTrim(target);
00499     
00500     // check if trimmable - if not, then do your best
00501     // If trim is -1 or 16, we allow it (because going to the range above wont improve matters!)
00502     // But we set the trim to the maximum or minimum as appropriate
00503     if ( i_trim>=0 && i_trim<=15) {
00504         t.valid=true;
00505         t.value.trim = i_trim;
00506     } else if (i_trim==-1) {
00507         t.valid=true;
00508         t.value.trim = 0;
00509         if (debug) {
00510         cout << "Wanted trim of -1, setting to 0 and passing" << endl;
00511         }
00512     } else if (i_trim==16) {
00513             t.valid=true;
00514         t.value.trim = 15;
00515         if (debug) {
00516         cout << "Wanted trim of 16, setting to 15 and passing" << endl;
00517         }
00518     } else {
00519             t.valid=false;
00520         if (debug && target>85. && target<95) {
00521         cout << "Couldn't trim channel: " << iChanInChip << " i_trim: " << i_trim << " for target: " << target << endl;
00522         cout << "Vt50 min: " << d.value.getVthr(0) << "Vt50 max: " << d.value.getVthr(15) << endl;
00523         }
00524             if (i_trim<0)
00525                 t.value.trim = 0;
00526             if (i_trim>15)
00527                 t.value.trim = 15;
00528         }
00529     
00530     //Don't use i_trim - we want the actual threshold that will be set in case i_trim<0 or >15
00531         t.value.vthr = d.value.getVthr(t.value.trim);
00532     }
00533     return chipTrim;
00534 }
00535 
00536 
00537 } // end of namespace SctAnalysis

Generated on Thu Jul 15 09:51:01 2004 for SCT DAQ/DCS Software - C++ by doxygen 1.3.5