py-neuromodulation 0.1.4__py3-none-any.whl → 0.1.7__py3-none-any.whl

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.
@@ -54,10 +54,10 @@ raw_normalization_settings:
54
54
  clip: 3
55
55
 
56
56
  preprocessing_filter:
57
- bandstop_filter: true
58
- lowpass_filter: true
59
- highpass_filter: true
60
- bandpass_filter: true
57
+ bandstop_filter: false
58
+ lowpass_filter: false
59
+ highpass_filter: false
60
+ bandpass_filter: false
61
61
  bandstop_filter_settings: [100, 160] # [low_hz, high_hz]
62
62
  bandpass_filter_settings: [3, 200] # [hz, _hz]
63
63
  lowpass_filter_cutoff_hz: 200
@@ -245,3 +245,4 @@ bispectrum_settings:
245
245
  mean: true
246
246
  sum: true
247
247
  var: true
248
+
@@ -87,17 +87,21 @@ class FooofAnalyzer(NMFeature):
87
87
 
88
88
  spectra = np.abs(rfft(data[:, -self.num_samples :])) # type: ignore
89
89
 
90
- self.fm.fit(self.f_vec, spectra, self.settings.freq_range_hz)
90
+ try:
91
+ self.fm.fit(self.f_vec, spectra, self.settings.freq_range_hz)
92
+ except:
93
+ failed_fits = list(range(len(self.ch_names)))
91
94
 
92
95
  if not self.fm.has_model or self.fm.null_inds_ is None:
93
- raise RuntimeError("FOOOF failed to fit model to data.")
94
-
95
- failed_fits: list[int] = self.fm.null_inds_
96
+ failed_fits = list(range(len(self.ch_names)))
97
+ else:
98
+ failed_fits: list[int] = self.fm.null_inds_
96
99
 
97
100
  feature_results = {}
98
101
  for ch_idx, ch_name in enumerate(self.ch_names):
99
102
  FIT_PASSED = ch_idx not in failed_fits
100
- exp = self.fm.get_params("aperiodic_params", "exponent")[ch_idx]
103
+ if FIT_PASSED:
104
+ exp = self.fm.get_params("aperiodic_params", "exponent")[ch_idx]
101
105
 
102
106
  for feat in self.settings.aperiodic.get_enabled():
103
107
  f_name = f"{ch_name}_fooof_a_{self.feat_name_map[feat]}"
@@ -85,16 +85,16 @@ class SharpwaveSettings(NMBaseModel):
85
85
  self.estimator[est] = []
86
86
 
87
87
  @model_validator(mode="after")
88
- def test_settings(cls, settings):
88
+ def test_settings(self):
89
89
  # check if all features are also enabled via an estimator
90
- estimator_list = [est for list_ in settings.estimator.values() for est in list_]
90
+ estimator_list = [est for list_ in self.estimator.values() for est in list_]
91
91
 
92
- for used_feature in settings.sharpwave_features.get_enabled():
92
+ for used_feature in self.sharpwave_features.get_enabled():
93
93
  assert (
94
94
  used_feature in estimator_list
95
95
  ), f"Add estimator key for {used_feature}"
96
96
 
97
- return settings
97
+ return self
98
98
 
99
99
 
100
100
  class SharpwaveAnalyzer(NMFeature):
@@ -151,7 +151,9 @@ class SharpwaveAnalyzer(NMFeature):
151
151
  self.filters = np.vstack([f for _, f in self.list_filter])
152
152
  self.filters = np.tile(self.filters[None, :, :], (len(self.ch_names), 1, 1))
153
153
  else:
154
- self.filters = [np.tile(f, (len(self.ch_names), 1)) for _, f in self.list_filter]
154
+ self.filters = [
155
+ np.tile(f, (len(self.ch_names), 1)) for _, f in self.list_filter
156
+ ]
155
157
 
156
158
  self.used_features = self.sw_settings.sharpwave_features.get_enabled()
157
159
 
@@ -282,7 +284,9 @@ class SharpwaveAnalyzer(NMFeature):
282
284
  if feature_name == "num_peaks":
283
285
  key_name = f"{ch_name}_Sharpwave_{feature_name}_{filter_name}"
