visfeats 0.1__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.
visfeats/__init__.py ADDED
@@ -0,0 +1,85 @@
1
+ import logging
2
+ from collections.abc import Sequence
3
+ from importlib.util import find_spec
4
+ from typing import Dict, Union
5
+
6
+ from PIL import Image
7
+
8
+ from .chromatic import hsv_features
9
+ from .spatial_frequency import spatial_frequency_features
10
+ from .memorability import memorability_resmem
11
+ from .misc import rms
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ _FEATURES = {
16
+ "hsv_features": hsv_features,
17
+ "spatial_frequency_features": spatial_frequency_features,
18
+ "rms": rms,
19
+ "memorability_resmem": memorability_resmem,
20
+ }
21
+
22
+ __all__ = [
23
+ "extract_all_features",
24
+ "spatial_frequency_features",
25
+ "hsv_features",
26
+ "rms",
27
+ "memorability_resmem",
28
+ ]
29
+
30
+
31
+ def extract_all_features(
32
+ image: Image.Image,
33
+ features: Union[str, Sequence[str]] = "all",
34
+ ) -> Dict[str, float]:
35
+ """Extract selected visual features from an image.
36
+
37
+ Parameters
38
+ ----------
39
+ image : PIL.Image.Image
40
+ Input image in any Pillow mode.
41
+ features : str or sequence of str, default="all"
42
+ ``"all"`` selects every package-level feature. A feature name or
43
+ sequence of feature names selects only those features. Available
44
+ names are ``"hsv_features"``, ``"spatial_frequency_features"``,
45
+ ``"rms"``, and ``"memorability_resmem"``.
46
+
47
+ Returns
48
+ -------
49
+ dict of {str: float}
50
+ A flat mapping of feature names to values. Multi-value features
51
+ contribute their own keys. ``memorability_resmem`` is skipped with a
52
+ warning if the optional ``resmem`` package is not installed.
53
+
54
+ Raises
55
+ ------
56
+ ValueError
57
+ If any requested feature name is not available.
58
+ """
59
+ if isinstance(features, str):
60
+ selected = list(_FEATURES) if features == "all" else [features]
61
+ else:
62
+ selected = list(dict.fromkeys(features))
63
+
64
+ unknown = set(selected) - _FEATURES.keys()
65
+ if unknown:
66
+ names = ", ".join(sorted(unknown))
67
+ raise ValueError(f"Unknown feature name(s): {names}")
68
+
69
+ extracted: Dict[str, float] = {}
70
+ for name in selected:
71
+ if name == "memorability_resmem" and find_spec("resmem") is None:
72
+ logger.warning(
73
+ "Skipping memorability_resmem because the optional `resmem` "
74
+ "package is not installed. Install it with: "
75
+ "pip install 'visfeats[resmem]'"
76
+ )
77
+ continue
78
+
79
+ result = _FEATURES[name](image)
80
+ if isinstance(result, dict):
81
+ extracted.update(result)
82
+ else:
83
+ extracted[name] = float(result)
84
+
85
+ return extracted
visfeats/chromatic.py ADDED
@@ -0,0 +1,60 @@
1
+ from PIL import Image
2
+ from skimage import color
3
+ import numpy as np
4
+ from scipy.stats import circmean, entropy
5
+
6
+ HSV_RANGE = (0.0, 1.0)
7
+
8
+
9
+ def hsv_features(image: Image.Image, bins: int = 100) -> dict:
10
+ """Compute the mean and entropy of the HSV channels of an image.
11
+
12
+ Parameters
13
+ ----------
14
+ image : PIL.Image.Image
15
+ Input image.
16
+ bins : int, default=100
17
+ Number of bins for the histograms used to compute entropy.
18
+
19
+ Returns
20
+ -------
21
+ dict of {str: float}
22
+ A mapping of feature names to values:
23
+
24
+ ================== ======================================
25
+ Key Description
26
+ ================== ======================================
27
+ hue_mean Circular mean hue in [0, 1).
28
+ saturation_mean Mean saturation in [0, 1].
29
+ value_mean Mean value (brightness) in [0, 1].
30
+ hue_entropy Entropy of the hue histogram.
31
+ saturation_entropy Entropy of the saturation histogram.
32
+ value_entropy Entropy of the value histogram.
33
+ ================== ======================================
34
+
35
+ Notes
36
+ -----
37
+ The image is converted to RGB, then to HSV using
38
+ ``skimage.color.rgb2hsv``. The mean and histogram entropy are computed
39
+ for each channel.
40
+ """
41
+ rgb = image.convert("RGB")
42
+ array = np.array(rgb)
43
+ hsv = color.rgb2hsv(array)
44
+
45
+ hue = hsv[:, :, 0]
46
+ saturation = hsv[:, :, 1]
47
+ value = hsv[:, :, 2]
48
+
49
+ hue_pk = np.histogram(hue, bins=bins, range=HSV_RANGE, density=True)
50
+ saturation_pk = np.histogram(saturation, bins=bins, range=HSV_RANGE, density=True)
51
+ value_pk = np.histogram(value, bins=bins, range=HSV_RANGE, density=True)
52
+
53
+ return {
54
+ "hue_mean": float(circmean(hue, high=1.0, low=0.0)), # circular mean of hue
55
+ "saturation_mean": float(saturation.mean()),
56
+ "value_mean": float(value.mean()),
57
+ "hue_entropy": float(entropy(hue_pk[0])),
58
+ "saturation_entropy": float(entropy(saturation_pk[0])),
59
+ "value_entropy": float(entropy(value_pk[0])),
60
+ }
@@ -0,0 +1,52 @@
1
+ from functools import lru_cache
2
+
3
+ from PIL import Image
4
+
5
+
6
+ @lru_cache(maxsize=1)
7
+ def _load_resmem():
8
+ # Import within function so resmem/torch load only when needed.
9
+ try:
10
+ from resmem import ResMem, transformer
11
+ except ImportError as error:
12
+ raise ImportError(
13
+ "memorability_resmem requires the optional dependency `resmem`. "
14
+ "Install it with: pip install resmem"
15
+ ) from error
16
+
17
+ model = ResMem(pretrained=True)
18
+ model.eval()
19
+ return model, transformer
20
+
21
+
22
+ def memorability_resmem(image: Image.Image) -> float:
23
+ """Estimate the intrinsic memorability of an image.
24
+
25
+ The image is converted to RGB, preprocessed with the ResMem transformer
26
+ (resized to 227 x 227 and normalized), and passed through the pretrained
27
+ ResMem network, a residual neural network trained to predict human
28
+ memorability scores (Needell and Bainbridge, 2022). The model is loaded
29
+ once per process and reused across calls.
30
+
31
+ Parameters
32
+ ----------
33
+ image : PIL.Image.Image
34
+ Input image in any Pillow mode; converted to RGB internally.
35
+
36
+ Returns
37
+ -------
38
+ float
39
+ Predicted memorability score in [0, 1]; higher values indicate a
40
+ more memorable image.
41
+
42
+ References
43
+ ----------
44
+ .. [1] Needell, C. D., & Bainbridge, W. A. (2022).
45
+ Embracing new techniques in deep learning for estimating image memorability.
46
+ Computational Brain & Behavior. https://doi.org/10.1007/s42113-022-00126-5
47
+ """
48
+ model, transformer = _load_resmem()
49
+ image = image.convert("RGB")
50
+ image_x = transformer(image)
51
+ prediction = model(image_x.view(-1, 3, 227, 227))
52
+ return float(prediction.detach().numpy().squeeze())
visfeats/misc.py ADDED
@@ -0,0 +1,23 @@
1
+ import numpy as np
2
+ from PIL import Image
3
+
4
+
5
+ def rms(image: Image.Image) -> float:
6
+ """Compute the root-mean-square (RMS) contrast of an image.
7
+
8
+ The image is converted to 8-bit grayscale and the RMS contrast is the
9
+ population standard deviation of the pixel intensities (values in
10
+ [0, 255]).
11
+
12
+ Parameters
13
+ ----------
14
+ image : PIL.Image.Image
15
+ Input image in any Pillow mode; converted to grayscale internally.
16
+
17
+ Returns
18
+ -------
19
+ float
20
+ RMS contrast. 0 for a uniform image; 127.5 is the maximum.
21
+ """
22
+ grayscale = image.convert("L")
23
+ return float(np.array(grayscale).std())
@@ -0,0 +1,272 @@
1
+ from typing import Dict, Tuple
2
+
3
+ import numpy as np
4
+ from PIL import Image
5
+ from scipy import fft
6
+
7
+
8
+ def _pad_and_resize_image(
9
+ image: Image.Image,
10
+ size: int = 1024,
11
+ pad: bool = True,
12
+ ) -> np.ndarray:
13
+ """Convert to grayscale, optionally pad to a square, then resize to ``size``."""
14
+ grayscale = np.asarray(image.convert("L"), dtype=np.float64)
15
+
16
+ if pad:
17
+ height, width = grayscale.shape
18
+
19
+ # Redies et al. pad using a uniform border at the image mean gray value.
20
+ side = max(height, width)
21
+ padded = np.full((side, side), grayscale.mean(), dtype=np.float64)
22
+
23
+ y0 = (side - height) // 2
24
+ x0 = (side - width) // 2
25
+ padded[y0 : y0 + height, x0 : x0 + width] = grayscale
26
+ grayscale = padded
27
+
28
+ # Pillow's F mode is float32; it preserves fractional values during bicubic
29
+ # resize (passing float64 with mode="F" would misread the buffer).
30
+ resized = Image.fromarray(grayscale.astype(np.float32), mode="F").resize(
31
+ (size, size),
32
+ resample=Image.Resampling.BICUBIC,
33
+ )
34
+
35
+ return np.asarray(resized, dtype=np.float64)
36
+
37
+
38
+ def _centered_power_spectrum(
39
+ grayscale: np.ndarray,
40
+ ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
41
+ """Return the centered 2D power spectrum and its (fy, fx) frequency grids."""
42
+ height, width = grayscale.shape
43
+ if height != width:
44
+ raise ValueError("Expected a square image.")
45
+
46
+ spectrum = fft.fftshift(fft.fft2(grayscale))
47
+ power = np.abs(spectrum) ** 2
48
+
49
+ # Frequencies expressed in cycles per image, rather than cycles/pixel.
50
+ # For a 1024-pixel image, values span roughly -512 to +511 cycles/image.
51
+ frequencies = fft.fftshift(fft.fftfreq(height)) * height
52
+ fy, fx = np.meshgrid(frequencies, frequencies, indexing="ij")
53
+
54
+ return power, fy, fx
55
+
56
+
57
+ def _radially_averaged_power_spectrum(
58
+ grayscale: np.ndarray,
59
+ ) -> Tuple[np.ndarray, np.ndarray]:
60
+ """Return radial spatial frequencies and mean power for a square image."""
61
+ power, fy, fx = _centered_power_spectrum(grayscale)
62
+ radius = np.hypot(fx, fy)
63
+
64
+ # Annular radial bins: 0-1, 1-2, 2-3 ... cycles/image.
65
+ radial_index = np.floor(radius).astype(np.intp)
66
+ max_radius = radial_index.max()
67
+
68
+ power_sum = np.bincount(
69
+ radial_index.ravel(),
70
+ weights=power.ravel(),
71
+ minlength=max_radius + 1,
72
+ )
73
+ sample_count = np.bincount(
74
+ radial_index.ravel(),
75
+ minlength=max_radius + 1,
76
+ )
77
+
78
+ radial_power = np.divide(
79
+ power_sum,
80
+ sample_count,
81
+ out=np.zeros_like(power_sum, dtype=np.float64),
82
+ where=sample_count > 0,
83
+ )
84
+
85
+ radial_frequency = np.arange(max_radius + 1, dtype=np.float64)
86
+
87
+ return radial_frequency, radial_power
88
+
89
+
90
+ def _spectral_centroid_and_variance(grayscale: np.ndarray) -> Tuple[float, float]:
91
+ """Return the power-weighted mean frequency and orientation circular variance."""
92
+ power, fy, fx = _centered_power_spectrum(grayscale)
93
+
94
+ # Exclude DC: it carries the mean luminance and has no defined orientation.
95
+ nonzero = np.hypot(fx, fy) > 0
96
+ power = power[nonzero]
97
+ radius = np.hypot(fx, fy)[nonzero]
98
+ angle = np.arctan2(fy, fx)[nonzero]
99
+
100
+ total_power = power.sum()
101
+ if total_power == 0:
102
+ raise ValueError("Total power of the Fourier spectrum is zero.")
103
+
104
+ centroid = (radius * power).sum() / total_power
105
+
106
+ # Orientation is axial (theta and theta + pi are equivalent in a
107
+ # real-valued image's spectrum), so angles are doubled before averaging.
108
+ resultant = np.abs((power * np.exp(2j * angle)).sum()) / total_power
109
+
110
+ return float(centroid), float(1.0 - resultant)
111
+
112
+
113
+ def _dimensionality(power: np.ndarray) -> float:
114
+ """Slope of log magnitude versus log rank of the radially averaged spectrum."""
115
+ # Skip the DC bin; rank all remaining components by magnitude.
116
+ magnitude = np.sqrt(power[1:])
117
+ magnitude = magnitude[magnitude > 0]
118
+ if magnitude.size < 2:
119
+ raise ValueError("At least two non-zero Fourier components are required.")
120
+
121
+ ranked = np.sort(magnitude)[::-1]
122
+ ranks = np.arange(1, ranked.size + 1)
123
+ return float(np.polyfit(np.log10(ranks), np.log10(ranked), deg=1)[0])
124
+
125
+
126
+ def spatial_frequency_features(
127
+ image: Image.Image,
128
+ size: int = 1024,
129
+ min_frequency: float = 10.0,
130
+ max_frequency: float = 256.0,
131
+ log_bins: int = 17,
132
+ pad: bool = True,
133
+ ) -> Dict[str, float]:
134
+ """
135
+ Calculate Fourier slope, Fourier sigma, dimensionality, spectral centroid,
136
+ and spectral variance of an image.
137
+
138
+ All are derived from the Fourier power spectrum of the grayscale image.
139
+
140
+ Parameters
141
+ ----------
142
+ image : PIL.Image.Image
143
+ Input image.
144
+ size : int, default=1024
145
+ Square analysis resolution.
146
+ min_frequency : float, default=10.0
147
+ Lowest fitted radial spatial frequency, in cycles/image.
148
+ max_frequency : float, default=256.0
149
+ Highest fitted radial spatial frequency, in cycles/image.
150
+ log_bins : int, default=17
151
+ Number of log-spaced frequency bins used before fitting [2]_.
152
+ pad : bool, default=True
153
+ Whether to pad non-square images to a square with the mean gray value
154
+ before resizing. If False, the image is resized directly to
155
+ ``size`` x ``size``, which distorts the aspect ratio of non-square
156
+ images.
157
+
158
+ Returns
159
+ -------
160
+ dict
161
+ ==================== ==============================================
162
+ Key Description
163
+ ==================== ==============================================
164
+ ``fourier_slope`` Slope of log power versus log frequency
165
+ (about -2 for natural scenes and many
166
+ artworks).
167
+ ``fourier_sigma`` Mean squared residual of the binned data
168
+ around the fitted line.
169
+ ``dimensionality`` Slope of log magnitude versus log rank of the
170
+ ranked spectrum (see Notes).
171
+ ``spectral_centroid`` Power-weighted mean spatial frequency, in
172
+ cycles/image.
173
+ ``spectral_variance`` Circular variance of the power spectrum over
174
+ orientation, in [0, 1] (see Notes).
175
+ ==================== ==============================================
176
+
177
+ Notes
178
+ -----
179
+ 1. Convert to grayscale, optionally (``pad``) pad to a square with the
180
+ mean gray value, and resize to ``size`` by bicubic interpolation [1]_.
181
+ 2. Compute the 2D power spectrum and average it in annular bins of
182
+ 1 cycle/image.
183
+ 3. ``fourier_slope`` and ``fourier_sigma``: keep bins between
184
+ ``min_frequency`` and ``max_frequency``, average log power and log
185
+ frequency within ``log_bins`` log-spaced bins, and fit a least-squares
186
+ line. The slope is ``fourier_slope``; the mean squared residual is
187
+ ``fourier_sigma`` [2]_.
188
+ 4. ``dimensionality``: using all bins except DC (no frequency
189
+ restriction), rank the magnitudes (square root of power) from largest
190
+ to smallest and fit a line to log magnitude versus log rank [3]_.
191
+ 5. ``spectral_centroid``: using the full 2D power spectrum without DC,
192
+ the mean radial frequency weighted by power (center of mass).
193
+ 6. ``spectral_variance``: circular variance, ``1 - R``, of the spectral
194
+ orientation weighted by power, where ``R`` is the length of the
195
+ power-weighted mean resultant vector of the doubled orientation angles
196
+ (the spectrum is point-symmetric). 0 means all power lies along one
197
+ orientation; 1 means power is spread evenly across orientations.
198
+
199
+ .. [1] Redies, C., Grebenkina, M., Mohseni, M., Kaduhm, A., & Dobel, C. (2020).
200
+ Global Image Properties Predict Ratings of Affective Pictures. Frontiers in Psychology, 11.
201
+ https://doi.org/10.3389/fpsyg.2020.00953
202
+ .. [2] Redies, C., Hasenstein, J., & Denzler, J. (2008).
203
+ Fractal-like image statistics in visual art: Similarity to natural scenes. Spatial Vision, 21(1–2), 137–148.
204
+ https://doi.org/10.1163/156856807782753921
205
+ .. [3] Uran, C., Peter, A., Lazar, A., Barnes, W., Klon-Lipok, J., Shapcott, K. A., Roese, R.,
206
+ Fries, P., Singer, W., & Vinck, M. (2022).
207
+ Predictive coding of natural images by V1 firing rates and rhythmic synchronization. Neuron, 110(7),
208
+ 1240-1257.e8. https://doi.org/10.1016/j.neuron.2022.01.002
209
+ """
210
+ grayscale = _pad_and_resize_image(image, size=size, pad=pad)
211
+ frequency, power = _radially_averaged_power_spectrum(grayscale)
212
+ dimensionality = _dimensionality(power)
213
+ centroid, variance = _spectral_centroid_and_variance(grayscale)
214
+ # Exclude DC (0 cycles/image) and restrict to the published fit range.
215
+ valid = (
216
+ (frequency >= min_frequency)
217
+ & (frequency <= max_frequency)
218
+ & (power > 0)
219
+ & np.isfinite(power)
220
+ )
221
+
222
+ frequency = frequency[valid]
223
+ power = power[valid]
224
+
225
+ if frequency.size < 2:
226
+ raise ValueError("Not enough valid Fourier frequencies to fit a line.")
227
+
228
+ log_frequency = np.log10(frequency)
229
+ log_power = np.log10(power)
230
+
231
+ # Equally spaced bins in log-frequency space.
232
+ edges = np.linspace(
233
+ np.log10(min_frequency),
234
+ np.log10(max_frequency),
235
+ log_bins + 1,
236
+ )
237
+ bin_index = np.digitize(log_frequency, edges, right=False) - 1
238
+ bin_index = np.clip(bin_index, 0, log_bins - 1)
239
+
240
+ counts = np.bincount(bin_index, minlength=log_bins)
241
+ x_sum = np.bincount(
242
+ bin_index,
243
+ weights=log_frequency,
244
+ minlength=log_bins,
245
+ )
246
+ y_sum = np.bincount(
247
+ bin_index,
248
+ weights=log_power,
249
+ minlength=log_bins,
250
+ )
251
+
252
+ occupied = counts > 0
253
+ x = x_sum[occupied] / counts[occupied]
254
+ y = y_sum[occupied] / counts[occupied]
255
+
256
+ if x.size < 2:
257
+ raise ValueError("Not enough occupied log-frequency bins to fit a line.")
258
+
259
+ # y = intercept + slope * x
260
+ slope, intercept = np.polyfit(x, y, deg=1)
261
+ fitted_y = intercept + slope * x
262
+
263
+ # Redies et al. (2007): sum of squared deviations / number of points.
264
+ fourier_sigma = np.mean((y - fitted_y) ** 2)
265
+
266
+ return {
267
+ "fourier_slope": float(slope),
268
+ "fourier_sigma": float(fourier_sigma),
269
+ "dimensionality": dimensionality,
270
+ "spectral_centroid": centroid,
271
+ "spectral_variance": variance,
272
+ }
@@ -0,0 +1,125 @@
1
+ Metadata-Version: 2.4
2
+ Name: visfeats
3
+ Version: 0.1
4
+ Summary: A library for extracting global visual features
5
+ Author-email: Qian Chu <qianchu99@gmail.com>
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: scikit-image
10
+ Provides-Extra: resmem
11
+ Requires-Dist: resmem; extra == "resmem"
12
+ Provides-Extra: dev
13
+ Requires-Dist: resmem; extra == "dev"
14
+ Requires-Dist: pytest; extra == "dev"
15
+ Requires-Dist: pre-commit; extra == "dev"
16
+ Dynamic: license-file
17
+
18
+ # VisFeats: Extraction of Global Visual Features from Images
19
+
20
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
21
+ ![Python Version](https://img.shields.io/badge/python-3.10%2B-blue)
22
+
23
+ VisFeats is a library for extracting global visual features from images.
24
+
25
+ ## Installation
26
+
27
+ VisFeats is installable via PyPI:
28
+
29
+ ```bash
30
+ pip install visfeats
31
+ ```
32
+
33
+ Or via GitHub (for the latest development version):
34
+
35
+ ```bash
36
+ pip install git+https://github.com/ncc-brain/VisFeats.git
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ To use VisFeats, first load an image using PIL:
42
+
43
+ ```python
44
+ from PIL import Image
45
+ image = Image.open("path/to/your/image.jpg")
46
+ ```
47
+
48
+ All functions in VisFeats take a PIL ``Image.Image`` as input and return the corresponding feature values. Available features are described below.
49
+
50
+ ### Extract multiple features
51
+
52
+ Use `extract_all_features` to collect all package-level features, or pass a
53
+ feature name or list of names to select a subset. The result is a flat
54
+ dictionary.
55
+
56
+ ```python
57
+ from visfeats import extract_all_features
58
+
59
+ features = extract_all_features(image, features=["rms", "hsv_features"])
60
+ ```
61
+
62
+ ### Mean and entropy of HSV channels
63
+
64
+ Chromatic features describe color in HSV (Hue, Saturation, Value) space, including channel means and entropies. The `hsv_features` function returns a dictionary with six keys:
65
+
66
+ - `hue_mean`
67
+ - `saturation_mean`
68
+ - `value_mean`
69
+ - `hue_entropy`
70
+ - `saturation_entropy`
71
+ - `value_entropy`
72
+
73
+ ```python
74
+ from visfeats import hsv_features
75
+ features = hsv_features(image)
76
+ ```
77
+
78
+ ### Spatial frequency features
79
+
80
+ Spatial frequency features capture the distribution of different spatial frequencies in an image, which relate to the level of detail and texture. The `spatial_frequency_features` function extracts these features:
81
+
82
+ - `fourier_slope`
83
+ - `fourier_sigma`
84
+ - `dimensionality`
85
+ - `spectral_centroid`
86
+ - `spectral_variance`
87
+
88
+ ```python
89
+ from visfeats import spatial_frequency_features
90
+ features = spatial_frequency_features(image)
91
+ ```
92
+
93
+ ### Memorability
94
+
95
+ While memorability is often considered a subjective property, recent research has shown that it can be predicted from image content. Here the `memorability_resmem` function estimates intrinsic memorability using the pretrained ResMem model described by [Needell and Bainbridge (2022)](https://doi.org/10.1007/s42113-022-00126-5).
96
+
97
+ ResMem is an optional dependency. It can be installed along with VisFeats using:
98
+
99
+ ```bash
100
+ pip install "visfeats[resmem]"
101
+ ```
102
+
103
+ Or separately with:
104
+
105
+ ```bash
106
+ pip install resmem
107
+ ```
108
+
109
+ Then you can use the `memorability_resmem` function to estimate the memorability of an image:
110
+
111
+ ```python
112
+ from visfeats import memorability_resmem
113
+ resmem_score = memorability_resmem(image)
114
+ ```
115
+
116
+ ### Other features
117
+
118
+ #### RMS contrast
119
+
120
+ The `rms` function computes the root-mean-square contrast as the population standard deviation of the image's grayscale pixel intensities.
121
+
122
+ ```python
123
+ from visfeats import rms
124
+ rms_contrast = rms(image)
125
+ ```
@@ -0,0 +1,10 @@
1
+ visfeats/__init__.py,sha256=rprYLjIT0pEu-MelUy1msGVLwKTzpCCxh3uZ6BGK0SA,2552
2
+ visfeats/chromatic.py,sha256=0SFdIygC6g5CzQu7_LBZh3-2frAgV311jNMjr7HBOrM,2091
3
+ visfeats/memorability.py,sha256=TotQGk6R2l0I-42ilJMesl6SEDmK-HunrgWrFlGn9dw,1719
4
+ visfeats/misc.py,sha256=YfLC2ptyKxx5yI_T9uKI1mES49qlVTdGVLa5ZDvMacI,628
5
+ visfeats/spatial_frequency.py,sha256=3cdEqD-U_Ce-0Rt76BA2yS-unHSjfucaVPxPiTbbGPU,10387
6
+ visfeats-0.1.dist-info/licenses/LICENSE,sha256=Kp1DPQGh36k51G0yLkCg_spVbXzIi66A3FbmfEfylaE,1065
7
+ visfeats-0.1.dist-info/METADATA,sha256=SbsKrwc_QiqFxA-0iyFOhUL364vD27Vbibn5mN9g54I,3495
8
+ visfeats-0.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
9
+ visfeats-0.1.dist-info/top_level.txt,sha256=BOjMNuJsd_gpQqQaG67djZI-cOCdYOrHkmvinMAerPM,9
10
+ visfeats-0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Qian Chu
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 @@
1
+ visfeats