Exemple #1
0
    def set_xml_dataset(self):
        def get_path():
            return '/Users/ross/Sandbox/11C2815.xml'

            dlg = FileDialog(action='open',
                             default_directory=paths.data_dir,
                             wildcard='*.db')
            if dlg.open():
                if dlg.path:
                    return dlg.path

        self._set_prev_db_kind()

        p = get_path()
        self.info('Setting analysis backend to {}'.format(p))
        self.db = XMLDatabase(path=p)

        # monkey patch make_analyses
        self.make_analyses = self.db.make_analyses
Exemple #2
0
    def set_xml_dataset(self):
        def get_path():
            return '/Users/ross/Sandbox/11C2815.xml'

            dlg = FileDialog(action='open',
                             default_directory=paths.data_dir,
                             wildcard='*.db')
            if dlg.open():
                if dlg.path:
                    return dlg.path

        self._set_prev_db_kind()

        p = get_path()
        self.info('Setting analysis backend to {}'.format(p))
        self.db = XMLDatabase(path=p)

        # monkey patch make_analyses
        self.make_analyses = self.db.make_analyses
Exemple #3
0
class Processor(IsotopeDatabaseManager):
    def set_xml_dataset(self):
        def get_path():
            return '/Users/ross/Sandbox/11C2815.xml'

            dlg = FileDialog(action='open',
                             default_directory=paths.data_dir,
                             wildcard='*.db')
            if dlg.open():
                if dlg.path:
                    return dlg.path

        self._set_prev_db_kind()

        p = get_path()
        self.info('Setting analysis backend to {}'.format(p))
        self.db = XMLDatabase(path=p)

        # monkey patch make_analyses
        self.make_analyses = self.db.make_analyses

        # self.db.trait_set(kind='sqlite',
        # path=get_path())
        # self.db.connect()

    def set_sqlite_dataset(self):
        def get_path():
            return '/Users/ross/Sandbox/exporttest2.db'
            dlg = FileDialog(action='open',
                             default_directory=paths.data_dir,
                             wildcard='*.db')
            if dlg.open():
                if dlg.path:
                    return dlg.path

        self._set_prev_db_kind()
        self.db.trait_set(kind='sqlite', path=get_path())
        self.db.connect()

    def get_adjacent_analysis(self, timestamp, previous, **kw):
        db = self.db
        with db.session_ctx():
            an = db.get_adjacent_analysis(timestamp, previous)
            return self.make_analysis(an, **kw)

    def unique_id(self, vs, *args):
        return unique_id(vs, *args)

    def find_references(self, unks):
        ans = []
        proc = self.processor
        uuids = []

        with proc.db.session_ctx():
            n = len(self.analyses)

            progress = None
            if n > 1:
                progress = proc.open_progress(n + 1)

            for ui in unks:
                if progress:
                    progress.change_message(
                        'Finding associated analyses for {}'.format(
                            ui.record_id))

                fans = self.find_associated_analyses(ui, exclude_uuids=uuids)

                nans = [ai for ai in fans if ai.uuid not in uuids]
                ans.extend(nans)
                nuuids = [ai.uuid for ai in nans]
                uuids.extend(nuuids)

            self.debug('find references pre make')

            ans = self.processor.make_analyses(ans, progress=progress)
            ans = sorted(ans, key=lambda x: x.analysis_timestamp)

            if progress:
                progress.soft_close()

            self.debug('find references finished')
            return ans

    def find_associated_analyses(self,
                                 analysis,
                                 delta=12,
                                 limit=10,
                                 atype=None,
                                 **kw):
        """
            find atype analyses +/- delta hours (12 hours default)
            if atype is None use "blank_{analysis.analysis_type}"
        """
        # if not isinstance(analysis, Analysis):
        # analysis = self.make_analysis(analysis)
        #             analysis.load_isotopes()

        ms = analysis.mass_spectrometer
        post = analysis.timestamp
        if isinstance(post, float):
            post = datetime.fromtimestamp(post)

        if atype is None:
            atype = 'blank_{}'.format(analysis.analysis_type)

        dt = timedelta(hours=delta)
        lpost = post - dt
        hpost = post + dt

        return self._filter_analyses(ms, lpost, hpost, limit, atype, **kw)
        # br = self._find_analyses(ms, post, -delta, atype, **kw)
        # ar = self._find_analyses(ms, post, delta, atype, **kw)
        # return br + ar

    def group_labnumbers(self, ls):
        def monitor_filter(pos):
            return pos.sample.name == 'FC-2'

        return partition(ls, monitor_filter)

    def group_level(self,
                    level,
                    irradiation=None,
                    monitor_filter=None,
                    ret_dbrecord=False):
        if monitor_filter is None:

            def monitor_filter(pos):
                return pos.sample == 'FC-2'

        db = self.db
        with db.session_ctx():
            if isinstance(level, str):
                level = db.get_level(level, irradiation)

            refs = []
            unks = []
            if level:
                positions = level.positions

                if positions:
                    if ret_dbrecord:
                        pos_factory = lambda x: x
                    else:

                        def pos_factory(px):
                            ip = IrradiationPositionRecord()
                            ip.create(px)
                            return ip

                    positions = [pos_factory(pp) for pp in positions]
                    refs, unks = partition(positions, monitor_filter)

        return refs, unks

    def analysis_series(self,
                        ms,
                        ed=None,
                        analysis_type=None,
                        weeks=0,
                        days=0,
                        hours=0,
                        limit=None):

        self.debug('atype={} ms={} ed={}'.format(analysis_type, ms, ed))
        db = self.db
        with db.session_ctx() as sess:
            q = sess.query(meas_AnalysisTable)
            q = q.join(meas_MeasurementTable)
            q = q.join(gen_MassSpectrometerTable)
            if analysis_type:
                q = q.join(gen_AnalysisTypeTable)

            if ed:
                q = q.join(meas_ExtractionTable)
                q = q.join(gen_ExtractionDeviceTable)

            d = datetime.today()
            today = datetime.today()
            d = d - timedelta(hours=hours, weeks=weeks, days=days)

            self.debug('Date Range >={}'.format(d))

            attr = meas_AnalysisTable.analysis_timestamp
            q = q.filter(and_(attr <= today, attr >= d))
            q = q.filter(gen_MassSpectrometerTable.name == ms)

            if analysis_type:
                q = q.filter(gen_AnalysisTypeTable.name == analysis_type)
            if ed:
                q = q.filter(gen_ExtractionDeviceTable.name == ed)

            q = q.order_by(attr.desc())
            if limit:
                q = q.limit(limit)

            return self._make_analyses_from_query(q)

    # ===============================================================================
    # corrections
    # ===============================================================================
    def add_history(self, dbrecord, kind, **kw):
        db = self.db
        return db.add_history(dbrecord, kind, **kw)

    def apply_fixed_correction(self, history, isotope, value, error,
                               correction_name):
        func = getattr(self.db, 'add_{}'.format(correction_name))
        func(history,
             isotope=isotope,
             use_set=False,
             user_value=value,
             user_error=error)

    def apply_fixed_value_correction(self, phistory, history, fit_obj,
                                     correction_name):
        db = self.db
        if phistory:
            bs = getattr(phistory, correction_name)
            bs = reversed(bs)
            prev = next((bi for bi in bs if bi.isotope == fit_obj.name), None)
            if prev:
                uv = prev.user_value
                ue = prev.user_error
                func = getattr(db, 'add_{}'.format(correction_name))
                func(history,
                     isotope=prev.isotope,
                     fit=prev.fit,
                     user_value=uv,
                     user_error=ue)

    def apply_session_blank_history(self, analysis, hist_id):
        db = self.db
        with db.session_ctx():
            hist = db.get_blank_history(hist_id)
            if hist.action:
                session = hist.action.session
                if session:
                    histories = db.get_session_blank_histories(session)
                    if histories:
                        for hi in histories:
                            an = hi.analysis
                            an.selected_histories.selected_blanks_id = hi
                            self.remove_from_cache(an)

    def apply_blank_history(self, analysis, hist_id):
        db = self.db
        with db.session_ctx():
            an = db.get_analysis_uuid(analysis.uuid)
            self.debug('setting {} blank history_id to {}. '
                       'table=proc_BlanksHistoryTable'.format(
                           an.record_id, hist_id))
            cid = an.selected_histories.selected_blanks_id
            self.debug('current blank id={} >> {}'.format(cid, hist_id))
            an.selected_histories.selected_blanks_id = hist_id

            analysis.sync_blanks(an)

    def add_predictor_valueset(self, refs, ss, es, dbblank):
        db = self.db
        # with db.session_ctx():
        # refs = [db.get_analysis_uuid(ri.uuid) for ri in refs]
        refs = db.get_analyses_uuid([ri.uuid for ri in refs], attr='id')
        for ri, vi, ei in zip(refs, ss, es):
            db.add_blank_set_value_table(vi, ei, dbblank, ri[0])

    def apply_correction(self, history, analysis, fit_obj, set_id, kind):
        # meas_analysis = self.db.get_analysis_uuid(analysis.uuid)

        func = getattr(self, '_apply_{}_correction'.format(kind))
        return func(history, analysis, fit_obj, set_id)

    def add_predictor_set(self, predictors, kind):
        set_id = None
        if predictors:
            db = self.db

            # make set_id
            dbrs = db.get_analyses_uuid([p.uuid for p in predictors],
                                        analysis_only=True)

            # set_id = hash(tuple((ai.id for ai in dbrs)))
            set_id = self.unique_id([ai.id for ai in dbrs])

            gfunc = getattr(db, 'get_{}_set'.format(kind))
            if not gfunc(set_id):
                func = getattr(db, 'add_{}_set'.format(kind))
                for dbr in dbrs:
                    func(dbr, set_id=set_id)

        return set_id

    # private
    def _apply_detector_intercalibration_correction(self, history, analysis,
                                                    fit_obj, set_id):
        n, d = fit_obj.name.split('/')

        iso = analysis.get_isotope(detector=d)
        if not iso:
            self.debug('************************* {} no detector {}'.format(
                analysis.record_id, d))
            return

        ic = iso.temporary_ic_factor
        # ic = analysis.get_isotope(detector=d).temporary_ic_factor
        if ic is None:
            self.debug(
                '************************* no ic factor for {} {}'.format(
                    analysis.record_id, fit_obj))
            return

        ic_v, ic_e = map(float, ic)

        # copy temp ic_factor to ic_factor
        iso.ic_factor = ufloat(ic_v, ic_e)

        db = self.db
        db.add_detector_intercalibration(history,
                                         detector=d,
                                         user_value=ic_v,
                                         user_error=ic_e,
                                         fit=fit_obj.fit,
                                         set_id=set_id)

    def _apply_blanks_correction(self, history, analysis, fit_obj, set_id):
        if not fit_obj.name in analysis.isotopes:
            return

        ss = analysis.isotopes[fit_obj.name]

        # ss=next((iso for iso in analysis.isotopes
        #        if iso.kind=='signal' and iso.molecular_weight.name==fit_obj.name), None)
        '''
            the blanks_editor may have set a temporary blank
            use that instead of the saved blank
        '''
        if hasattr(ss, 'temporary_blank'):
            blank = ss.temporary_blank
            ss.blank = blank
        else:
            blank = ss.blank

        db = self.db
        return db.add_blanks(history,
                             isotope=fit_obj.name,
                             user_value=float(blank.value),
                             user_error=float(blank.error),
                             fit=fit_obj.fit,
                             set_id=set_id)

        #        ps = self.interpolation_correction.predictors
        # if predictors:
        #
        #     lns = set([si.labnumber for si in predictors])
        #     ids = [si.uuid for si in predictors]
        #
        #     def f(t, t2):
        #         return t2.identifier.in_(lns), t.uuid.in_(ids)
        #
        #     ans = db.get_analyses(uuid=f)
        #     for ai in ans:
        #         db.add_blanks_set(item, ai)
        #for pi in predictors:
        #    dbr = db.get_analysis_uuid(pi.uuid)
        #    #                 self.db.add_blanks_set(item, pi.dbrecord)
        #    db.add_blanks_set(item, dbr)

    def _set_prev_db_kind(self):
        # the db's connection paramaters are bound to the ConnectionPreferences.
        # this binding is two way so by setting kind='sqlite', kind in the preferences is also changed
        # easiest solution is to save the current kind value and when the database plugin stops
        # set kind to the old value

        from pychron.globals import globalv
        if self.db.kind:
            globalv.prev_db_kind = self.db.kind

    # def _find_analyses(self, ms, post, delta, atype, step=0.5, maxtries=10, **kw):
    #     if delta < 0:
    #         step = -step
    # def _find_analyses(self, ms, post, delta, atype, step=0.5, maxtries=10, **kw):
    # if delta < 0:
    #         step = -step
    #
    #     if isinstance(post, float):
    #         post = datetime.fromtimestamp(post)
    #
    #     for i in range(maxtries):
    #         win = timedelta(hours=delta + i * step)
    #         lpost = post - win
    #         hpost = post + win
    #         rs = self._filter_analyses(ms, lpost, hpost, 5, atype, **kw)
    #         if rs:
    #             return rs
    #     else:
    #         return []

    def _filter_analyses(self,
                         ms,
                         lpost,
                         hpost,
                         lim,
                         at,
                         exclude_uuids=None,
                         filter_hook=None):
        """
            ms= spectrometer
            post= timestamp
            delta= time in hours
            at=analysis type

            if delta is negative
            get all before post and after post-delta

            if delta is post
            get all before post+delta and after post
        """
        sess = self.db.get_session()
        q = sess.query(meas_AnalysisTable)
        q = q.join(meas_MeasurementTable)
        q = q.join(gen_AnalysisTypeTable)
        q = q.join(gen_MassSpectrometerTable)

        # win = timedelta(hours=delta)
        # if isinstance(post, float):
        # post = datetime.fromtimestamp(post)
        #
        # dt = post + win
        # if delta < 0:
        #     a, b = dt, post
        # else:
        #     a, b = post, dt

        q = q.filter(
            and_(gen_AnalysisTypeTable.name == at,
                 meas_AnalysisTable.analysis_timestamp >= lpost,
                 meas_AnalysisTable.analysis_timestamp <= hpost,
                 gen_MassSpectrometerTable.name == ms))

        if filter_hook:
            q = filter_hook(q)

        if exclude_uuids:
            q = q.filter(not_(meas_AnalysisTable.uuid.in_(exclude_uuids)))

        q = q.order_by(meas_AnalysisTable.analysis_timestamp.desc())
        q = q.limit(lim)

        try:
            return q.all()
        except NoResultFound:
            pass

    def _make_analyses_from_query(self, q):
        ans = []
        try:
            self.debug('query count={}'.format(q.count()))
            ans = q.all()
        except Exception, e:
            import traceback

            traceback.print_exc()

        return self.make_analyses(ans, calculate_age=True)
        # ============= EOF =============================================