284
286
  if len(waveform_results[feature_name]) == 1:
285
- dict_ch_features[key_name][key_name_pt] = waveform_results[feature_name][0]
287
+ dict_ch_features[key_name][key_name_pt] = waveform_results[
288
+ feature_name
289
+ ][0]
286
290
  continue
287
291
  else:
288
292
  raise ValueError("num_peaks should be a list with length 1")
@@ -317,8 +321,12 @@ class SharpwaveAnalyzer(NMFeature):
317
321
  for ch_name in self.ch_names:
318
322
  for filter_name in self.filter_names:
319
323
  key_name = f"{ch_name}_Sharpwave_num_peaks_{filter_name}"
320
- feature_results[key_name] = np_mean([dict_ch_features[key_name]["Peak"],
321
- dict_ch_features[key_name]["Trough"]])
324
+ feature_results[key_name] = np_mean(
325
+ [
326
+ dict_ch_features[key_name]["Peak"],
327
+ dict_ch_features[key_name]["Trough"],
328
+ ]
329
+ )
322
330
  else:
323
331
  # otherwise, save all write all "flattened" key value pairs in feature_results
324
332
  for key, subdict in dict_ch_features.items():
@@ -55,7 +55,7 @@ class ReReferencer(NMPreprocessor):
55
55
  # if ind not in channels_used:
56
56
  # continue
57
57
  ref = refs[ind]
58
- if ref.lower() == "none" or pd.isnull(ref):
58
+ if ref.lower() == "none" or pd.isnull(ref) or channels["status"][ind] != "good":
59
59
  ref_idx = None
60
60
  continue
61
61
  if ref.lower() == "average":
@@ -78,7 +78,12 @@ class ReReferencer(NMPreprocessor):
78
78
  )
79
79
  ref_idx.append(ch_names.index(ref_chan))
80
80
  ref_matrix[ind, ref_idx] = -1 / len(ref_idx)
81
+
81
82
  self.ref_matrix = ref_matrix
83
+
84
+ # only index good channels
85
+ good_idxs = np.where(channels["status"] == "good")[0]
86
+ self.ref_matrix = self.ref_matrix[good_idxs, :][:, good_idxs]
82
87
 
83
88
  def process(self, data: np.ndarray) -> np.ndarray:
