@@ -3206,7 +3206,7 @@ struct N42DecodeHelper2012
32063206 }// std::string concat_2012_N42_characteristic_node( const rapidxml::xml_node<char> *node )
32073207
32083208
3209- static void set_deriv_data ( shared_ptr<Measurement> &meas, const string &dd_id, const string &spec_id )
3209+ static void set_deriv_data ( const shared_ptr<Measurement> &meas, const string &dd_id, const string &spec_id )
32103210 {
32113211 typedef Measurement::DerivedDataProperties DerivedProps;
32123212
@@ -7927,7 +7927,7 @@ namespace SpecUtils
79277927
79287928 get_2012_N42_energy_calibrations ( calibrations, rad_data_node, remarks_, parse_warnings_ );
79297929
7930- // XXX - implement using RadItemInformation
7930+ // TODO: implement using RadItemInformation
79317931 // for( const rapidxml::xml_node<char> *rad_item_node = XML_FIRST_NODE(rad_data_node, "RadItemInformation");
79327932 // rad_item_node;
79337933 // rad_item_node = XML_NEXT_TWIN(rad_item_node) )
@@ -8182,25 +8182,52 @@ namespace SpecUtils
81828182 size_t numRadMeasNodes = 0 ;
81838183 std::mutex meas_mutex, calib_mutex;
81848184
8185+ // Symetrica detectors _may_ have a <RadMeasurement> node with id="ForegroundMeasureSum",
8186+ // "BackgroundMeasure...5" (for gamma), "BackgroundMeasure...6" (for neutron),
8187+ // "CalMeasurementGamma-...", "StabMeasurement...", and then they have a bunch
8188+ // of ("TickMeasurement..."|"ForegroundMeasure...") that are 0.2 seconds long.
8189+ // We probably want the "ForegroundMeasureSum" + "BackgroundMeasure..." (neut+gamma
8190+ // combined) - so we will separate all these from the "Tick" measurements, by
8191+ // marking them as derived (which I guess they kinda are).
8192+ // Its not great, but maybe better than nothing.
8193+ // A particular example where this was giving trouble can be seen in ref3Z3LPD6CY6
8194+ const bool is_symetrica = SpecUtils::istarts_with ( manufacturer_, " symetrica" );
8195+ vector<shared_ptr<vector<shared_ptr<Measurement>>>> symetrica_special_meas;
8196+ vector<pair<string,string>> symetrica_special_attribs;
8197+
8198+
8199+
81858200 // See note above about system that has multiple N42 documents in a single file.
81868201 for ( const rapidxml::xml_node<char > *rad_data_node = data_node; rad_data_node;
81878202 rad_data_node = rad_data_node->next_sibling (" RadInstrumentData" ) )
81888203 {
81898204 XML_FOREACH_CHILD ( meas_node, rad_data_node, " RadMeasurement" )
81908205 {
8191- // see ref3Z3LPD6CY6
8192- if ( numRadMeasNodes > 32 && xml_value_compare (meas_node->first_attribute (" id" ), " ForegroundMeasureSum" ) )
8193- {
8194- continue ;
8195- }
8196-
81978206 std::shared_ptr<std::mutex> mutexptr = std::make_shared<std::mutex>();
81988207 auto these_meas = std::make_shared< vector<std::shared_ptr<Measurement> > >();
81998208
82008209 ++numRadMeasNodes;
82018210 meas_mutexs.push_back ( mutexptr );
82028211 measurements_each_meas.push_back ( these_meas );
82038212
8213+ if ( is_symetrica )
8214+ {
8215+ const rapidxml::xml_attribute<char > *id_attrib = meas_node->first_attribute (" id" );
8216+ const string id_val_str = xml_value_str ( id_attrib );
8217+
8218+ if ( SpecUtils::istarts_with (id_val_str, " ForegroundMeasureSum" )
8219+ || SpecUtils::istarts_with (id_val_str, " BackgroundMeasure" )
8220+ || SpecUtils::istarts_with (id_val_str, " StabMeasurement" )
8221+ || SpecUtils::istarts_with (id_val_str, " CalMeasurementGamma" )
8222+ )
8223+ {
8224+ symetrica_special_meas.push_back ( these_meas );
8225+
8226+ const string spec_id = xml_value_str ( XML_FIRST_NODE (meas_node, " Spectrum" ) );
8227+ symetrica_special_attribs.push_back ( std::make_pair (id_val_str, spec_id) );
8228+ }//
8229+ }// if( is_symetrica )
8230+
82048231 workerpool.post ( [these_meas,meas_node,&id_to_dettype,&calibrations,mutexptr,&calib_mutex](){
82058232 N42DecodeHelper2012::decode_2012_N42_rad_measurement_node ( *these_meas, meas_node, &id_to_dettype, &calibrations, *mutexptr, calib_mutex );
82068233 } );
@@ -8223,6 +8250,100 @@ namespace SpecUtils
82238250
82248251 workerpool.join ();
82258252
8253+
8254+ // Now go back up and mark Symetrica special measurements as derived, so this way the
8255+ // "raw" and "derived" datas will kinda be sensical.
8256+ assert ( symetrica_special_meas.size () == symetrica_special_attribs.size () );
8257+ if ( !symetrica_special_meas.empty () )
8258+ {
8259+ // We will look for a background neutron-only record, and a background gamma-only
8260+ // record, and combine them, if found (and then remove from `measurements_each_meas`)
8261+ // TODO: For multi-detector systems (portals) we should actually try to match the neutrons
8262+ // to gammas for each detector, but we arent doing that yet
8263+ bool single_gamma_neutron_back = true ;
8264+ shared_ptr<Measurement> neut_only_back, gamma_only_back;
8265+
8266+ for ( size_t sym_index = 0 ;
8267+ single_gamma_neutron_back && (sym_index < symetrica_special_meas.size ()); ++sym_index )
8268+ {
8269+ if ( !symetrica_special_meas[sym_index] || (sym_index >= symetrica_special_attribs.size ()) )
8270+ continue ;
8271+
8272+ const shared_ptr<vector<shared_ptr<Measurement>>> &measv = symetrica_special_meas[sym_index];
8273+ const pair<string,string> &attribs = symetrica_special_attribs[sym_index];
8274+
8275+ for ( const shared_ptr<Measurement> &m : *measv )
8276+ {
8277+ N42DecodeHelper2012::set_deriv_data ( m, attribs.first , attribs.second );
8278+ if ( m->source_type () == SourceType::Background )
8279+ {
8280+ if ( (m->num_gamma_channels () > 6 ) && !m->contained_neutron () )
8281+ {
8282+ single_gamma_neutron_back = (single_gamma_neutron_back && !gamma_only_back);
8283+ gamma_only_back = m;
8284+ }else if ( !m->num_gamma_channels () && m->contained_neutron () )
8285+ {
8286+ single_gamma_neutron_back = (single_gamma_neutron_back && !neut_only_back);
8287+ neut_only_back = m;
8288+ }
8289+ if ( !single_gamma_neutron_back )
8290+ break ;
8291+ }// if( m->source_type() == SourceType::Background )
8292+ }// for( const shared_ptr<Measurement> &m : *measv )
8293+ }// for( const auto &meass : symetrica_special_meas )
8294+
8295+ if ( single_gamma_neutron_back && gamma_only_back && neut_only_back )
8296+ {
8297+ // Below is commented out 20231004 - only leaving in for a short while to look at different variants
8298+ // cout << "Symetrica\n\tNeutron:"
8299+ // "\n\t\tstart_time=" << to_iso_string( neut_only_back->start_time() )
8300+ // << "\n\t\tRealTime=" << neut_only_back->real_time()
8301+ // << "\n\t\tLiveTime=" << neut_only_back->live_time()
8302+ // << "\n\tGamma:"
8303+ // << "\n\t\tstart_time=" << to_iso_string( gamma_only_back->start_time() )
8304+ // << "\n\t\tRealTime=" << gamma_only_back->real_time()
8305+ // << "\n\t\tLiveTime=" << gamma_only_back->live_time()
8306+ // << "\nDiffs:"
8307+ // << "\n\t\tStartTime: " << chrono::duration_cast<chrono::microseconds>(neut_only_back->start_time() - gamma_only_back->start_time()).count() << " us"
8308+ // << "\n\t\tRealTime: " << (neut_only_back->real_time() - gamma_only_back->real_time())
8309+ // << "\n\t\tLiveTime: " << (neut_only_back->live_time() - gamma_only_back->live_time())
8310+ // << endl;
8311+
8312+ // The neutron background isnt always at the same time, or same duration, so dont combine
8313+ // in these cases
8314+ const float gamma_rt = gamma_only_back->real_time ();
8315+ if ( ((neut_only_back->start_time () - gamma_only_back->start_time ()) < chrono::seconds (15 )) // Arbitrary 15 seconds
8316+ && fabs ((neut_only_back->real_time () - gamma_rt) < 0.01 *gamma_rt) ) // Arbitrary 1% match
8317+ {
8318+ gamma_only_back->contained_neutron_ = true ;
8319+ gamma_only_back->remarks_ .push_back ( " Neutron and gamma backgrounds have been combined into a single record." );
8320+ gamma_only_back->remarks_ .push_back ( " Neutron Real Time: PT" + to_string (neut_only_back->real_time ()) + " S" );
8321+ gamma_only_back->remarks_ .push_back ( " Neutron Live Time: PT" + to_string (neut_only_back->live_time ()) + " S" );
8322+
8323+ gamma_only_back->neutron_counts_ = neut_only_back->neutron_counts_ ;
8324+ gamma_only_back->neutron_counts_sum_ = neut_only_back->neutron_counts_sum_ ;
8325+
8326+ if ( (gamma_only_back->dose_rate_ >= 0.0 ) && (neut_only_back->dose_rate_ >= 0.0 ) )
8327+ gamma_only_back->dose_rate_ += neut_only_back->dose_rate_ ;
8328+ if ( (gamma_only_back->exposure_rate_ >= 0.0 ) && (neut_only_back->exposure_rate_ >= 0.0 ) )
8329+ gamma_only_back->exposure_rate_ += neut_only_back->exposure_rate_ ;
8330+
8331+ for ( auto miter = begin (measurements_each_meas); miter != end (measurements_each_meas); ++miter )
8332+ {
8333+ auto &measv = **miter;
8334+ auto pos = std::find ( begin (measv), end (measv), neut_only_back );
8335+ if ( pos != end (measv) )
8336+ {
8337+ measv.erase ( pos );
8338+ break ;
8339+ }// if( pos != end(measv) )
8340+ }
8341+ }// if( gamma/neutron start and real times are reasonably close )
8342+ }// if( gamma_only_back && neut_only_back )
8343+ }// if( !symetrica_special_meas.empty() )
8344+
8345+
8346+
82268347 for ( size_t i = 0 ; i < measurements_each_meas.size (); ++i )
82278348 for ( size_t j = 0 ; j < measurements_each_meas[i]->size (); ++j )
82288349 measurements_.push_back ( (*measurements_each_meas[i])[j] );
0 commit comments