nanite-community 0.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Refractive Index Imaging
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,37 @@
1
+ Metadata-Version: 2.4
2
+ Name: nanite-community
3
+ Version: 0.0.0
4
+ Summary: Repository for storing nanite models
5
+ Author-email: Eoghan O'Connell <eoclives@hotmail.com>, Paul Müller <dev@craban.de>
6
+ License: MIT
7
+ Keywords: AFM,Atomic Force Microscopy,Hertz KVM,nanite,pyjibe
8
+ Classifier: Operating System :: OS Independent
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Intended Audience :: Science/Research
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: lmfit>=1.3.2
15
+ Requires-Dist: numpy==1.26.*
16
+ Dynamic: license-file
17
+
18
+ # Nanite Community Models
19
+
20
+ This is a repository for storing
21
+ [nanite](https://github.com/AFM-Analysis/nanite) models. These can be models
22
+ that exist as part of a publication, or not. Storing them here will make them
23
+ easy to use in nanite and PyJibe.
24
+
25
+ ## Future Plans
26
+
27
+ We soon plan to incorporate this package into the
28
+ [nanite](https://github.com/AFM-Analysis/nanite) package so that all
29
+ models are available as an optional dependency.
30
+
31
+ ## Contributing
32
+
33
+ If you wish to add a model to this repository and are not familiar with git
34
+ and github, please make a new Issue.
35
+ Then, place the model code in a single python file as described in the
36
+ [nanite docs here](https://nanite.readthedocs.io/en/stable/sec_develop.html#writing-model-functions).
37
+ Then, fork this repository, make a Pull Request and choose a reviewer.
@@ -0,0 +1,20 @@
1
+ # Nanite Community Models
2
+
3
+ This is a repository for storing
4
+ [nanite](https://github.com/AFM-Analysis/nanite) models. These can be models
5
+ that exist as part of a publication, or not. Storing them here will make them
6
+ easy to use in nanite and PyJibe.
7
+
8
+ ## Future Plans
9
+
10
+ We soon plan to incorporate this package into the
11
+ [nanite](https://github.com/AFM-Analysis/nanite) package so that all
12
+ models are available as an optional dependency.
13
+
14
+ ## Contributing
15
+
16
+ If you wish to add a model to this repository and are not familiar with git
17
+ and github, please make a new Issue.
18
+ Then, place the model code in a single python file as described in the
19
+ [nanite docs here](https://nanite.readthedocs.io/en/stable/sec_develop.html#writing-model-functions).
20
+ Then, fork this repository, make a Pull Request and choose a reviewer.
File without changes
@@ -0,0 +1,429 @@
1
+ import lmfit
2
+ import numpy as np
3
+ import copy
4
+
5
+
6
+ def get_parameter_defaults():
7
+ # The order of the parameters must match the order
8
+ # of ´parameter_names´ and ´parameter_keys´.
9
+ params = lmfit.Parameters()
10
+ params.add("_mod_constraint", value=1e2, min=0, max=20e3, vary=True)
11
+ params.add("Eu", value=1e3, min=0, max=20e3, vary=True)
12
+ params.add("Eapp", expr='Eu-_mod_constraint')
13
+ params.add("time_ind", value=1, min=1e-10, max=10, vary=False)
14
+ params.add("R", value=2.5e-6, vary=False)
15
+ params.add("eta", value=.5, min=0, vary=False)
16
+ params.add("nu", value=.5, vary=False)
17
+ params.add('_constraint', value=0.5, min=1 / 5, max=10, vary=True)
18
+ params.add("lmd", expr='_constraint*time_ind')
19
+ params.add("velocity", value=5e-6, vary=False)
20
+ params.add("contact_point", value=0, vary=True)
21
+ params.add("baseline", value=0, vary=False)
22
+ return params
23
+
24
+
25
+ def hertz_corrected_viscelasticity_KVM(delta, _mod_constraint, Eu, Eapp,
26
+ time_ind, R, eta, nu,
27
+ _constraint, lmd, velocity,
28
+ contact_point,
29
+ baseline):
30
+ r"""Hertz model for a spherical indenter including correction for
31
+ viscoelasticity
32
+
33
+ Parameters
34
+ ----------
35
+ _mod_constraint: float
36
+ Constraint for Eu > Eapp
37
+ Eu: float
38
+ Unrelaxed Young's modulus = E0 + E1[N/m²]
39
+ Eapp: float
40
+ Apparent Young's modulus = E0 + E1*exp(
41
+ -0.365*time_indentation/lambda) [N/m²]
42
+ delta: 1d ndarray
43
+ Indentation [m]
44
+ time_ind: float
45
+ Indentation time [s]
46
+ R: float
47
+ Tip radius [m]
48
+ eta: float
49
+ Dashpot value of Kelvin Voigt model [Pa.s]
50
+ nu: float
51
+ Poisson's ratio
52
+ _constraint: float
53
+ constraint for Maxwell relaxation (lmd)
54
+ lmd: float
55
+ relaxation time of Maxwell elements [s]
56
+ velocity: float
57
+ Velocity of indentation [m/s]
58
+ contact_point: float
59
+ Indentation offset [m]
60
+ baseline: float
61
+ Force offset [N]
62
+ negindent: bool
63
+ If `True`, will assume that the indentation value(s) given by
64
+ `delta` are negative and must be multiplied by -1.
65
+
66
+ Returns
67
+ -------
68
+ F: float
69
+ Force [N]
70
+
71
+ Notes
72
+ -----
73
+ These approximations are made by the Hertz model:
74
+
75
+ - The sample is isotropic.
76
+ - The sample is behave as Kelvin-Voigt-Maxwell material.
77
+ - The sample is extended infinitely in one half space.
78
+ - The indenter is not deformable.
79
+ - There are no additional interactions between sample and indenter.
80
+
81
+ Additional assumptions:
82
+
83
+ - no surface forces
84
+
85
+ .. math::
86
+
87
+ F = \\(frac{4}{3}
88
+ \\frac{E}{1-\\nu^2}
89
+ \\sqrt{R}
90
+ \\delta^{3/2})
91
+ \\(1-0.15frac{delta}{R})
92
+ \\+(frac{4}{3}
93
+ \\frac{E1}{1-\\nu^2}
94
+ \\sqrt{R}
95
+ \\delta^{3/2})
96
+ \\(1-0.15frac{delta}{R})(\\exp^(frac{-0.365delta}{\\lambdaV}))
97
+ \\+7.25delta^{1/2}R^{1/2}\\muV
98
+
99
+ References
100
+ ----------
101
+ .. [1] Sneddon (1965) :cite:`Sneddon1965`
102
+ .. [2] Dobler (personal communication, 2018) :cite:`Dobler`
103
+ .. [3] Ding et. al (2017) :cite:'Ding'
104
+ .. [4] Abuhattum et al. (2022) :cite:'Abuhattum'
105
+
106
+ Original model python file:
107
+ .. [5] Abuhattum, Shada; Mokbel, Dominic; Müller, Paul; Soteriou, Despina;
108
+ Guck, Jochen; Aland, Sebastian (2022), “An explicit model to extract
109
+ viscoelastic properties of cells from AFM force-indentation curves”,
110
+ Mendeley Data, V2, doi: 10.17632/c2gccnfkgd.2
111
+
112
+ """
113
+ root = (contact_point - delta)
114
+ pos = root > 0
115
+ D = (1 - nu ** 2)
116
+ aa0 = 4 / 3 * np.sqrt(R) * (Eu - (Eu - Eapp) /
117
+ (1 - np.exp(-0.365 * time_ind / lmd))) / D
118
+
119
+ aa1 = 4 / 3 * np.sqrt(R) * ((Eu - Eapp) /
120
+ (1 - np.exp(-0.365 * time_ind / lmd))) / D
121
+
122
+ bb = np.zeros_like(delta)
123
+ bb[pos] = (root[pos]) ** (3 / 2)
124
+ cc = np.zeros_like(delta)
125
+
126
+ cc[pos] = 1 - 0.15 * root[pos] / R
127
+
128
+ dd = np.zeros_like(delta)
129
+ dd[pos] = (np.exp(-0.365 * root[pos] / (velocity * lmd)))
130
+
131
+ ee = np.zeros_like(delta)
132
+ ee[pos] = 7.25 * (root[pos]) ** (1 / 2) * R ** (1 / 2) * eta * velocity
133
+ return bb * cc * (aa0 + aa1 * dd) + ee + baseline
134
+
135
+
136
+ def model(params, x):
137
+ if x[0] < x[-1]:
138
+ revert = True
139
+ else:
140
+ revert = False
141
+ if revert:
142
+ x = x[::-1]
143
+
144
+ mf = hertz_corrected_viscelasticity_KVM(delta=x,
145
+ _mod_constraint=params[
146
+ "_mod_constraint"].value,
147
+ Eu=params["Eu"].value,
148
+ Eapp=params["Eapp"].value,
149
+ time_ind=params["time_ind"].value,
150
+ R=params["R"].value,
151
+ eta=params["eta"].value,
152
+ nu=params["nu"].value,
153
+ _constraint=params["_constraint"]
154
+ .value,
155
+ lmd=params["lmd"].value,
156
+ velocity=params["velocity"].value,
157
+ contact_point=params
158
+ ["contact_point"].value,
159
+ baseline=params["baseline"].value)
160
+
161
+ if revert:
162
+ return mf[::-1]
163
+ return mf
164
+
165
+
166
+ def compute_ancillaries(idnt):
167
+ # This function takes a nanite.indent.Indentation instance
168
+ # (https://nanite.readthedocs.io/en/stable/sec_code_reference.html
169
+ # nanite.indent.Indentation)
170
+ # as an argument and returns additional parameters as a
171
+ # dictionary.
172
+ """first, find the contact point that is computed from simple Hertz
173
+ model"""
174
+ parms = idnt.get_initial_fit_parameters(model_key=model_key,
175
+ model_ancillaries=False)
176
+ R = parms["R"].value
177
+ velocity = parms["velocity"].value
178
+ indentation = copy.deepcopy(idnt)
179
+ indentation.fit_properties["model_key"] = "hertz_para"
180
+ params_ind = indentation.get_initial_fit_parameters()
181
+ params_ind["R"].value = R
182
+
183
+ indentation.fit_model(model_key="hertz_para",
184
+ params_initial=params_ind,
185
+ range_x=(-2e-6, 1e-6), range_type='absolute')
186
+
187
+ if "params_fitted" in indentation.fit_properties:
188
+ params_ind_updated = indentation.fit_properties["params_fitted"]
189
+ contact_point_simple = params_ind_updated["contact_point"].value
190
+ else:
191
+ contact_point_simple = 0
192
+
193
+ force = indentation.data["force"]
194
+ segment = indentation.data["segment"]
195
+ tip_position = indentation.data["tip position"]
196
+ force = force[(segment == 1)]
197
+ tip_position = tip_position[(segment == 1)]
198
+
199
+ force_jump_max_indent, fitted_max_indent = fit_viscosity_jump_model(
200
+ tip_position=tip_position,
201
+ force=force,
202
+ R=R,
203
+ velocity=velocity)
204
+
205
+ fitted_max_indent = fitted_max_indent - contact_point_simple
206
+ force_jump_max_indent = force_jump_max_indent
207
+
208
+ eta_constants = 2 * 7.25 * velocity * R ** (1 / 2) * (
209
+ abs(fitted_max_indent)) ** (1 / 2)
210
+ time_ind = -(fitted_max_indent) / velocity
211
+ eta = force_jump_max_indent / eta_constants
212
+
213
+ anc_dict = {"force_jump_max_indent": force_jump_max_indent,
214
+ "eta": eta,
215
+ "time_ind": time_ind}
216
+ return anc_dict
217
+
218
+
219
+ def helper_retract_fraction(tip_position, force, fraction_force):
220
+ """
221
+ The function specifies the segment of the retract curve that will be
222
+ used for fitting the viscosity model.
223
+
224
+ Parameters
225
+ ----------
226
+ tip_position: 1D numpy array
227
+ the tip position values of the cantilever's retract motion [m]
228
+ force: 1D numpy array
229
+ the force values of the cantilever's retract motion [N]
230
+ fraction_force: float
231
+ fraction of the data points that should be taken into account for
232
+ fitting, the decision is made according to the maximal value of
233
+ the force
234
+
235
+ Returns
236
+ -------
237
+ position_seg: 1D numpy array
238
+ the segment of tip position array that will be used for the fitting [m]
239
+ force_seg: 1D numpy array
240
+ the segment of force array that will be used for the fitting [N]
241
+ max_position: float
242
+ the maximal absolute value of the tip position [m]
243
+ max_force: float
244
+ the maximal value of the force [N]
245
+
246
+ Notes
247
+ -----
248
+ max_position is assumed to be the first point of the retract
249
+ curve even though it is not always the largest indentation value. This
250
+ is because the movement of the Piezo changes the direction at the
251
+ beginning of the retract signal and larger indentation values after the
252
+ first point are assumed to be as a result of the measurement noise.
253
+ """
254
+
255
+ fraction = int(0.8 * len(force))
256
+ max_force = np.max(force[:fraction])
257
+ max_position = -1 * tip_position[0]
258
+ fit_stop = int(np.argwhere(force < (max_force * fraction_force))[0])
259
+ position_seg = tip_position[:fit_stop].copy()
260
+ force_seg = force[:fit_stop].copy()
261
+
262
+ return position_seg, force_seg, max_position, max_force
263
+
264
+
265
+ def helper_line_polynomial_fit(position_seg, force_seg):
266
+ """
267
+ piecewise fitting of two functions, the first part of the signal with a
268
+ vertical line followed by a third order polynomial.
269
+ ..math:: y = \\y0
270
+ when x<x0 \\y0 + a*(x-x0) + b*(x-x0)^2 + c*(x-x0)^3 when x>=x0
271
+
272
+ Parameters
273
+ ----------
274
+ position_seg: 1D numpy array [m]
275
+ the tip position values used for the fitting
276
+ force_seg: 1D numpy array [N]
277
+ the force values used for the fitting
278
+
279
+ Returns
280
+ -------
281
+ fit: 1D numpy array
282
+ fit results
283
+ res.params: lmfit.parameter
284
+ fitting parameters
285
+ """
286
+ params = lmfit.Parameters()
287
+ params.add("x0",
288
+ value=force_seg[0] * 0.98,
289
+ max=force_seg[0],
290
+ min=force_seg[0] * 0.6,
291
+ vary=True)
292
+
293
+ params.add("y0",
294
+ value=position_seg[0],
295
+ max=position_seg[0] * 0.98,
296
+ min=position_seg[0] * 1.02,
297
+ vary=False)
298
+
299
+ params.add('a',
300
+ value=-1.0e1,
301
+ vary=True)
302
+ params.add("b",
303
+ value=-1.0e2,
304
+ vary=True)
305
+ params.add("c",
306
+ value=1.0e2,
307
+ vary=True)
308
+ params.add('d',
309
+ expr="y0")
310
+
311
+ def fcn(params, x, y):
312
+ parvals = params.valuesdict()
313
+ split = x < parvals["x0"]
314
+ mod1 = np.zeros_like(y)
315
+ mod1 += parvals["d"] + parvals["a"] * (x - parvals["x0"]) + parvals[
316
+ "b"] * (x - parvals["x0"]) ** 2 + parvals[
317
+ "c"] * (x - parvals["x0"]) ** 3
318
+ mod1[~split] = 0
319
+
320
+ mod2 = np.zeros_like(y)
321
+ mod2 += parvals["y0"]
322
+ mod2[split] = 0
323
+
324
+ return y - mod1 - mod2
325
+
326
+ res = lmfit.minimize(fcn=fcn,
327
+ params=params,
328
+ method='nelder',
329
+ args=(force_seg, position_seg))
330
+ fit = position_seg - res.residual
331
+
332
+ return fit, res.params
333
+
334
+
335
+ def force_jump(params, max_force):
336
+ """
337
+ calculating the value of the force jump from the difference between
338
+ the maximal force and the force value at the end of the fitted
339
+ vertical line in the piecewise function fitting.
340
+
341
+ Parameters
342
+ ----------
343
+ params: lmfit.parameter
344
+ piecewise fitting parameters
345
+ max_force: float [N]
346
+ the maximal value of the force
347
+
348
+ Returns
349
+ -------
350
+ force_jump_max_indent: float
351
+ force jump value [N]
352
+ jump_end: float
353
+ the force value at the end (smallest force) of the fitted
354
+ vertical line [N]
355
+ fitted_max_indent: float
356
+ the fitted maximal indentation [m]
357
+ """
358
+ fitting_parvals = params.valuesdict()
359
+ jump_end = fitting_parvals['x0']
360
+ fitted_max_indent = fitting_parvals['y0']
361
+ force_jump_max_indent = max_force - jump_end
362
+ return force_jump_max_indent, jump_end, fitted_max_indent
363
+
364
+
365
+ def fit_viscosity_jump_model(tip_position: object, force: object, R: object,
366
+ velocity: object, fraction_force: object =
367
+ 0.85) -> object:
368
+ """ finding the viscosity value from the force jump in the retract curve
369
+ .. math::
370
+ mu =
371
+ \frac{F_jump}{2*7.25*delta_max^{1/2}*R^{1/2}*velocity}
372
+
373
+ Parameters
374
+ ----------
375
+ tip_position: 1D numpy array
376
+ the tip position values of the cantilever's retract motion [m]
377
+ force: 1D numpy array
378
+ the force values of the cantilever's retract motion [N]
379
+ R: float [m]
380
+ the radius of the spherical tip used for indentation
381
+ velocity: float [m/s]
382
+ the absolute value of the Piezo velocity set by the user
383
+ fraction_force: float
384
+ fraction of the data points that should be taken into account for
385
+ fitting, the decision is made according to the maximal value of the
386
+ force
387
+
388
+ Returns
389
+ -------
390
+ force_jump_max_indent: float
391
+ value of the the force jump at the maximal indentation [N]
392
+ fitted_max_indent: float
393
+ value of the fitted maximal indentation [m]
394
+ """
395
+ position_seg, force_seg, max_position, max_force = helper_retract_fraction(
396
+ tip_position, force, fraction_force)
397
+ position_fit, params = helper_line_polynomial_fit(position_seg, force_seg)
398
+ force_jump_max_indent, jump_end, fitted_max_indent = force_jump(params,
399
+ max_force)
400
+
401
+ return force_jump_max_indent, fitted_max_indent
402
+
403
+
404
+ model_doc = hertz_corrected_viscelasticity_KVM.__doc__
405
+ model_func = hertz_corrected_viscelasticity_KVM
406
+ model_key = "hertz_corr_visco_KVM"
407
+ model_name = "Hertz model corrected for viscoelasticity using KVM model"
408
+ parameter_keys = ["_mod_constraint", "Eu", "Eapp", "time_ind", "R", "eta",
409
+ "nu",
410
+ "_constraint", "lmd", "velocity", "contact_point",
411
+ "baseline"]
412
+ parameter_names = ["Constraint Modulus", "Young's Modulus unrelaxed",
413
+ "Young's Modulus apparent",
414
+ "Time to indent",
415
+ "Tip Radius", "viscosity", "Poisson's Ratio",
416
+ "Constraint",
417
+ "Maxwell element relaxation",
418
+ "Velocity of indenter",
419
+ "Contact Point", "Force Baseline"]
420
+ parameter_units = ["Pa", "Pa", "Pa", "s", "m", "Pa·s", " ", " ", "s", "m/s",
421
+ "m", "N"]
422
+ parameter_anc_keys = ["force_jump_max_indent", "eta",
423
+ "time_ind"]
424
+ parameter_anc_names = ["Force jump at max indent", "viscosity",
425
+ "Time to indent"]
426
+ parameter_anc_units = ["N", "Pa·s", "s"]
427
+
428
+ valid_axes_x = ["tip position"]
429
+ valid_axes_y = ["force"]
@@ -0,0 +1,37 @@
1
+ Metadata-Version: 2.4
2
+ Name: nanite-community
3
+ Version: 0.0.0
4
+ Summary: Repository for storing nanite models
5
+ Author-email: Eoghan O'Connell <eoclives@hotmail.com>, Paul Müller <dev@craban.de>
6
+ License: MIT
7
+ Keywords: AFM,Atomic Force Microscopy,Hertz KVM,nanite,pyjibe
8
+ Classifier: Operating System :: OS Independent
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Intended Audience :: Science/Research
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: lmfit>=1.3.2
15
+ Requires-Dist: numpy==1.26.*
16
+ Dynamic: license-file
17
+
18
+ # Nanite Community Models
19
+
20
+ This is a repository for storing
21
+ [nanite](https://github.com/AFM-Analysis/nanite) models. These can be models
22
+ that exist as part of a publication, or not. Storing them here will make them
23
+ easy to use in nanite and PyJibe.
24
+
25
+ ## Future Plans
26
+
27
+ We soon plan to incorporate this package into the
28
+ [nanite](https://github.com/AFM-Analysis/nanite) package so that all
29
+ models are available as an optional dependency.
30
+
31
+ ## Contributing
32
+
33
+ If you wish to add a model to this repository and are not familiar with git
34
+ and github, please make a new Issue.
35
+ Then, place the model code in a single python file as described in the
36
+ [nanite docs here](https://nanite.readthedocs.io/en/stable/sec_develop.html#writing-model-functions).
37
+ Then, fork this repository, make a Pull Request and choose a reviewer.
@@ -0,0 +1,11 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ nanite_community/__init__.py
5
+ nanite_community.egg-info/PKG-INFO
6
+ nanite_community.egg-info/SOURCES.txt
7
+ nanite_community.egg-info/dependency_links.txt
8
+ nanite_community.egg-info/requires.txt
9
+ nanite_community.egg-info/top_level.txt
10
+ nanite_community/models/__init__.py
11
+ nanite_community/models/model_hertz_corrected_viscoelasticity_KVM.py
@@ -0,0 +1,2 @@
1
+ lmfit>=1.3.2
2
+ numpy==1.26.*
@@ -0,0 +1 @@
1
+ nanite_community
@@ -0,0 +1,33 @@
1
+ [project]
2
+ name = "nanite-community"
3
+ description = "Repository for storing nanite models"
4
+ requires-python = ">=3.10"
5
+ authors = [
6
+ {name="Eoghan O'Connell", email="eoclives@hotmail.com"},
7
+ {name="Paul Müller", email="dev@craban.de"},
8
+ ]
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ dependencies = [
12
+ "lmfit>=1.3.2",
13
+ "numpy==1.26.*",
14
+ ]
15
+ dynamic = ["version"]
16
+ keywords=[
17
+ "AFM",
18
+ "Atomic Force Microscopy",
19
+ "Hertz KVM",
20
+ "nanite",
21
+ "pyjibe",
22
+ ]
23
+ classifiers=[
24
+ "Operating System :: OS Independent",
25
+ "Programming Language :: Python :: 3",
26
+ "Intended Audience :: Science/Research"
27
+ ]
28
+
29
+ [dependency-groups]
30
+ lint = ["flake8"]
31
+ dev = [
32
+ {include-group = "lint"}
33
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+