def load_file(self, path, callback, user_metadata): """Load a data file, optionally asking for missing metadata Parameters ---------- path: str or pathlib.Path path to the data file callback: callable function for tracking loading progress user_metadata: dict dictionary holding the user-defined metadata; this dictionary is edited in-place (i.e. the user only needs to specify missing metadata once per import) if the `user_metadata` is used consistently in an import loop (like in `self.add_files`). Note that only the metadata that is missing are used from user_metadata. """ try: grp = nanite.IndentationGroup(path, callback=callback) except afmformats.errors.MissingMetaDataError as e: custom_metadata = {} for key in e.meta_keys: # check if the keys are in user_metadata if key not in user_metadata: # show dialog asking for missing metadata name, si_unit, _ = afmformats.meta.DEF_ALL[key] hr_unit = units.hrunit(name, si_unit) scale_unit = units.hrscale(name, si_unit) value, ok_pressed = QtWidgets.QInputDialog.getDouble( self, f"Missing metadata '{name}'", f"Please specify the {name}" + (f" [{hr_unit}]" if hr_unit else "") + ":", .0, .0, 99999, 6, ) if not ok_pressed: # if user pressed cancel, raise AbortError raise AbortProgress(f"User did not enter {key}!") # add new keys in user_metadata user_metadata[key] = value / scale_unit # populate custom_metadata with the user_metadata custom_metadata[key] = user_metadata[key] # now loading the data should work grp = nanite.IndentationGroup(path, callback=callback, meta_override=custom_metadata) return grp
def test_apply_preprocessing_remember_fit_properties(): """ Normally, the fit properties would be overridden if the preprocessing changes. For user convenience, nanite remembers it. This is the test """ ds1 = nanite.IndentationGroup(jpkfile) idnt = ds1[0] idnt.apply_preprocessing(["compute_tip_position"]) inparams = nanite.model.model_hertz_paraboloidal.get_parameter_defaults() inparams["baseline"].vary = True cp1 = 1.8029310065572342e-05 inparams["contact_point"].set(cp1) inparams["contact_point"].vary = False # Fit with absolute full range idnt.fit_model(model_key="hertz_para", params_initial=inparams, range_x=(0, 0), range_type="absolute", x_axis="tip position", y_axis="force", segment="approach", weight_cp=False) assert cp1 == idnt.fit_properties["params_initial"]["contact_point"].value assert cp1 == idnt.fit_properties["params_fitted"]["contact_point"].value # Change preprocessing idnt.apply_preprocessing(["compute_tip_position", "correct_force_offset"]) assert cp1 == idnt.fit_properties["params_initial"]["contact_point"].value
def test_preprocessing_reset(): fd = nanite.IndentationGroup(jpkfile)[0] fd.apply_preprocessing( ["compute_tip_position", "correct_force_offset", "correct_tip_offset"], options={"correct_tip_offset": { "method": "fit_constant_line" }}) inparams = nanite.model.model_hertz_paraboloidal.get_parameter_defaults() inparams["baseline"].vary = True inparams["contact_point"].set(1.8e-5) # Perform fit and make sure that all properties are set fd.fit_model(model_key="hertz_para", params_initial=inparams, range_x=(0, 0), range_type="absolute", x_axis="tip position", y_axis="force", segment="approach", weight_cp=False) fd.rate_quality(training_set="zef18", regressor="Extra Trees") assert fd._rating is not None assert fd.preprocessing == [ "compute_tip_position", "correct_force_offset", "correct_tip_offset" ] assert fd.preprocessing_options == { "correct_tip_offset": { "method": "fit_constant_line" } } assert not fd._preprocessing_details assert np.allclose( fd._fit_properties["params_fitted"]["contact_point"].value, -3.5147555568064786e-06, atol=0) assert "tip position" in fd # Change preprocessing and make sure properties are reset fd.apply_preprocessing( ["compute_tip_position", "correct_force_offset", "correct_tip_offset"], options={"correct_tip_offset": { "method": "fit_constant_polynomial" }}) assert fd._rating is None assert fd.preprocessing == [ "compute_tip_position", "correct_force_offset", "correct_tip_offset" ] assert fd.preprocessing_options == { "correct_tip_offset": { "method": "fit_constant_polynomial" } } assert not fd._preprocessing_details assert "params_fitted" not in fd._fit_properties assert "tip position" in fd # Change preprocessing, removing tip position fd.apply_preprocessing(["correct_force_offset"]) assert "tip position" not in fd
def test_export(): ds1 = nanite.IndentationGroup(jpkfile) idnt = ds1[0] # tip-sample separation idnt.apply_preprocessing(["compute_tip_position"]) # create temporary file _, path = tempfile.mkstemp(suffix=".tab", prefix="nanite_idnt_export") idnt.export(path) data = afmformats.load_data(path)[0] assert len(data) == 4000 assert np.allclose(data["force"][100], -4.853736717639109e-10) assert np.allclose(data["height (measured)"][100], 2.256791903750211e-05, atol=1e-10, rtol=0) assert data["segment"][100] == 0 assert np.allclose(data["time"][100], 0.04999999999999999) assert np.allclose(data["tip position"][100], 2.255675939721752e-05, atol=1e-10, rtol=0) assert data["segment"][3000] == 1 try: pathlib.Path(path).unlink() except OSError: pass
def test_repr_str(): ds1 = nanite.IndentationGroup(jpkfile) idnt = ds1[0] assert "AFMForceDistance" not in str(idnt) assert "Indentation" in str(idnt) assert "fmt-jpk-fd_spot3-0192.jpk-force" in str(idnt) assert "Indentation" in repr(idnt) assert "fmt-jpk-fd_spot3-0192.jpk-force" in repr(idnt)
def selected_curves(self): """IndentationGroup with all curves selected by the user""" curves = nanite.IndentationGroup() for ar in self.data_set: idx = self.data_set.index(ar) item = self.list_curves.topLevelItem(idx) if item.checkState(3) == QtCore.Qt.Checked: curves.append(ar) return curves
def test_basic(): ds1 = nanite.IndentationGroup(jpkfile) idnt = ds1[0] # tip-sample separation idnt.apply_preprocessing(["compute_tip_position"]) assert idnt.preprocessing == ["compute_tip_position"] assert idnt["tip position"][0] == 2.2803841798545836e-05 # correct for an offset in the tip idnt.apply_preprocessing(["compute_tip_position", "correct_tip_offset"]) # This value is subject to change if a better way to estimate the # contact point is found: assert idnt["tip position"][0] == 4.765854684370548e-06
def test_fitting(): ds1 = nanite.IndentationGroup(jpkfile) idnt = ds1[0] idnt.apply_preprocessing(["compute_tip_position"]) inparams = nanite.model.model_hertz_paraboloidal.get_parameter_defaults() inparams["baseline"].vary = True inparams["contact_point"].set(1.8e-5) # Fit with absolute full range idnt.fit_model(model_key="hertz_para", params_initial=inparams, range_x=(0, 0), range_type="absolute", x_axis="tip position", y_axis="force", segment="approach", weight_cp=False) params = idnt.fit_properties["params_fitted"] assert np.allclose(params["contact_point"].value, 1.8029310201193193e-05) assert np.allclose(params["E"].value, 14741.958242422093) # Fit with absolute short idnt.fit_model(model_key="hertz_para", params_initial=inparams, range_x=(17e-06, 19e-6), range_type="absolute", x_axis="tip position", y_axis="force", segment="approach", weight_cp=False) params2 = idnt.fit_properties["params_fitted"] assert np.allclose(params2["contact_point"].value, 1.8028461828272924e-05) assert np.allclose(params2["E"].value, 14840.840404880484) # Fit with relative to initial fit idnt.fit_model(model_key="hertz_para", params_initial=params2, range_x=(-2e-6, 1e-6), range_type="relative cp", x_axis="tip position", y_axis="force", segment="approach", weight_cp=False) params3 = idnt.fit_properties["params_fitted"] # These results are subject to change if the "relative cp" method is # changed. assert np.allclose(params3["contact_point"].value, 1.8028478083499856e-05) assert np.allclose(params3["E"].value, 14839.821714634612)
def test_rate_quality_disabled(): ds1 = nanite.IndentationGroup(jpkfile) idnt = ds1[0] idnt.apply_preprocessing(["compute_tip_position"]) inparams = nanite.model.model_hertz_paraboloidal.get_parameter_defaults() inparams["baseline"].vary = True inparams["contact_point"].set(1.8e-5) # Fit with absolute full range idnt.fit_model(model_key="hertz_para", params_initial=inparams, range_x=(0, 0), range_type="absolute", x_axis="tip position", y_axis="force", segment="approach", weight_cp=False) r1 = idnt.rate_quality(training_set="zef18", regressor="none") assert r1 == -1
def test_get_initial_fit_parameters(): """This is a convenience function""" ds1 = nanite.IndentationGroup(jpkfile) idnt = ds1[0] # A: sanity check fp = idnt.get_initial_fit_parameters() assert fp["contact_point"].value == 0, "need to get tip position first" # B: get default fit parameters (hertz_para) idnt.apply_preprocessing(["compute_tip_position"]) fp = idnt.get_initial_fit_parameters() for kk in ['E', 'R', 'nu', 'contact_point', 'baseline']: assert kk in fp.keys() # C: set other model and get fit parameters idnt.fit_properties["model_key"] = "hertz_pyr3s" fp2 = idnt.get_initial_fit_parameters() for kk in ['E', 'alpha', 'nu', 'contact_point', 'baseline']: assert kk in fp2.keys() # D: fit and get from fit_properties idnt.fit_model(params_initial=fp2) fp3 = idnt.get_initial_fit_parameters() assert fp2 == fp3
def test_simple_ancillary_override_nan(): """nan values are not used and should be ignored""" ds1 = nanite.IndentationGroup(jpkfile) idnt = ds1[0] with MockModelModule(compute_ancillaries=lambda x: {"E": np.nan}, parameter_anc_keys=["E"], parameter_anc_names=["ancillary E guess"], parameter_anc_units=["Pa"], model_key="test2"): # We need to perform preprocessing first, if we want to get the # correct initial contact point. idnt.apply_preprocessing(["compute_tip_position"]) # We set the baseline fixed, because this test was written so) params_initial = idnt.get_initial_fit_parameters(model_key="test2") params_initial["baseline"].set(vary=False) idnt.fit_model(model_key="test2", params_initial=params_initial) assert idnt.fit_properties["params_initial"]["E"].value == 3000 assert np.allclose(idnt.fit_properties["params_fitted"]["E"].value, 1584.8876592662375, atol=0, rtol=1e-5)
def test_simple_ancillary_override(): """basic test for ancillary parameters""" ds1 = nanite.IndentationGroup(jpkfile) idnt = ds1[0] with MockModel( compute_ancillaries=lambda x: {"E": 1580}, parameter_anc_keys=["E"], parameter_anc_names=["ancillary E guess"], parameter_anc_units=["Pa"], model_key="test1"): # We need to perform preprocessing first, if we want to get the # correct initial contact point. idnt.apply_preprocessing(["compute_tip_position"]) # We set the baseline fixed, because this test was written so) params_initial = idnt.get_initial_fit_parameters(model_key="test1") params_initial["baseline"].set(vary=False) idnt.fit_model(model_key="test1", params_initial=params_initial) assert idnt.fit_properties["params_initial"]["E"].value == 1580 assert np.allclose(idnt.fit_properties["params_fitted"]["E"].value, 1572.7685940809245, atol=0, rtol=1e-5)
def test_rate_quality_nofit(): ds1 = nanite.IndentationGroup(jpkfile) idnt = ds1[0] r1 = idnt.rate_quality() assert r1 == -1
def test_apply_preprocessing(): ds1 = nanite.IndentationGroup(jpkfile) idnt = ds1[0] # apply preprocessing by manually setting the list idnt.preprocessing = ["compute_tip_position"] idnt.apply_preprocessing()
def __init__(self, *args, **kwargs): """Base class for force-indentation analysis""" super(UiForceDistance, self).__init__(*args, **kwargs) path_ui = pkg_resources.resource_filename("pyjibe.fd", "main.ui") uic.loadUi(path_ui, self) self.settings = QtCore.QSettings() self.settings.setIniCodec("utf-8") if not self.settings.value("force-distance/rate ts path", ""): dataloc = pathlib.Path(QtCore.QStandardPaths.writableLocation( QtCore.QStandardPaths.AppDataLocation)) ts_import_path = dataloc / "force-distance_rate-ts-user" self.settings.setValue("force-distance/rate ts path", str(ts_import_path)) UiForceDistance._instance_counter += 1 title = "Force-Distance #{}".format(self._instance_counter) self.parent().setWindowTitle(title) self.data_set = nanite.IndentationGroup() # rating scheme self.rating_scheme_setup() # Signals # tabs self.tabs.currentChanged.connect(self.on_tab_changed) # fitting / parameters self.tab_edelta.sp_delta_num_samples.valueChanged.connect( self.on_params_init) self.btn_fitall.clicked.connect(self.on_fit_all) self.tab_fit.cb_delta_select.currentIndexChanged.connect( self.tab_edelta.cb_delta_select.setCurrentIndex) self.tab_edelta.cb_delta_select.currentIndexChanged.connect( self.tab_fit.cb_delta_select.setCurrentIndex) self.tab_fit.sp_range_1.valueChanged["double"].connect( self.tab_edelta.on_delta_change_spin) self.tab_fit.sp_range_1.valueChanged.connect(self.on_params_init) self.tab_fit.sp_range_2.valueChanged.connect(self.on_params_init) # rating self.btn_rating_filter.clicked.connect(self.on_rating_threshold) self.cb_rating_scheme.currentTextChanged.connect( self.on_cb_rating_scheme) self.btn_rater.clicked.connect(self.on_user_rate) # plotting parameters self.cb_mpl_rescale_plot_x.stateChanged.connect( self.on_mpl_curve_update) self.cb_mpl_rescale_plot_x_min.valueChanged.connect( self.on_mpl_curve_update) self.cb_mpl_rescale_plot_x_max.valueChanged.connect( self.on_mpl_curve_update) self.cb_mpl_rescale_plot_y.stateChanged.connect( self.on_mpl_curve_update) self.cb_mpl_rescale_plot_y_min.valueChanged.connect( self.on_mpl_curve_update) self.cb_mpl_rescale_plot_y_max.valueChanged.connect( self.on_mpl_curve_update) # Random string for identification of autosaved results self._autosave_randstr = hashlib.md5(time.ctime().encode("utf-8") ).hexdigest()[:5] # Initialize `override` parameter: # -1: not decided # 0: do not override existing files # 1: override existing files # 2: create additional file with date self._autosave_override = -1 # Filenames that were created by this instance self._autosave_original_files = []