# def save_arar(self, analysis, meas_analysis):
#     with self.db.session_ctx():
#         hist = meas_analysis.selected_histories.selected_arar
#         if not hist:
#             hist = self.db.add_arar_history(meas_analysis)
#             arar = self.db.add_arar(hist)
#         else:
#             arar = hist.arar_result
#
#         #force analysis to recalculate age
#         #potentially redundant
#         analysis.calculate_age(force=True)
#
#         units = analysis.arar_constants.age_scalar
#
#         attr = ('Ar40', 'Ar39', 'Ar38', 'Ar37', 'Ar36',
#                 'rad40', 'cl36', 'ca37', 'k39')
#         for ai in attr:
#             a = getattr(analysis, ai)
#             v, e = float(a.nominal_value), float(a.std_dev)
#             setattr(arar, ai, v)
#             setattr(arar, '{}_err'.format(ai), e)
#
#         age = analysis.age * units
#         v, e = age.nominal_value, age.std_dev
#
#         je = analysis.age_error_wo_j * units
#
#         arar.age = float(v)
#         arar.age_err = float(e)
#         arar.age_err_wo_j = je
#
#         #update arar_history timestamp
#         arar.history.create_date = datetime.now()

#     def new_ideogram2(self, ans, plotter_options=None):
#         '''
#             return a plotcontainer
#         '''
#
#         probability_curve_kind = 'cumulative'
#         mean_calculation_kind = 'weighted_mean'
#         data_label_font = None
#         metadata_label_font = None
# #        highlight_omitted = True
#         display_mean_indicator = True
#         display_mean_text = True
#
#         p = Ideogram(
# #                     db=self.db,
# #                     processing_manager=self,
#                      probability_curve_kind=probability_curve_kind,
#                      mean_calculation_kind=mean_calculation_kind
#                      )
#         options = dict(
#                        title='',
#                        data_label_font=data_label_font,
#                        metadata_label_font=metadata_label_font,
#                        display_mean_text=display_mean_text,
#                        display_mean_indicator=display_mean_indicator,
#                        )
#
#         if plotter_options is None:
#             pom = IdeogramOptionsManager()
#             plotter_options = pom.plotter_options
#
#         if ans:
# #             self.analyses = ans
#             gideo = p.build(ans, options=options,
#                             plotter_options=plotter_options)
#             if gideo:
#                 gideo, _plots = gideo
#
#             return gideo, p
#     def new_spectrum(self, ans, plotter_options=None):
#         pass
#
#         p = Spectrum()
#
#         if plotter_options is None:
#             pom = SpectrumOptionsManager()
#             plotter_options = pom.plotter_options
#
#         options = {}
#
#         self._plotter_options = plotter_options
#         if ans:
# #             self.analyses = ans
#             gspec = p.build(ans, options=options,
#                             plotter_options=plotter_options)
#             if gspec:
#                 gspec, _plots = gspec
#
#             return gspec, p
#def load_sample_analyses(self, labnumber, sample, aliquot=None):
#    db = self.db
#    sess = db.get_session()
#    q = sess.query(meas_AnalysisTable)
#    q = q.join(gen_LabTable)
#    q = q.join(gen_SampleTable)
#
#    q = q.filter(gen_SampleTable.name == sample)
#    if aliquot is not None:
#        q = q.filter(meas_AnalysisTable.aliquot == aliquot)
#
#    if sample == 'FC-2':
#        q = q.filter(gen_LabTable.identifier == labnumber)
#
#    #        q = q.limit(10)
#    return self._make_analyses_from_query(q)