84
89
  """Rereference data according to the initialized ReReferencer class.
@@ -143,12 +143,17 @@ class DataProcessor:
143
143
  ) -> tuple[list[str], list[str], list[int], np.ndarray]:
144
144
  """Get used feature and label info from channels"""
145
145
  channels = self.channels
146
- ch_names_used = channels[channels["used"] == 1]["new_name"].tolist()
147
- ch_types_used = channels[channels["used"] == 1]["type"].tolist()
146
+ ch_names_used = channels.query("used == 1 and status == 'good'")["new_name"].tolist()
147
+ ch_types_used = channels.query("used == 1 and status == 'good'")["type"].tolist()
148
148
 
149
149
  # used channels for feature estimation
150
150
  feature_idx = np.where(channels["used"] & ~channels["target"])[0].tolist()
151
151
 
152
+ # remove the idxs of status == "bad"
153
+ feature_idx = [
154
+ idx for idx in feature_idx if channels.loc[idx, "status"] == "good"
155
+ ]
156
+
152
157
  # If multiple targets exist, select only the first
153
158
  label_idx = np.where(channels["target"] == 1)[0]
154
159
 
@@ -46,6 +46,7 @@ def set_channels(
46
46
  For this, the channel names must contain the substring "_L_" and/or
47
47
  "_R_" (lower or upper case). CAVE: Adjacent channels will be
48
48
  determined using the sort() function.
49
+ Re-reference can be also "average" for common-average-referencing
49
50
  bads : str | list of str, default: None
50
51
  channels that should be marked as bad and not be used for
51
52
  average re-referencing etc.
@@ -127,9 +128,12 @@ def set_channels(
127
128
  if isinstance(reference, str):
128
129
  if reference.lower() == "default":
129
130
  df = _get_default_references(df=df, ch_names=ch_names, ch_types=ch_types)
131
+ elif reference.lower() == "average":
132
+ df["rereference"] = ["average" if df["used"][ch_idx] == 1 else "None"
133
+ for ch_idx in range(len(ch_names))]
130
134
  else:
131
135
  raise ValueError(
132
- "`reference` must be either `default`, `None` or "
136
+ "`reference` must be either `default`, `None`, `average` or "
133
137
  "an iterable of new reference channel names. "
134
138
  f"Got: {reference}."
135
139
  )
@@ -5,10 +5,11 @@ from typing import (
5
5
  get_args,
6
6
  get_type_hints,
7
7
  Literal,
8
+ Unpack,
9
+ TypedDict,
8
10
  cast,
9
11
  Sequence,
10
12
  )
11
- from typing_extensions import Unpack, TypedDict
12
13
  from pydantic import BaseModel, GetCoreSchemaHandler, ConfigDict
13
14
 
14
15
  from pydantic_core import (
@@ -118,23 +119,13 @@ class _NMExtraFieldInputs(TypedDict, total=False):
118
119
  custom_metadata: dict[str, Any]
119
120
 
120
121
 
121
- class _NMFieldInfoInputs(_FieldInfoInputs, _NMExtraFieldInputs, total=False):
122
- """Combine pydantic FieldInfo inputs with PyNM additional inputs"""
123
-
124
- pass
125
-
126
-
127
- class _NMFromFieldInfoInputs(_FromFieldInfoInputs, _NMExtraFieldInputs, total=False):
128
- """Combine pydantic FieldInfo.from_field inputs with PyNM additional inputs"""
129
-
130
- pass
131
-
132
-
133
122
  class NMFieldInfo(FieldInfo):
134
123
  # Add default values for any other custom fields here
135
124
  _default_values = {}
136
125
 
137
- def __init__(self, **kwargs: Unpack[_NMFieldInfoInputs]) -> None:
126
+ def __init__(
127
+ self, **kwargs: Unpack[_FieldInfoInputs | _NMExtraFieldInputs]
128
+ ) -> None:
138
129
  self.sequence: bool = kwargs.pop("sequence", False) # type: ignore
139
130
  self.custom_metadata: dict[str, Any] = kwargs.pop("custom_metadata", {})
140
131
  super().__init__(**kwargs)
@@ -162,7 +153,7 @@ class NMFieldInfo(FieldInfo):
162
153
  @staticmethod
163
154
  def from_field(
164
155
  default: Any = PydanticUndefined,
165
- **kwargs: Unpack[_NMFromFieldInfoInputs],
156
+ **kwargs: Unpack[_FromFieldInfoInputs | _NMExtraFieldInputs],
166
157
  ) -> "NMFieldInfo":
167
158
  if "annotation" in kwargs:
168
159
  raise TypeError('"annotation" is not permitted as a Field keyword argument')
@@ -178,7 +169,7 @@ class NMFieldInfo(FieldInfo):
178
169
 
179
170
  def NMField(
180
171
  default: Any = PydanticUndefined,
181
- **kwargs: Unpack[_NMFromFieldInfoInputs],
172
+ **kwargs: Unpack[_FromFieldInfoInputs | _NMExtraFieldInputs],
182
173
  ) -> Any:
183
174
  return NMFieldInfo.from_field(default=default, **kwargs)
184
175
 
@@ -197,7 +188,7 @@ class NMBaseModel(BaseModel):
197
188
  super().__init__(*args, **kwargs)
198
189
  return
199
190
 
200
- field_names = list(self.model_fields.keys())
191
+ field_names = list(self.__class__.model_fields.keys())
201
192
  # If we have more positional args than fields, that's an error
202
193
  if len(args) > len(field_names):
203
194
  raise ValueError(
@@ -238,7 +229,7 @@ class NMBaseModel(BaseModel):
238
229
 
239
230
  @property
240
231
  def fields(self) -> dict[str, FieldInfo | NMFieldInfo]:
241
- return self.model_fields # type: ignore
232
+ return self.__class__.model_fields # type: ignore
242
233
 
243
234
  def serialize_with_metadata(self):
244
235
  result: dict[str, Any] = {"__field_type__": self.__class__.__name__}
@@ -135,17 +135,17 @@ class BoolSelector(NMBaseModel):
135
135
  def get_enabled(self):
136
136
  return [
137
137
  f
138
- for f in self.model_fields.keys()
138
+ for f in self.__class__.model_fields.keys()
139
139
  if (isinstance(self[f], bool) and self[f])
140
140
  ]
141
141
 
142
142
  def enable_all(self):
143
- for f in self.model_fields.keys():
143
+ for f in self.__class__.model_fields.keys():
144
144
  if isinstance(self[f], bool):
145
145
  self[f] = True
146
146
 
147
147
  def disable_all(self):
148
- for f in self.model_fields.keys():
148
+ for f in self.__class__.model_fields.keys():
149
149
  if isinstance(self[f], bool):
150
150
  self[f] = False
151
151
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: py_neuromodulation
3
- Version: 0.1.4
3
+ Version: 0.1.7
4
4
  Summary: Real-time analysis of intracranial neurophysiology recordings.
5
5
  Project-URL: Homepage, https://neuromodulation.github.io/py_neuromodulation/
6
6
  Project-URL: Documentation, https://neuromodulation.github.io/py_neuromodulation/
@@ -92,9 +92,18 @@ Requires-Dist: pytest-xdist; extra == 'test'
92
92
  Requires-Dist: pytest>=8.0.2; extra == 'test'
93
93
  Description-Content-Type: text/x-rst
94
94
 
95
+
96
+
95
97
  py_neuromodulation
96
98
  ==================
97
99
 
100
+ Journal of Open Source Science publication:
101
+
102
+ .. image:: https://joss.theoj.org/papers/10.21105/joss.08258/status.svg
103
+ :target: https://doi.org/10.21105/joss.08258
104
+
105
+
106
+
98
107
  Documentation: https://neuromodulation.github.io/py_neuromodulation/
99
108
 
100
109
  Analyzing neural data can be a troublesome, trial and error prone,
@@ -1,5 +1,5 @@
1
1
  py_neuromodulation/__init__.py,sha256=gu0XWs6bzSMJ7JTu8xxFMCPj6TY0nQ6Q0tuBkK7onRI,2996
2
- py_neuromodulation/default_settings.yaml,sha256=tEKmC16yQwy1MFMRI8cdshT8tTdZ35DB5SqqlNWES04,6387
2
+ py_neuromodulation/default_settings.yaml,sha256=7e2UYayioKx85HeW43OyVS7CvWEt37yeITp0lJCPFGU,6393
3
3
  py_neuromodulation/grid_cortex.tsv,sha256=k2QOkHY1ej3lJ33LD6DOPVlTynzB3s4BYaoQaoUCyYc,643
4
4
  py_neuromodulation/grid_subcortex.tsv,sha256=oCQDYLDdYSa1DAI9ybwECfuzWulFzXqKHyf7oZ1oDBM,25842
5
5
  py_neuromodulation/lsl_api.cfg,sha256=oKJ5S_9mJjLUCuI4i1jZVOOquNebzdCDIMQWv1gwT3U,39
@@ -36,13 +36,13 @@ py_neuromodulation/features/bispectra.py,sha256=BzI0UsMnBWal6qtqmTmLOR20IA_Wly7E
36
36
  py_neuromodulation/features/bursts.py,sha256=HPLZ7EHbDjDthN96bRgUjb9JDvSoZkf-aYGroI9GLdM,11574
37
37
  py_neuromodulation/features/coherence.py,sha256=RmY-KPROZbsnmjewOrZjFO4KEq7D_D5P4sgZ3W_CCaw,9238
38
38
  py_neuromodulation/features/feature_processor.py,sha256=S1hFg7Ke5CaD64sti0ZaTBL-KnOrIjliq3ytpO01Wls,3976
39
- py_neuromodulation/features/fooof.py,sha256=9HJjjmkFcchDy8ZsSn3DKPla-Ns-Hx5xaln6C0OkTEE,5191
39
+ py_neuromodulation/features/fooof.py,sha256=YF3i_RgzJ4SMTRkOBPXB4hQNppcSKhS0S-KS0bTbpt8,5319
40
40
  py_neuromodulation/features/hjorth_raw.py,sha256=mRPioPHJdN73AGErRbJ5S1Vz__qEQ89jAKaeN5k8eXo,1845
41
41
  py_neuromodulation/features/linelength.py,sha256=8BTctvr9Zj8TEK2HLJqi73j_y2Xgt8lKK-mJhv8eAsM,641
42
42
  py_neuromodulation/features/mne_connectivity.py,sha256=lQHLIXmoyDOWf5agmGMLeq1cQLiG6ud-vYf75CpYTVI,4109
43
43
  py_neuromodulation/features/nolds.py,sha256=jNCKQlIfmcAhmzjTAJMbFPhRuPtYZF5BDzR4qHlLp1k,3374
44
44
  py_neuromodulation/features/oscillatory.py,sha256=KzQQ3EA75G-hJVmL_YBybWTnuNJFZXAM0aBHudr-dvM,7806
45
- py_neuromodulation/features/sharpwaves.py,sha256=UpNsaD77Kf5wY22cEhQ9WMNxiBVxjxzDfcsVqubYgm0,18562
45
+ py_neuromodulation/features/sharpwaves.py,sha256=lnX6y4JsNQ7B0iU0W9GDkqvldVHO9FyoaUF1cMHuCbs,18714
46
46
  py_neuromodulation/filter/__init__.py,sha256=ut1q8daCZoN7lhTKURGpk1X5oKiS3eSNqR7SkZyGDJw,128
47
47
  py_neuromodulation/filter/kalman_filter.py,sha256=-aSAq7KcJ8LUjUThsQtTaIcvz-Qtavik6ltk59j7O-Q,2194
48
48
  py_neuromodulation/filter/kalman_filter_external.py,sha256=_7FFq-1GQY9mNA0EvmaM4wQ46DVkHC9bYFIgiw9b6nY,61367
@@ -82,28 +82,28 @@ py_neuromodulation/processing/data_preprocessor.py,sha256=4_yTKl9efCRFDicStgArQl
82
82
  py_neuromodulation/processing/filter_preprocessing.py,sha256=uIp2ewKQe3ojvGLHK26e9fLubEHOg9UF3uypTLkktFg,3611
83
83
  py_neuromodulation/processing/normalization.py,sha256=ixzl4zBotppQjJwCYdU2tGR_sJEf2mtYTfCRlRRd8E4,6150
84
84
  py_neuromodulation/processing/projection.py,sha256=JYYRG9_PcPL7a2ge3FjrCWiEIaCXGIKKegTjW1ecQ_Y,14772
85
- py_neuromodulation/processing/rereference.py,sha256=n9zLNDkuqeP7zwrP5bhsiXF9eYVPwqI4hZvvQzZ5cks,3304
85
+ py_neuromodulation/processing/rereference.py,sha256=X8YdOGt6mVxIE6gfkP7b1OsQVY7SXE-PMHZBgu_gP4k,3526
86
86
  py_neuromodulation/processing/resample.py,sha256=J87gjTSqWM7dDB1ZkoMo9EP3iAL4bI03K9RAMzCCowQ,1354
87
87
  py_neuromodulation/stream/__init__.py,sha256=4kagKtWOLKKZ7l6Dag1EpOFBOf8vv1eu2ll7EYbFD2Y,266
88
88
  py_neuromodulation/stream/backend_interface.py,sha256=Qn2iUH4l3VNfTXXWNJykseNzZQQAZF0M-5e_nd4qjMM,1789
89
- py_neuromodulation/stream/data_processor.py,sha256=-uSeszbqN51xBxFwa46YCpMjX5HHgN_aXDJ--aDdUhQ,12204
89
+ py_neuromodulation/stream/data_processor.py,sha256=pBXyma25KNl1npWLEHBYXcJ35fFoByzPd8WaaT5OdUQ,12398
90
90
  py_neuromodulation/stream/generator.py,sha256=UKLuM8gz2YLBuVQnQNkkOOKhwsyW27ZgvRJ_5BK7Glo,1588
91
91
  py_neuromodulation/stream/mnelsl_player.py,sha256=KksAWzr78JPqPlECweWZ7JThoxmTVWGo1_-m-fEL0N4,6351
92
92
  py_neuromodulation/stream/mnelsl_stream.py,sha256=-uHMCNLZwIcjiT9AMGWkJit5GsZjEJYkpki_DoaLzWY,4352
93
93
  py_neuromodulation/stream/settings.py,sha256=--K4NOlR3QNmg4ISgtvC1z_Ucf7MvGChvocck3w6LoI,11832
94
94
  py_neuromodulation/stream/stream.py,sha256=tcIH0tW99qxrMS3GeRF45xe9K3PO6FB8JlYESH5uNDk,16593
95
95
  py_neuromodulation/utils/__init__.py,sha256=Ok3STMpsflCTclJC9C1iQgdT-3HNGMM7U45w5Oespr4,46
96
- py_neuromodulation/utils/channels.py,sha256=bDUZZ8MB3T-Kcs6zI52MUmvC8qx9KAH-F9U0GyND79U,10649
96
+ py_neuromodulation/utils/channels.py,sha256=QYgDqB5lgTlPLSgpHMA_6U_AgIwWAoHxYF_1haeuggw,10934
97
97
  py_neuromodulation/utils/database.py,sha256=VEFsmbYDQWwaoZKmJCG8oyWoDTbfSiT_p0n7da9_Pn4,4755
98
98
  py_neuromodulation/utils/file_writer.py,sha256=eJLx3hK1-1VHKFXWHl4bPZH-dJ8zplUBLVQPDOa_VgQ,3427
99
99
  py_neuromodulation/utils/io.py,sha256=uE4k3ScspRy_RuWrrV7cdosH4eKxJKAkvnr5KC8SG6A,11515
100
100
  py_neuromodulation/utils/keyboard.py,sha256=swoxYhf4Q3pj50EKALUFt6hREfXnoXq2Z2q01IahPe8,1505
101
101
  py_neuromodulation/utils/logging.py,sha256=eIBFBRaAMb3KJnoxNFiCkMrTGzWwgfeDs8m5iq6FxN8,2178
102
102
  py_neuromodulation/utils/perf.py,sha256=10LYM13iTuWA-il-EMMOyZke3-1gcFEa6WLlHsJLO50,5471
103
- py_neuromodulation/utils/pydantic_extensions.py,sha256=nkgq8QHeKSJgmf1cBThBLQ9prdeZZ3_Pyeogs-WnHTA,11030
104
- py_neuromodulation/utils/types.py,sha256=hT4U9uj4PhHfu8cO7xE1oXj_KT1uHtP-rNY_cDbofr0,4643
105
- py_neuromodulation-0.1.4.dist-info/METADATA,sha256=VHAB6P_4FPz5JZtaJHtI15rXEMRuBbThJX8fKOv8-wE,7733
106
- py_neuromodulation-0.1.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
107
- py_neuromodulation-0.1.4.dist-info/entry_points.txt,sha256=hImSrCn9vJcwocoeehqNyJ-qj5Hgfrg2o6MPAnIaAa0,60
108
- py_neuromodulation-0.1.4.dist-info/licenses/LICENSE,sha256=EMBwuBRPBo-WkHSjqxZ55E6j95gKNBZ8x30pt-VGfrM,1118
109
- py_neuromodulation-0.1.4.dist-info/RECORD,,
103
+ py_neuromodulation/utils/pydantic_extensions.py,sha256=Z7dpGw6cKuXM1CM5AKYzlbdItGfsDT1Yx8MHSNA0VP0,10751
104
+ py_neuromodulation/utils/types.py,sha256=3AukBYuOqZJ5Mw6ZimRIYxGRSDdmMU9lsnaZkPUNWRA,4673
105
+ py_neuromodulation-0.1.7.dist-info/METADATA,sha256=u__cUSnRv36x-Ac1o_Imm8l3WTXjTAUKt9KtBcdkjMM,7903
106
+ py_neuromodulation-0.1.7.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
107
+ py_neuromodulation-0.1.7.dist-info/entry_points.txt,sha256=hImSrCn9vJcwocoeehqNyJ-qj5Hgfrg2o6MPAnIaAa0,60
108
+ py_neuromodulation-0.1.7.dist-info/licenses/LICENSE,sha256=EMBwuBRPBo-WkHSjqxZ55E6j95gKNBZ8x30pt-VGfrM,1118
109
+ py_neuromodulation-0.1.7.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.27.0
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any