SeInE-orientation 0.1.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,51 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ jobs:
9
+ build:
10
+ runs-on: ubuntu-latest
11
+
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ with:
15
+ fetch-depth: 0
16
+
17
+ - uses: actions/setup-python@v5
18
+ with:
19
+ python-version: "3.x"
20
+
21
+ - name: Install build tools
22
+ run: python -m pip install --upgrade build
23
+
24
+ - name: Build package
25
+ run: python -m build
26
+
27
+ - name: Upload distribution artifacts
28
+ uses: actions/upload-artifact@v4
29
+ with:
30
+ name: python-package-distributions
31
+ path: dist/
32
+
33
+ publish:
34
+ needs: build
35
+ runs-on: ubuntu-latest
36
+
37
+ environment:
38
+ name: pypi
39
+
40
+ permissions:
41
+ id-token: write
42
+
43
+ steps:
44
+ - name: Download distributions
45
+ uses: actions/download-artifact@v4
46
+ with:
47
+ name: python-package-distributions
48
+ path: dist/
49
+
50
+ - name: Publish to PyPI
51
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,11 @@
1
+ *.pyc
2
+ *.egg-info
3
+
4
+ interactive/
5
+ notebooks/
6
+ figures/
7
+ Discussions/
8
+ data/
9
+
10
+
11
+ seine_orientation/_version.py
@@ -0,0 +1,13 @@
1
+ Metadata-Version: 2.4
2
+ Name: SeInE-orientation
3
+ Version: 0.1.0
4
+ Summary: Orientation Selectivity Inference from a Gabor model
5
+ Author: Alexander Schmidt
6
+ Requires-Python: >=3.11
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: numpy
9
+ Requires-Dist: scipy
10
+ Requires-Dist: SeInE-HB>=0.1.3
11
+ Requires-Dist: event-estimator>=0.1.1
12
+
13
+ Inferring model parameters of tilted Gabor-filter with non-linear response rate and overdispersed spike count responses, using SeInE
@@ -0,0 +1 @@
1
+ Inferring model parameters of tilted Gabor-filter with non-linear response rate and overdispersed spike count responses, using SeInE
@@ -0,0 +1,13 @@
1
+ Metadata-Version: 2.4
2
+ Name: SeInE-orientation
3
+ Version: 0.1.0
4
+ Summary: Orientation Selectivity Inference from a Gabor model
5
+ Author: Alexander Schmidt
6
+ Requires-Python: >=3.11
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: numpy
9
+ Requires-Dist: scipy
10
+ Requires-Dist: SeInE-HB>=0.1.3
11
+ Requires-Dist: event-estimator>=0.1.1
12
+
13
+ Inferring model parameters of tilted Gabor-filter with non-linear response rate and overdispersed spike count responses, using SeInE
@@ -0,0 +1,20 @@
1
+ .gitignore
2
+ README.md
3
+ pyproject.toml
4
+ .github/workflows/release.yml
5
+ SeInE_orientation.egg-info/PKG-INFO
6
+ SeInE_orientation.egg-info/SOURCES.txt
7
+ SeInE_orientation.egg-info/dependency_links.txt
8
+ SeInE_orientation.egg-info/requires.txt
9
+ SeInE_orientation.egg-info/scm_file_list.json
10
+ SeInE_orientation.egg-info/scm_version.json
11
+ SeInE_orientation.egg-info/top_level.txt
12
+ seine_orientation/BayesModel.py
13
+ seine_orientation/__init__.py
14
+ seine_orientation/_version.py
15
+ seine_orientation/process_session.py
16
+ seine_orientation/utils/__init__.py
17
+ seine_orientation/utils/display_inference_results.py
18
+ seine_orientation/utils/utils_analysis.py
19
+ seine_orientation/utils/utils_display.py
20
+ seine_orientation/utils/utils_model.py
@@ -0,0 +1,4 @@
1
+ numpy
2
+ scipy
3
+ SeInE-HB>=0.1.3
4
+ event-estimator>=0.1.1
@@ -0,0 +1,16 @@
1
+ {
2
+ "files": [
3
+ ".github/workflows/release.yml",
4
+ ".gitignore",
5
+ "README.md",
6
+ "pyproject.toml",
7
+ "seine_orientation/BayesModel.py",
8
+ "seine_orientation/__init__.py",
9
+ "seine_orientation/process_session.py",
10
+ "seine_orientation/utils/__init__.py",
11
+ "seine_orientation/utils/display_inference_results.py",
12
+ "seine_orientation/utils/utils_analysis.py",
13
+ "seine_orientation/utils/utils_display.py",
14
+ "seine_orientation/utils/utils_model.py"
15
+ ]
16
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "0.1.0",
3
+ "distance": 0,
4
+ "node": "gffe43a317a747f366b3c4784d510adc1ddf141e7",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-10-02"
8
+ }
@@ -0,0 +1 @@
1
+ seine_orientation
@@ -0,0 +1,33 @@
1
+ [build-system]
2
+ requires = [
3
+ "setuptools>=77.0.3",
4
+ "setuptools-scm>=8",
5
+ ]
6
+ build-backend = "setuptools.build_meta"
7
+
8
+ [project]
9
+ name = "SeInE-orientation"
10
+ dynamic = ["version"]
11
+ description = "Orientation Selectivity Inference from a Gabor model"
12
+ readme = "README.md"
13
+ requires-python = ">=3.11"
14
+ license = { file = "LICENSE" }
15
+
16
+ authors = [
17
+ { name = "Alexander Schmidt" }
18
+ ]
19
+
20
+ dependencies = [
21
+ "numpy",
22
+ "scipy",
23
+ "SeInE-HB>=0.1.3",
24
+ "event-estimator>=0.1.1"
25
+ ]
26
+
27
+ [tool.setuptools.packages.find]
28
+ where = ["."]
29
+ include = ["seine_orientation*"]
30
+
31
+ [tool.setuptools_scm]
32
+ version_file = "seine_orientation/_version.py"
33
+ local_scheme = "no-local-version"
@@ -0,0 +1,283 @@
1
+ import numpy as np
2
+ import time
3
+ import itertools
4
+
5
+ from seine.NestedSamplingMethods import (
6
+ run_sampling,
7
+ )
8
+ from seine import HierarchicalModel, functions as prior_fn
9
+ from seine.structures import (
10
+ prior_structure,
11
+ )
12
+
13
+ from .utils import gabor_filter, gabor_response, sine_grating, softplus, ReLU, sigmoid
14
+
15
+
16
+ class HierarchicalBayesInference(HierarchicalModel):
17
+
18
+ def set_gratings(self, measure_points, FoV_range, FoV_steps):
19
+
20
+ self.measure_points = measure_points
21
+
22
+ self.X_FoV, self.Y_FoV = np.meshgrid(
23
+ np.linspace(-FoV_range[0] / 2, FoV_range[0] / 2, FoV_steps),
24
+ np.linspace(-FoV_range[1] / 2, FoV_range[1] / 2, FoV_steps),
25
+ )
26
+ self.FoV_grid = np.dstack((self.X_FoV, self.Y_FoV))
27
+
28
+ FoV_steps = self.X_FoV.shape[0]
29
+
30
+ self.gratings = np.zeros(
31
+ (
32
+ *[len(mp) for mp in self.measure_points],
33
+ FoV_steps,
34
+ FoV_steps,
35
+ )
36
+ )
37
+ for prod in itertools.product(
38
+ enumerate(self.measure_points[0]),
39
+ enumerate(self.measure_points[1]),
40
+ enumerate(self.measure_points[2]),
41
+ ):
42
+ idx, elems = zip(*prod)
43
+ phi_0, theta, f = elems
44
+ self.gratings[*idx, ...] = sine_grating(
45
+ self.X_FoV, # [0, ...],
46
+ self.Y_FoV, # [0, ...],
47
+ theta,
48
+ f,
49
+ phi_0,
50
+ square=True,
51
+ )
52
+
53
+ def set_priors(self, priors_init=None, coding="simple"):
54
+
55
+ if priors_init is None:
56
+
57
+ if hasattr(self, "data"):
58
+ fmap = self.data["observed_counts"] / self.data["T"]
59
+ A0_guess, A_guess = np.percentile(fmap, [50, 90])
60
+ A0_guess = np.maximum(A0_guess, 1.0)
61
+ A_guess = np.maximum(A_guess, 2.0)
62
+ else:
63
+ A0_guess, A_guess = 1.0, 3.0
64
+ self.priors_init = {}
65
+ # print("A guesses from data:", A0_guess, A_guess)
66
+
67
+ ## define gabor model priors
68
+ if coding in ["simple", "complex"]:
69
+ self.priors_init["theta"] = prior_structure(
70
+ prior_fn.bounded_flat,
71
+ low=-np.pi / 2,
72
+ high=np.pi / 2,
73
+ label=r"$\theta$",
74
+ periodic=True,
75
+ )
76
+ self.priors_init["f"] = prior_structure(
77
+ prior_fn.halfnorm_ppf,
78
+ loc=0.0,
79
+ scale=5.0,
80
+ label=r"$f$",
81
+ )
82
+ if coding == "simple":
83
+ ## not required for complex cell model
84
+ self.priors_init["phi_0"] = prior_structure(
85
+ prior_fn.bounded_flat,
86
+ low=-np.pi,
87
+ high=np.pi,
88
+ periodic=True,
89
+ label=r"$\phi_0$",
90
+ )
91
+ else:
92
+ self.priors_init["phi_0"] = prior_structure(
93
+ None,
94
+ value=0.0,
95
+ periodic=True,
96
+ label=r"$\phi_0$",
97
+ )
98
+
99
+ self.priors_init["sigma"] = prior_structure(
100
+ prior_fn.halfnorm_ppf,
101
+ loc=0.05,
102
+ scale=0.1,
103
+ label=r"$\sigma$",
104
+ )
105
+ self.priors_init["gamma"] = prior_structure(
106
+ prior_fn.halfnorm_ppf,
107
+ loc=1.0,
108
+ scale=1.0,
109
+ label=r"$\gamma$",
110
+ )
111
+ self.priors_init["theta_gauss"] = prior_structure(
112
+ prior_fn.bounded_flat,
113
+ low=-np.pi / 2,
114
+ high=np.pi / 2,
115
+ periodic=True,
116
+ label=r"$\theta_{gauss}$",
117
+ )
118
+
119
+ ## define nonlinearity priors - should start with "nl_" to be recognized by model response function
120
+ self.priors_init["nl_baseline"] = prior_structure(
121
+ prior_fn.halfnorm_ppf,
122
+ loc=0.0,
123
+ scale=A0_guess,
124
+ label=r"$A_{baseline}$",
125
+ )
126
+ if coding in ["simple", "complex"]:
127
+ self.priors_init["nl_transition"] = prior_structure(
128
+ prior_fn.halfnorm_ppf,
129
+ loc=0.0,
130
+ scale=1.0,
131
+ label=r"$A_{transition}$",
132
+ )
133
+ self.priors_init["nl_amplitude"] = prior_structure(
134
+ prior_fn.halfnorm_ppf,
135
+ loc=A0_guess * 0.2,
136
+ scale=A_guess - A0_guess,
137
+ label=r"$A_{amplitude}$",
138
+ )
139
+
140
+ # parameters for loglikelihood
141
+ self.priors_init["logl_alpha"] = prior_structure(
142
+ prior_fn.halfnorm_ppf,
143
+ loc=0.0,
144
+ scale=1.0,
145
+ label=r"$\alpha_{logl}$",
146
+ )
147
+
148
+ else:
149
+ self.priors_init = priors_init
150
+
151
+ super().set_priors(self.priors_init)
152
+
153
+ def get_model_rate_response(self, params, coding="simple"):
154
+
155
+ ## transform parameters, if not already provided in proper format
156
+ if not isinstance(params, dict):
157
+ params = self.get_params_from_p(params)
158
+
159
+ ## get (normalized) model response
160
+ if coding == "random":
161
+ response = np.zeros(self.dimensions["shape"])
162
+ else:
163
+ response = gabor_response(
164
+ self.X_FoV,
165
+ self.Y_FoV,
166
+ params,
167
+ self.gratings,
168
+ mode=coding,
169
+ significance_threshold=0.01,
170
+ logger=self.log,
171
+ )
172
+ self.timeit("calculating model and response firing rate")
173
+
174
+ ## apply nonlinearity to adjust to scale of firing rates and get final model response in firing rate units
175
+ rate_response = self.apply_nonlinearity(response, params)
176
+ return rate_response
177
+
178
+ def apply_nonlinearity(self, response, params, nonlinearity="softplus"):
179
+
180
+ if nonlinearity == "softplus":
181
+ # print(
182
+ # f"Applying softplus nonlinearity with parameters: baseline={params.get('nl_baseline', 0.0):.3f}, transition={params.get('nl_transition', 1.0):.3f}, amplitude={params.get('nl_amplitude', 1.0):.3f}"
183
+ # )
184
+ rate_response = softplus(
185
+ response * params.get("nl_amplitude", 1.0),
186
+ alpha=params.get("nl_transition", 1.0),
187
+ delta=params.get("nl_baseline", 0.0),
188
+ )
189
+ elif nonlinearity == "ReLU":
190
+ # rate_response =
191
+ raise NotImplementedError("ReLU nonlinearity not implemented yet")
192
+ elif nonlinearity == "sigmoid":
193
+ raise NotImplementedError("Sigmoid nonlinearity not implemented yet")
194
+ else:
195
+ raise ValueError(f"Unknown nonlinearity: {nonlinearity}")
196
+
197
+ self.timeit("applying nonlinearity")
198
+ return rate_response
199
+
200
+ def set_logp_func(self, coding="simple"):
201
+ """
202
+ some nice description
203
+ """
204
+
205
+ def get_logp(p_in):
206
+
207
+ self.timeit()
208
+ """
209
+ build switch between 3 models:
210
+ - gabor type model
211
+ - fourier component model (with n components in each direction)
212
+ - ellipse in rate space
213
+ """
214
+
215
+ params = self.get_params_from_p(p_in)
216
+ self.timeit("transforming parameters")
217
+
218
+ model_rate_response = self.get_model_rate_response(params, coding)
219
+ if not (model_rate_response.shape == self.data["observed_counts"].shape):
220
+ model_rate_response = model_rate_response[None, ...]
221
+
222
+ logp = self.probability_of_spike_observation(
223
+ model_rate_response,
224
+ model="negative_binomial",
225
+ **params,
226
+ )
227
+ self.timeit("calculating log probability of spike observation")
228
+
229
+ return logp.sum()
230
+
231
+ return get_logp
232
+
233
+
234
+ def run_inference(
235
+ hbm: HierarchicalBayesInference,
236
+ coding="simple",
237
+ n_live=200,
238
+ nP=1,
239
+ dlogz=1.0,
240
+ show_status=True,
241
+ ):
242
+ hbm.set_priors(coding=coding)
243
+ my_trafo = hbm.set_prior_transform()
244
+ my_logp = hbm.set_logp_func(coding=coding)
245
+
246
+ results, sampler = run_sampling(
247
+ my_trafo,
248
+ my_logp,
249
+ hbm.parameter_names_all,
250
+ hbm.periodic,
251
+ show_status=show_status,
252
+ n_live=n_live,
253
+ nP=nP,
254
+ dlogz=dlogz,
255
+ )
256
+ return results
257
+
258
+
259
+ def run_model_comparison(data, dwelltime, measure_points, show_status=True, **kwargs):
260
+
261
+ t_start = time.time()
262
+ hbm = HierarchicalBayesInference()
263
+
264
+ hbm.set_gratings(
265
+ measure_points,
266
+ [np.deg2rad(140), np.deg2rad(114)],
267
+ FoV_steps=51,
268
+ )
269
+
270
+ hbm.prepare_data(
271
+ data,
272
+ dwelltime,
273
+ iter_dims=False,
274
+ )
275
+
276
+ results = {}
277
+ for coding in ["random", "simple", "complex"]:
278
+ results[coding] = run_inference(
279
+ hbm, coding=coding, show_status=show_status, **kwargs
280
+ )
281
+ t_end = time.time()
282
+ print(f"Model comparison done after {t_end - t_start:.2f} seconds")
283
+ return results
@@ -0,0 +1,2 @@
1
+ from .BayesModel import HierarchicalBayesInference, run_inference, run_model_comparison
2
+ from .process_session import process_session
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '0.1.0'
22
+ __version_tuple__ = version_tuple = (0, 1, 0)
23
+
24
+ __commit_id__ = commit_id = 'gffe43a317'