#def _make_analyses_from_query(self, q):
#    ans = None
#    try:
#        ans = q.all()
#        self.debug('{}'.format(ans))
#    except Exception, e:
#        import traceback
#
#        traceback.print_exc()
#
#    if ans:
#        ans = self.make_analyses(ans)
#        return ans

#     def auto_blank_fit(self, irradiation, level, kind):
#         if kind == 'preceding':
#             '''
#             1. supply a list of labnumbers/ supply level and extract labnumbers (with project minnabluff)
#             2. get all analyses for the labnumbers
#             3. sort analyses by run date
#             4. calculate blank
#                 1. preceding/bracketing
#                     get max 2 predictors
#
#                 2. fit
#                     a. group analyses by run date
#                     b. get n predictors based on group date
#             5. save blank
#             '''
#             db = self.db
#             level = db.get_irradiation_level(irradiation, level)
#
#             labnumbers = [pi.labnumber for pi in level.positions
#                             if pi.labnumber.sample.project.name in ('j', 'Minna Bluff', 'Mina Bluff')]
#             ans = [ai
#                     for ln in labnumbers
#                         for ai in ln.analyses
#                         ]
#             pd = self.open_progress(n=len(ans))
#             for ai in ans:
#                 self.preceding_blank_correct(ai, pd=pd)
#             db.commit()

#def refit_isotopes(self, meas_analysis, pd=None, fits=None, keys=None, verbose=False):
#
##         if not isinstance(analysis, Analysis):
#    analysis = self.make_analysis(meas_analysis)
#
#    #         analysis.load_isotopes()
#    dbr = meas_analysis
#    #         dbr = analysis.dbrecord
#    if keys is None:
#        keys = [iso.molecular_weight.name for iso in dbr.isotopes
#                if iso.kind == 'signal']
#
#    '''
#        if spectrometer is map use all linear
#
#        if spectrometer is Jan or Obama
#            if counts >150 use parabolic
#            else use linear
#    '''
#    if fits is None:
#        if analysis.mass_spectrometer in ('pychron obama', 'pychron jan', 'jan', 'obama'):
#            n = 0
#            if keys:
#                n = analysis.isotopes[keys[0]].xs.shape[0]
#
#            if n >= 150:
#                fits = ['parabolic', ] * len(keys)
#            else:
#                fits = ['linear', ] * len(keys)
#
#        else:
#            fits = ['linear', ] * len(keys)
#
#    db = self.db
#
#    if not dbr.selected_histories:
#        db.add_selected_histories(dbr)
#        db.sess.flush()
#
#    msg = 'fitting isotopes for {}'.format(analysis.record_id)
#    if pd is not None:
#        pd.change_message(msg)
#    self.debug(msg)
#    dbhist = db.add_fit_history(dbr)
#    for key, fit in zip(keys, fits):
#        dbiso_baseline = next((iso for iso in dbr.isotopes
#                               if iso.molecular_weight.name == key and iso.kind == 'baseline'), None)
#        if dbiso_baseline:
#            if verbose:
#                self.debug('{} {}'.format(key, fit))
#
#            vv = analysis.isotopes[key]
#            baseline = vv.baseline
#            if not baseline:
#                continue
#
#            v, e = baseline.value, baseline.error
#            db.add_fit(dbhist, dbiso_baseline, fit='average_sem', filter_outliers=True,
#                       filter_outlier_std_devs=2)
#            db.add_isotope_result(dbiso_baseline, dbhist, signal_=float(v), signal_err=float(e))
#
#            dbiso = next((iso for iso in dbr.isotopes
#                          if iso.molecular_weight.name == key and iso.kind == 'signal'), None)
#            if dbiso:
#                vv = analysis.isotopes[key]
#                v, e = vv.value, vv.error
#
#                db.add_fit(dbhist, dbiso, fit=fit, filter_outliers=True,
#                           filter_outlier_std_devs=2)
#                db.add_isotope_result(dbiso, dbhist, signal_=float(v), signal_err=float(e))
#
#    if pd is not None:
#        pd.increment()
#def _get_preceding_analysis(self, ms, post, atype):
#    if isinstance(post, float):
#        post = datetime.datetime.fromtimestamp(post)
#
#    sess = self.db.get_session()
#    q = sess.query(meas_AnalysisTable)
#    q = q.join(meas_MeasurementTable)
#    q = q.join(gen_AnalysisTypeTable)
#    q = q.join(gen_MassSpectrometerTable)
#
#    q = q.filter(and_(
#        gen_AnalysisTypeTable.name == atype,
#        gen_MassSpectrometerTable.name == ms,
#        meas_AnalysisTable.analysis_timestamp < post,
#    )
#    )
#
#    q = q.order_by(meas_AnalysisTable.analysis_timestamp.desc())
#    try:
#        return q.first()
#    except NoResultFound:
#        pass
#def preceding_blank_correct(self, analysis, keys=None, pd=None):
#    from pychron.core.regression.interpolation_regressor import InterpolationRegressor
#
#    if not isinstance(analysis, Analysis):
#        analysis = self.make_analysis(analysis)
#        #             analysis.load_isotopes()
#
#    msg = 'applying preceding blank for {}'.format(analysis.record_id)
#    if pd is not None:
#        pd.change_message(msg)
#        pd.increment()
#
#    #         self.info(msg)
#    ms = analysis.mass_spectrometer
#
#    post = analysis.timestamp
#
#    #         delta = -5
#    atype = 'blank_{}'.format(analysis.analysis_type)
#
#    an = self._get_preceding_analysis(ms, post, atype)
#
#    if not an:
#        self.warning('no preceding blank for {}'.format(analysis.record_id))
#        return
#
#    ai = self.make_analyses(an)
#    #         ai.load_isotopes()
#
#    if keys is None:
#        keys = analysis.isotope_keys
#
#    kind = 'blanks'
#    history = self.add_history(an, kind)
#
#    fit = 'preceding'
#
#    reg = InterpolationRegressor(kind=fit)
#    for key in keys:
#    #             predictors = [ai for ai in br if key in ai.isotopes]
#        if key in ai.isotopes:
#            r_xs, r_y = (ai.timestamp,), (ai.isotopes[key].baseline_corrected_value(),)
#            #             if predictors:
#            #                 r_xs, r_y = zip(*[(ai.timestamp, ai.isotopes[key].baseline_corrected_value()
#            #                                           )
#            #                                         for ai in predictors])
#            r_ys, r_es = zip(*[(yi.nominal_value, yi.std_dev) for yi in r_y])
#
#            reg.trait_set(xs=r_xs,
#                          ys=r_ys,
#                          yserr=r_es,
#            )
#
#            fit_obj = Fit(name=key, fit=fit)
#            v, e = reg.predict(post), reg.predict_error(post)
#            analysis.set_blank(key, (v[0], e[0]))
#            self.apply_correction(history, analysis, fit_obj, [ai], kind)
Exemple #4
0
class Processor(IsotopeDatabaseManager):
    def set_xml_dataset(self):
        def get_path():
            return '/Users/ross/Sandbox/11C2815.xml'

            dlg = FileDialog(action='open',
                             default_directory=paths.data_dir,
                             wildcard='*.db')
            if dlg.open():
                if dlg.path:
                    return dlg.path

        self._set_prev_db_kind()

        p = get_path()
        self.info('Setting analysis backend to {}'.format(p))
        self.db = XMLDatabase(path=p)

        # monkey patch make_analyses
        self.make_analyses = self.db.make_analyses

        # self.db.trait_set(kind='sqlite',
        # path=get_path())
        # self.db.connect()

    def set_sqlite_dataset(self):
        def get_path():
            return '/Users/ross/Sandbox/exporttest2.db'
            dlg = FileDialog(action='open',
                             default_directory=paths.data_dir,
                             wildcard='*.db')
            if dlg.open():
                if dlg.path:
                    return dlg.path

        self._set_prev_db_kind()
        self.db.trait_set(kind='sqlite',
                          path=get_path())
        self.db.connect()

    def get_adjacent_analysis(self, timestamp, previous, **kw):
        db = self.db
        with db.session_ctx():
            an = db.get_adjacent_analysis(timestamp, previous)
            return self.make_analysis(an, **kw)

    def unique_id(self, vs, *args):
        return unique_id(vs, *args)

    def find_references(self, unks):
        ans = []
        proc = self.processor
        uuids = []

        with proc.db.session_ctx():
            n = len(self.analyses)

            progress = None
            if n > 1:
                progress = proc.open_progress(n + 1)

            for ui in unks:
                if progress:
                    progress.change_message('Finding associated analyses for {}'.format(ui.record_id))

                fans = self.find_associated_analyses(ui, exclude_uuids=uuids)

                nans = [ai for ai in fans if ai.uuid not in uuids]
                ans.extend(nans)
                nuuids = [ai.uuid for ai in nans]
                uuids.extend(nuuids)

            self.debug('find references pre make')

            ans = self.processor.make_analyses(ans, progress=progress)
            ans = sorted(ans, key=lambda x: x.analysis_timestamp)

            if progress:
                progress.soft_close()

            self.debug('find references finished')
            return ans

    def find_associated_analyses(self, analysis, delta=12, limit=10, atype=None, **kw):
        """
            find atype analyses +/- delta hours (12 hours default)
            if atype is None use "blank_{analysis.analysis_type}"
        """
        # if not isinstance(analysis, Analysis):
        # analysis = self.make_analysis(analysis)
        #             analysis.load_isotopes()

        ms = analysis.mass_spectrometer
        post = analysis.timestamp
        if isinstance(post, float):
            post = datetime.fromtimestamp(post)

        if atype is None:
            atype = 'blank_{}'.format(analysis.analysis_type)

        dt = timedelta(hours=delta)
        lpost = post - dt
        hpost = post + dt

        return self._filter_analyses(ms, lpost, hpost, limit, atype, **kw)
        # br = self._find_analyses(ms, post, -delta, atype, **kw)
        # ar = self._find_analyses(ms, post, delta, atype, **kw)
        # return br + ar

    def group_labnumbers(self, ls):
        def monitor_filter(pos):
            return pos.sample.name == 'FC-2'

        return partition(ls, monitor_filter)

    def group_level(self, level, irradiation=None, monitor_filter=None, ret_dbrecord=False):
        if monitor_filter is None:
            def monitor_filter(pos):
                return pos.sample == 'FC-2'

        db = self.db
        with db.session_ctx():
            if isinstance(level, str):
                level = db.get_level(level, irradiation)

            refs = []
            unks = []
            if level:
                positions = level.positions

                if positions:
                    if ret_dbrecord:
                        pos_factory = lambda x: x
                    else:
                        def pos_factory(px):
                            ip = IrradiationPositionRecord()
                            ip.create(px)
                            return ip

                    positions = [pos_factory(pp) for pp in positions]
                    refs, unks = partition(positions, monitor_filter)

        return refs, unks

    def analysis_series(self, ms, ed=None, analysis_type=None, weeks=0,
                        days=0, hours=0, limit=None):

        self.debug('atype={} ms={} ed={}'.format(analysis_type, ms, ed))
        db = self.db
        with db.session_ctx() as sess:
            q = sess.query(meas_AnalysisTable)
            q = q.join(meas_MeasurementTable)
            q = q.join(gen_MassSpectrometerTable)
            if analysis_type:
                q = q.join(gen_AnalysisTypeTable)

            if ed:
                q = q.join(meas_ExtractionTable)
                q = q.join(gen_ExtractionDeviceTable)

            d = datetime.today()
            today = datetime.today()
            d = d - timedelta(hours=hours, weeks=weeks, days=days)

            self.debug('Date Range >={}'.format(d))

            attr = meas_AnalysisTable.analysis_timestamp
            q = q.filter(and_(attr <= today, attr >= d))
            q = q.filter(gen_MassSpectrometerTable.name == ms)

            if analysis_type:
                q = q.filter(gen_AnalysisTypeTable.name == analysis_type)
            if ed:
                q = q.filter(gen_ExtractionDeviceTable.name == ed)

            q = q.order_by(attr.desc())
            if limit:
                q = q.limit(limit)

            return self._make_analyses_from_query(q)

    # ===============================================================================
    # corrections
    # ===============================================================================
    def add_history(self, dbrecord, kind, **kw):
        db = self.db
        return db.add_history(dbrecord, kind, **kw)

    def apply_fixed_correction(self, history, isotope, value, error, correction_name):
        func = getattr(self.db, 'add_{}'.format(correction_name))
        func(history, isotope=isotope, use_set=False,
             user_value=value, user_error=error)

    def apply_fixed_value_correction(self, phistory, history, fit_obj, correction_name):
        db = self.db
        if phistory:
            bs = getattr(phistory, correction_name)
            bs = reversed(bs)
            prev = next((bi for bi in bs if bi.isotope == fit_obj.name), None)
            if prev:
                uv = prev.user_value
                ue = prev.user_error
                func = getattr(db, 'add_{}'.format(correction_name))
                func(history,
                     isotope=prev.isotope,
                     fit=prev.fit,
                     user_value=uv,
                     user_error=ue)

    def apply_session_blank_history(self, analysis, hist_id):
        db = self.db
        with db.session_ctx():
            hist = db.get_blank_history(hist_id)
            if hist.action:
                session = hist.action.session
                if session:
                    histories = db.get_session_blank_histories(session)
                    if histories:
                        for hi in histories:
                            an = hi.analysis
                            an.selected_histories.selected_blanks_id = hi
                            self.remove_from_cache(an)

    def apply_blank_history(self, analysis, hist_id):
        db = self.db
        with db.session_ctx():
            an = db.get_analysis_uuid(analysis.uuid)
            self.debug('setting {} blank history_id to {}. '
                       'table=proc_BlanksHistoryTable'.format(an.record_id, hist_id))
            cid = an.selected_histories.selected_blanks_id
            self.debug('current blank id={} >> {}'.format(cid, hist_id))
            an.selected_histories.selected_blanks_id = hist_id

            analysis.sync_blanks(an)

    def add_predictor_valueset(self, refs, ss, es, dbblank):
        db = self.db
        # with db.session_ctx():
        # refs = [db.get_analysis_uuid(ri.uuid) for ri in refs]
        refs = db.get_analyses_uuid([ri.uuid for ri in refs],
                                    attr='id')
        for ri, vi, ei in zip(refs, ss, es):
            db.add_blank_set_value_table(vi, ei, dbblank, ri[0])

    def apply_correction(self, history, analysis, fit_obj, set_id, kind):
        # meas_analysis = self.db.get_analysis_uuid(analysis.uuid)

        func = getattr(self, '_apply_{}_correction'.format(kind))
        return func(history, analysis, fit_obj, set_id)

    def add_predictor_set(self, predictors, kind):
        set_id = None
        if predictors:
            db = self.db

            # make set_id
            dbrs = db.get_analyses_uuid([p.uuid for p in predictors], analysis_only=True)

            # set_id = hash(tuple((ai.id for ai in dbrs)))
            set_id = self.unique_id([ai.id for ai in dbrs])

            gfunc = getattr(db, 'get_{}_set'.format(kind))
            if not gfunc(set_id):
                func = getattr(db, 'add_{}_set'.format(kind))
                for dbr in dbrs:
                    func(dbr, set_id=set_id)

        return set_id

    # private
    def _apply_detector_intercalibration_correction(self, history, analysis, fit_obj, set_id):
        n, d = fit_obj.name.split('/')

        iso = analysis.get_isotope(detector=d)
        if not iso:
            self.debug('************************* {} no detector {}'.format(analysis.record_id, d))
            return

        ic = iso.temporary_ic_factor
        # ic = analysis.get_isotope(detector=d).temporary_ic_factor
        if ic is None:
            self.debug('************************* no ic factor for {} {}'.format(analysis.record_id,
                                                                                 fit_obj))
            return

        ic_v, ic_e = map(float, ic)

        # copy temp ic_factor to ic_factor
        iso.ic_factor = ufloat(ic_v, ic_e)

        db = self.db
        db.add_detector_intercalibration(history,
                                         detector=d,
                                         user_value=ic_v,
                                         user_error=ic_e,
                                         fit=fit_obj.fit,
                                         set_id=set_id)

    def _apply_blanks_correction(self, history, analysis, fit_obj, set_id):
        if not fit_obj.name in analysis.isotopes:
            return

        ss = analysis.isotopes[fit_obj.name]

        # ss=next((iso for iso in analysis.isotopes
        #        if iso.kind=='signal' and iso.molecular_weight.name==fit_obj.name), None)

        '''
            the blanks_editor may have set a temporary blank
            use that instead of the saved blank
        '''
        if hasattr(ss, 'temporary_blank'):
            blank = ss.temporary_blank
            ss.blank = blank
        else:
            blank = ss.blank

        db = self.db
        return db.add_blanks(history,
                             isotope=fit_obj.name,
                             user_value=float(blank.value),
                             user_error=float(blank.error),
                             fit=fit_obj.fit,
                             set_id=set_id)

        #        ps = self.interpolation_correction.predictors
        # if predictors:
        #
        #     lns = set([si.labnumber for si in predictors])
        #     ids = [si.uuid for si in predictors]
        #
        #     def f(t, t2):
        #         return t2.identifier.in_(lns), t.uuid.in_(ids)
        #
        #     ans = db.get_analyses(uuid=f)
        #     for ai in ans:
        #         db.add_blanks_set(item, ai)
        #for pi in predictors:
        #    dbr = db.get_analysis_uuid(pi.uuid)
        #    #                 self.db.add_blanks_set(item, pi.dbrecord)
        #    db.add_blanks_set(item, dbr)

    def _set_prev_db_kind(self):
        # the db's connection paramaters are bound to the ConnectionPreferences.
        # this binding is two way so by setting kind='sqlite', kind in the preferences is also changed
        # easiest solution is to save the current kind value and when the database plugin stops
        # set kind to the old value

        from pychron.globals import globalv
        if self.db.kind:
            globalv.prev_db_kind = self.db.kind

    # def _find_analyses(self, ms, post, delta, atype, step=0.5, maxtries=10, **kw):
    #     if delta < 0:
    #         step = -step
    # def _find_analyses(self, ms, post, delta, atype, step=0.5, maxtries=10, **kw):
    # if delta < 0:
    #         step = -step
    #
    #     if isinstance(post, float):
    #         post = datetime.fromtimestamp(post)
    #
    #     for i in range(maxtries):
    #         win = timedelta(hours=delta + i * step)
    #         lpost = post - win
    #         hpost = post + win
    #         rs = self._filter_analyses(ms, lpost, hpost, 5, atype, **kw)
    #         if rs:
    #             return rs
    #     else:
    #         return []

    def _filter_analyses(self, ms, lpost, hpost, lim, at, exclude_uuids=None, filter_hook=None):
        """
            ms= spectrometer
            post= timestamp
            delta= time in hours
            at=analysis type

            if delta is negative
            get all before post and after post-delta

            if delta is post
            get all before post+delta and after post
        """
        sess = self.db.get_session()
        q = sess.query(meas_AnalysisTable)
        q = q.join(meas_MeasurementTable)
        q = q.join(gen_AnalysisTypeTable)
        q = q.join(gen_MassSpectrometerTable)

        # win = timedelta(hours=delta)
        # if isinstance(post, float):
        # post = datetime.fromtimestamp(post)
        #
        # dt = post + win
        # if delta < 0:
        #     a, b = dt, post
        # else:
        #     a, b = post, dt

        q = q.filter(and_(
            gen_AnalysisTypeTable.name == at,
            meas_AnalysisTable.analysis_timestamp >= lpost,
            meas_AnalysisTable.analysis_timestamp <= hpost,
            gen_MassSpectrometerTable.name == ms))

        if filter_hook:
            q = filter_hook(q)

        if exclude_uuids:
            q = q.filter(not_(meas_AnalysisTable.uuid.in_(exclude_uuids)))

        q = q.order_by(meas_AnalysisTable.analysis_timestamp.desc())
        q = q.limit(lim)

        try:
            return q.all()
        except NoResultFound:
            pass

    def _make_analyses_from_query(self, q):
        ans = []
        try:
            self.debug('query count={}'.format(q.count()))
            ans = q.all()
        except Exception, e:
            import traceback

            traceback.print_exc()

        return self.make_analyses(ans, calculate_age=True)
        # ============= EOF =============================================
# def save_arar(self, analysis, meas_analysis):
#     with self.db.session_ctx():
#         hist = meas_analysis.selected_histories.selected_arar
#         if not hist:
#             hist = self.db.add_arar_history(meas_analysis)
#             arar = self.db.add_arar(hist)
#         else:
#             arar = hist.arar_result
#
#         #force analysis to recalculate age
#         #potentially redundant
#         analysis.calculate_age(force=True)
#
#         units = analysis.arar_constants.age_scalar
#
#         attr = ('Ar40', 'Ar39', 'Ar38', 'Ar37', 'Ar36',
#                 'rad40', 'cl36', 'ca37', 'k39')
#         for ai in attr:
#             a = getattr(analysis, ai)
#             v, e = float(a.nominal_value), float(a.std_dev)
#             setattr(arar, ai, v)
#             setattr(arar, '{}_err'.format(ai), e)
#
#         age = analysis.age * units
#         v, e = age.nominal_value, age.std_dev
#
#         je = analysis.age_error_wo_j * units
#
#         arar.age = float(v)
#         arar.age_err = float(e)
#         arar.age_err_wo_j = je
#
#         #update arar_history timestamp
#         arar.history.create_date = datetime.now()

        #     def new_ideogram2(self, ans, plotter_options=None):
        #         '''
        #             return a plotcontainer
        #         '''
        #
        #         probability_curve_kind = 'cumulative'
        #         mean_calculation_kind = 'weighted_mean'
        #         data_label_font = None
        #         metadata_label_font = None
        # #        highlight_omitted = True
        #         display_mean_indicator = True
        #         display_mean_text = True
        #
        #         p = Ideogram(
        # #                     db=self.db,
        # #                     processing_manager=self,
        #                      probability_curve_kind=probability_curve_kind,
        #                      mean_calculation_kind=mean_calculation_kind
        #                      )
        #         options = dict(
        #                        title='',
        #                        data_label_font=data_label_font,
        #                        metadata_label_font=metadata_label_font,
        #                        display_mean_text=display_mean_text,
        #                        display_mean_indicator=display_mean_indicator,
        #                        )
        #
        #         if plotter_options is None:
        #             pom = IdeogramOptionsManager()
        #             plotter_options = pom.plotter_options
        #
        #         if ans:
        # #             self.analyses = ans
        #             gideo = p.build(ans, options=options,
        #                             plotter_options=plotter_options)
        #             if gideo:
        #                 gideo, _plots = gideo
        #
        #             return gideo, p
        #     def new_spectrum(self, ans, plotter_options=None):
        #         pass
        #
        #         p = Spectrum()
        #
        #         if plotter_options is None:
        #             pom = SpectrumOptionsManager()
        #             plotter_options = pom.plotter_options
        #
        #         options = {}
        #
        #         self._plotter_options = plotter_options
        #         if ans:
        # #             self.analyses = ans
        #             gspec = p.build(ans, options=options,
        #                             plotter_options=plotter_options)
        #             if gspec:
        #                 gspec, _plots = gspec
        #
        #             return gspec, p
        #def load_sample_analyses(self, labnumber, sample, aliquot=None):
        #    db = self.db
        #    sess = db.get_session()
        #    q = sess.query(meas_AnalysisTable)
        #    q = q.join(gen_LabTable)
        #    q = q.join(gen_SampleTable)
        #
        #    q = q.filter(gen_SampleTable.name == sample)
        #    if aliquot is not None:
        #        q = q.filter(meas_AnalysisTable.aliquot == aliquot)
        #
        #    if sample == 'FC-2':
        #        q = q.filter(gen_LabTable.identifier == labnumber)
        #
        #    #        q = q.limit(10)
        #    return self._make_analyses_from_query(q)

        #def _make_analyses_from_query(self, q):
        #    ans = None
        #    try:
        #        ans = q.all()
        #        self.debug('{}'.format(ans))
        #    except Exception, e:
        #        import traceback
        #
        #        traceback.print_exc()
        #
        #    if ans:
        #        ans = self.make_analyses(ans)
        #        return ans

        #     def auto_blank_fit(self, irradiation, level, kind):
        #         if kind == 'preceding':
        #             '''
        #             1. supply a list of labnumbers/ supply level and extract labnumbers (with project minnabluff)
        #             2. get all analyses for the labnumbers
        #             3. sort analyses by run date
        #             4. calculate blank
        #                 1. preceding/bracketing
        #                     get max 2 predictors
        #
        #                 2. fit
        #                     a. group analyses by run date
        #                     b. get n predictors based on group date
        #             5. save blank
        #             '''
        #             db = self.db
        #             level = db.get_irradiation_level(irradiation, level)
        #
        #             labnumbers = [pi.labnumber for pi in level.positions
        #                             if pi.labnumber.sample.project.name in ('j', 'Minna Bluff', 'Mina Bluff')]
        #             ans = [ai
        #                     for ln in labnumbers
        #                         for ai in ln.analyses
        #                         ]
        #             pd = self.open_progress(n=len(ans))
        #             for ai in ans:
        #                 self.preceding_blank_correct(ai, pd=pd)
        #             db.commit()

        #def refit_isotopes(self, meas_analysis, pd=None, fits=None, keys=None, verbose=False):
        #
        ##         if not isinstance(analysis, Analysis):
        #    analysis = self.make_analysis(meas_analysis)
        #
        #    #         analysis.load_isotopes()
        #    dbr = meas_analysis
        #    #         dbr = analysis.dbrecord
        #    if keys is None:
        #        keys = [iso.molecular_weight.name for iso in dbr.isotopes
        #                if iso.kind == 'signal']
        #
        #    '''
        #        if spectrometer is map use all linear
        #
        #        if spectrometer is Jan or Obama
        #            if counts >150 use parabolic
        #            else use linear
        #    '''
        #    if fits is None:
        #        if analysis.mass_spectrometer in ('pychron obama', 'pychron jan', 'jan', 'obama'):
        #            n = 0
        #            if keys:
        #                n = analysis.isotopes[keys[0]].xs.shape[0]
        #
        #            if n >= 150:
        #                fits = ['parabolic', ] * len(keys)
        #            else:
        #                fits = ['linear', ] * len(keys)
        #
        #        else:
        #            fits = ['linear', ] * len(keys)
        #
        #    db = self.db
        #
        #    if not dbr.selected_histories:
        #        db.add_selected_histories(dbr)
        #        db.sess.flush()
        #
        #    msg = 'fitting isotopes for {}'.format(analysis.record_id)
        #    if pd is not None:
        #        pd.change_message(msg)
        #    self.debug(msg)
        #    dbhist = db.add_fit_history(dbr)
        #    for key, fit in zip(keys, fits):
        #        dbiso_baseline = next((iso for iso in dbr.isotopes
        #                               if iso.molecular_weight.name == key and iso.kind == 'baseline'), None)
        #        if dbiso_baseline:
        #            if verbose:
        #                self.debug('{} {}'.format(key, fit))
        #
        #            vv = analysis.isotopes[key]
        #            baseline = vv.baseline
        #            if not baseline:
        #                continue
        #
        #            v, e = baseline.value, baseline.error
        #            db.add_fit(dbhist, dbiso_baseline, fit='average_sem', filter_outliers=True,
        #                       filter_outlier_std_devs=2)
        #            db.add_isotope_result(dbiso_baseline, dbhist, signal_=float(v), signal_err=float(e))
        #
        #            dbiso = next((iso for iso in dbr.isotopes
        #                          if iso.molecular_weight.name == key and iso.kind == 'signal'), None)
        #            if dbiso:
        #                vv = analysis.isotopes[key]
        #                v, e = vv.value, vv.error
        #
        #                db.add_fit(dbhist, dbiso, fit=fit, filter_outliers=True,
        #                           filter_outlier_std_devs=2)
        #                db.add_isotope_result(dbiso, dbhist, signal_=float(v), signal_err=float(e))
        #
        #    if pd is not None:
        #        pd.increment()
        #def _get_preceding_analysis(self, ms, post, atype):
        #    if isinstance(post, float):
        #        post = datetime.datetime.fromtimestamp(post)
        #
        #    sess = self.db.get_session()
        #    q = sess.query(meas_AnalysisTable)
        #    q = q.join(meas_MeasurementTable)
        #    q = q.join(gen_AnalysisTypeTable)
        #    q = q.join(gen_MassSpectrometerTable)
        #
        #    q = q.filter(and_(
        #        gen_AnalysisTypeTable.name == atype,
        #        gen_MassSpectrometerTable.name == ms,
        #        meas_AnalysisTable.analysis_timestamp < post,
        #    )
        #    )
        #
        #    q = q.order_by(meas_AnalysisTable.analysis_timestamp.desc())
        #    try:
        #        return q.first()
        #    except NoResultFound:
        #        pass
        #def preceding_blank_correct(self, analysis, keys=None, pd=None):
        #    from pychron.core.regression.interpolation_regressor import InterpolationRegressor
        #
        #    if not isinstance(analysis, Analysis):
        #        analysis = self.make_analysis(analysis)
        #        #             analysis.load_isotopes()
        #
        #    msg = 'applying preceding blank for {}'.format(analysis.record_id)
        #    if pd is not None:
        #        pd.change_message(msg)
        #        pd.increment()
        #
        #    #         self.info(msg)
        #    ms = analysis.mass_spectrometer
        #
        #    post = analysis.timestamp
        #
        #    #         delta = -5
        #    atype = 'blank_{}'.format(analysis.analysis_type)
        #
        #    an = self._get_preceding_analysis(ms, post, atype)
        #
        #    if not an:
        #        self.warning('no preceding blank for {}'.format(analysis.record_id))
        #        return
        #
        #    ai = self.make_analyses(an)
        #    #         ai.load_isotopes()
        #
        #    if keys is None:
        #        keys = analysis.isotope_keys
        #
        #    kind = 'blanks'
        #    history = self.add_history(an, kind)
        #
        #    fit = 'preceding'
        #
        #    reg = InterpolationRegressor(kind=fit)
        #    for key in keys:
        #    #             predictors = [ai for ai in br if key in ai.isotopes]
        #        if key in ai.isotopes:
        #            r_xs, r_y = (ai.timestamp,), (ai.isotopes[key].baseline_corrected_value(),)
        #            #             if predictors:
        #            #                 r_xs, r_y = zip(*[(ai.timestamp, ai.isotopes[key].baseline_corrected_value()
        #            #                                           )
        #            #                                         for ai in predictors])
        #            r_ys, r_es = zip(*[(yi.nominal_value, yi.std_dev) for yi in r_y])
        #
        #            reg.trait_set(xs=r_xs,
        #                          ys=r_ys,
        #                          yserr=r_es,
        #            )
        #
        #            fit_obj = Fit(name=key, fit=fit)
        #            v, e = reg.predict(post), reg.predict_error(post)
        #            analysis.set_blank(key, (v[0], e[0]))
        #            self.apply_correction(history, analysis, fit_obj, [ai], kind)