minosse 0.1.0__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.
minosse/__init__.py ADDED
@@ -0,0 +1 @@
1
+ from .detector import analyze, analyze_path, Result
minosse/__main__.py ADDED
@@ -0,0 +1,2 @@
1
+ from .cli import main
2
+ raise SystemExit(main())
minosse/_version.py ADDED
@@ -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 = None
minosse/cli.py ADDED
@@ -0,0 +1,49 @@
1
+ """Command line entry point.
2
+
3
+ usage: python -m minosse [--proof [DIR]] <image> [image ...]
4
+
5
+ --proof renders a side-by-side evidence sheet per image (original plus
6
+ red heatmap panels for each fired signal) as <name>_proof.png, saved in
7
+ DIR if given, else next to the input image.
8
+ """
9
+
10
+ import os
11
+ import sys
12
+
13
+ from PIL import Image
14
+
15
+ from .detector import analyze
16
+ from .evidence import render
17
+
18
+
19
+ def main(argv=None):
20
+ args = list(argv if argv is not None else sys.argv[1:])
21
+ proof = False
22
+ proof_dir = None
23
+ if "--proof" in args:
24
+ i = args.index("--proof")
25
+ args.pop(i)
26
+ if i < len(args) and os.path.isdir(args[i]):
27
+ proof_dir = args.pop(i)
28
+ proof = True
29
+ if not args:
30
+ print(__doc__.strip())
31
+ return 2
32
+ for path in args:
33
+ img = Image.open(path)
34
+ r = analyze(img)
35
+ print(f"{path}")
36
+ print(f" signals : {', '.join(r.reasons) if r.reasons else 'none'}")
37
+ print(f" decay={r.decay:.4f} vhf_mf={r.vhf_mf:.3f} "
38
+ f"resid_std={r.resid_std:.4f} flat_noise={r.flat_noise:.4f}")
39
+ print(f" verdict : {r.verdict}")
40
+ if proof:
41
+ base = os.path.splitext(os.path.basename(path))[0] + "_proof.png"
42
+ out = os.path.join(proof_dir or os.path.dirname(path) or ".", base)
43
+ render(img, r).save(out)
44
+ print(f" proof : {out}")
45
+ return 0
46
+
47
+
48
+ if __name__ == "__main__":
49
+ raise SystemExit(main())
minosse/detector.py ADDED
@@ -0,0 +1,110 @@
1
+ """Minosse — detect AI-generated / AI-edited images.
2
+
3
+ Three complementary signals, each targeting a different artifact class:
4
+
5
+ ringing
6
+ Diffusion VAE decoders leave oscillating ripple halos around
7
+ high-contrast strokes (text, UI borders). Measured as a radial
8
+ profile of band-passed energy vs. distance from strong edges:
9
+ real edges decay fast, ringing persists 10-40 px out.
10
+
11
+ noisy-flats
12
+ AI images (and AI "film grain") carry noise in regions that should
13
+ be perfectly flat — solid UI panels, plain backgrounds. Real
14
+ screenshots have truly flat flats, and JPEG smooths photo flats.
15
+
16
+ synthetic-noise
17
+ Generated/upsampled noise dies out before the Nyquist frequency,
18
+ while real sensor noise stays hot at the highest frequencies. An
19
+ image with strong overall residual noise but a cold top ring of the
20
+ spectrum — and noise present even in flat regions — is synthetic.
21
+
22
+ Thresholds were tuned on data/clean (65 real photos + screenshots) and
23
+ data/dirty (5 AI-generated/edited): 5/5 detected, 0/65 false positives.
24
+ """
25
+
26
+ from dataclasses import dataclass, field
27
+
28
+ import numpy as np
29
+ from PIL import Image
30
+ from scipy import ndimage
31
+
32
+ _DIST_BINS = [(3, 6), (6, 10), (10, 15), (15, 25), (25, 40), (40, 60), (60, 120)]
33
+
34
+
35
+ @dataclass
36
+ class Result:
37
+ decay: float # halo persistence: mid-distance / near-edge energy
38
+ halo_vs_bg: float # mid-distance energy vs image's own background
39
+ vhf_mf: float # spectral energy at Nyquist ring vs mid frequencies
40
+ resid_std: float # overall noise-residual strength
41
+ flat_noise: float # RMS residual inside coarse-flat regions
42
+ reasons: list = field(default_factory=list)
43
+ score: float = 0.0
44
+ verdict: str = ""
45
+
46
+
47
+ def _features(img: Image.Image) -> dict:
48
+ img = img.convert("RGB")
49
+ if max(img.size) > 1600:
50
+ s = 1600 / max(img.size)
51
+ img = img.resize((int(img.width * s), int(img.height * s)), Image.LANCZOS)
52
+ a = np.asarray(img, dtype=np.float64) / 255.0
53
+ g = a.mean(2)
54
+
55
+ # --- ringing: band energy vs distance from strong edges
56
+ grad = np.hypot(ndimage.sobel(g, axis=1), ndimage.sobel(g, axis=0))
57
+ edge = grad > np.quantile(grad, 0.97)
58
+ band = ndimage.gaussian_filter(g, 0.8) - ndimage.gaussian_filter(g, 3.0)
59
+ energy = ndimage.uniform_filter(band * band, size=5)
60
+ dist = ndimage.distance_transform_edt(~edge)
61
+ prof = []
62
+ for d0, d1 in _DIST_BINS:
63
+ m = (dist >= d0) & (dist < d1)
64
+ prof.append(float(np.median(energy[m])) if m.any() else 0.0)
65
+ p = np.array(prof) + 1e-12
66
+ decay = float(np.median(p[2:5]) / p[0])
67
+ halo_vs_bg = float(np.median(p[2:5]) / np.median(p[5:7]))
68
+
69
+ # --- spectral shape of the noise residual (center crop, windowed FFT)
70
+ h, w = g.shape
71
+ s2 = min(512, h, w)
72
+ y0, x0 = (h - s2) // 2, (w - s2) // 2
73
+ gc = g[y0:y0 + s2, x0:x0 + s2]
74
+ res_c = gc - ndimage.median_filter(gc, 3)
75
+ win = np.hanning(s2)
76
+ F = np.abs(np.fft.fftshift(np.fft.fft2(res_c * win[:, None] * win[None, :]))) ** 2
77
+ yy, xx = np.indices(F.shape)
78
+ r = np.hypot(yy - s2 / 2, xx - s2 / 2) / (s2 / 2)
79
+ ring = lambda a_, b_: F[(r >= a_) & (r < b_)].mean()
80
+ vhf_mf = float(ring(0.95, 1.2) / (ring(0.25, 0.5) + 1e-20))
81
+ resid_std = float(np.std(res_c))
82
+
83
+ # --- noise inside coarse-flat regions
84
+ coarse = ndimage.gaussian_filter(g, 4)
85
+ cgrad = np.hypot(ndimage.sobel(coarse, axis=1), ndimage.sobel(coarse, axis=0))
86
+ flat = cgrad <= np.quantile(cgrad, 0.20)
87
+ res = g - ndimage.median_filter(g, 3)
88
+ flat_noise = float(np.sqrt(np.mean(res[flat] ** 2)))
89
+
90
+ return dict(decay=decay, halo_vs_bg=halo_vs_bg, vhf_mf=vhf_mf,
91
+ resid_std=resid_std, flat_noise=flat_noise)
92
+
93
+
94
+ def analyze(img: Image.Image) -> Result:
95
+ f = _features(img)
96
+ reasons = []
97
+ if f["vhf_mf"] < 0.22 and f["decay"] > 0.065:
98
+ reasons.append("ringing")
99
+ if f["flat_noise"] > 0.015:
100
+ reasons.append("noisy-flats")
101
+ if f["vhf_mf"] < 0.09 and f["resid_std"] > 0.02 and f["flat_noise"] > 0.001:
102
+ reasons.append("synthetic-noise")
103
+
104
+ score = min(1.0, 0.75 * len(reasons)) if reasons else 0.0
105
+ verdict = "likely AI-generated/edited" if reasons else "likely clean"
106
+ return Result(**f, reasons=reasons, score=score, verdict=verdict)
107
+
108
+
109
+ def analyze_path(path: str) -> Result:
110
+ return analyze(Image.open(path))
minosse/evidence.py ADDED
@@ -0,0 +1,98 @@
1
+ """Render visual proof sheets: original image next to panels that
2
+ highlight, in red, the spatial evidence behind each fired signal.
3
+
4
+ - ringing : band-passed oscillation energy in the halo zone
5
+ (3-40 px from strong edges, off-stroke)
6
+ - noisy-flats : noise residual energy inside coarse-flat regions
7
+ - synthetic-noise: amplified full-frame noise residual (its spatial
8
+ texture is the evidence; spectrum stats in caption)
9
+ """
10
+
11
+ import numpy as np
12
+ from PIL import Image, ImageDraw
13
+ from scipy import ndimage
14
+
15
+ from .detector import Result, _features # noqa: F401 (shared pipeline)
16
+
17
+ _PANEL_W = 640
18
+
19
+
20
+ def _prep(img: Image.Image):
21
+ img = img.convert("RGB")
22
+ if max(img.size) > 1600:
23
+ s = 1600 / max(img.size)
24
+ img = img.resize((int(img.width * s), int(img.height * s)), Image.LANCZOS)
25
+ g = np.asarray(img, dtype=np.float64) / 255.0
26
+ return img, g.mean(2)
27
+
28
+
29
+ def _heat_overlay(base: Image.Image, heat: np.ndarray, gain: float = 1.0) -> Image.Image:
30
+ """Dim grayscale base with the heat map burned in as red."""
31
+ h = np.clip(heat * gain, 0, 1)
32
+ gray = np.asarray(base.convert("L"), dtype=np.float64) / 255.0 * 0.35
33
+ rgb = np.stack([gray + h * 0.65, gray + h * 0.10, gray], axis=-1)
34
+ return Image.fromarray((np.clip(rgb, 0, 1) * 255).astype(np.uint8))
35
+
36
+
37
+ def _abs_norm(x: np.ndarray, full_scale: float) -> np.ndarray:
38
+ """Absolute scaling: `full_scale` maps to 1.0. Calibrated to the
39
+ detector thresholds so clean images render dark instead of being
40
+ stretched to look hot by per-image normalization."""
41
+ return np.clip(np.sqrt(np.clip(x, 0, None)) / full_scale, 0, 1)
42
+
43
+
44
+ def _maps(g: np.ndarray) -> dict:
45
+ grad = np.hypot(ndimage.sobel(g, axis=1), ndimage.sobel(g, axis=0))
46
+ edge = grad > np.quantile(grad, 0.97)
47
+ band = ndimage.gaussian_filter(g, 0.8) - ndimage.gaussian_filter(g, 3.0)
48
+ energy = ndimage.uniform_filter(band * band, size=5)
49
+ dist = ndimage.distance_transform_edt(~edge)
50
+
51
+ coarse = ndimage.gaussian_filter(g, 4)
52
+ cgrad = np.hypot(ndimage.sobel(coarse, axis=1), ndimage.sobel(coarse, axis=0))
53
+ flat = cgrad <= np.quantile(cgrad, 0.20)
54
+ res = g - ndimage.median_filter(g, 3)
55
+
56
+ # Ringing evidence = oscillation where the image should be smooth:
57
+ # 8-40 px from a strong edge (past the 1-2 px overshoot every real
58
+ # edge has) AND locally flat at coarse scale, so legitimate texture
59
+ # (fabric, foliage, carpet) is excluded.
60
+ smooth = cgrad <= np.quantile(cgrad, 0.60)
61
+ halo = (dist >= 8) & (dist < 40) & smooth
62
+ ringing = np.where(halo, energy, 0.0)
63
+ flat_noise = np.where(flat, ndimage.uniform_filter(res * res, size=9), 0.0)
64
+
65
+ # full-scale values sit a bit above the detector's firing thresholds
66
+ return {
67
+ "ringing": _abs_norm(ringing, 0.007),
68
+ "noisy-flats": _abs_norm(flat_noise, 0.03),
69
+ "synthetic-noise": np.clip(np.abs(res) * 12, 0, 1),
70
+ }
71
+
72
+
73
+ def render(img: Image.Image, result: Result) -> Image.Image:
74
+ """Build a proof sheet: original + one evidence panel per fired
75
+ signal (all three panels if nothing fired, for comparison)."""
76
+ base, g = _prep(img)
77
+ maps = _maps(g)
78
+ signals = result.reasons if result.reasons else list(maps)
79
+
80
+ scale = _PANEL_W / base.width
81
+ size = (_PANEL_W, int(base.height * scale))
82
+ panels = [("original", base.resize(size, Image.LANCZOS))]
83
+ for name in signals:
84
+ overlay = _heat_overlay(base, maps[name])
85
+ panels.append((name, overlay.resize(size, Image.LANCZOS)))
86
+
87
+ cap_h = 26
88
+ sheet = Image.new("RGB", (_PANEL_W * len(panels), size[1] + cap_h), (12, 12, 12))
89
+ draw = ImageDraw.Draw(sheet)
90
+ for i, (name, panel) in enumerate(panels):
91
+ sheet.paste(panel, (i * _PANEL_W, cap_h))
92
+ label = name if name == "original" else (
93
+ f"{name} {'[FIRED]' if name in result.reasons else '(not fired)'}")
94
+ draw.text((i * _PANEL_W + 8, 6), label,
95
+ fill=(255, 80, 80) if name in result.reasons else (200, 200, 200))
96
+ draw.text((sheet.width - 320, 6), f"verdict: {result.verdict}",
97
+ fill=(255, 80, 80) if result.reasons else (120, 220, 120))
98
+ return sheet
@@ -0,0 +1,159 @@
1
+ Metadata-Version: 2.4
2
+ Name: minosse
3
+ Version: 0.1.0
4
+ Summary: Detects AI-generated or AI-edited images using signal-processing artifact analysis — no ML model.
5
+ Project-URL: Homepage, https://github.com/cesp99/Minosse
6
+ Project-URL: Issues, https://github.com/cesp99/Minosse/issues
7
+ Author: cesp99
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: ai-detection,diffusion,image-forensics,signal-processing
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Topic :: Scientific/Engineering :: Image Processing
23
+ Classifier: Topic :: Security
24
+ Requires-Python: >=3.9
25
+ Requires-Dist: numpy
26
+ Requires-Dist: pillow
27
+ Requires-Dist: scipy
28
+ Description-Content-Type: text/markdown
29
+
30
+ # Minosse
31
+
32
+ Detects AI-generated or AI-edited images using three complementary,
33
+ hand-crafted artifact signals — **no ML model, just signal processing.**
34
+
35
+ Minosse looks for tell-tale traces that diffusion models and neural
36
+ upsamplers leave behind: ringing halos around text, noise in flat
37
+ regions that shouldn't have any, and a synthetic signature in the
38
+ high-frequency spectrum.
39
+
40
+ ## Signals
41
+
42
+ | Signal | What it catches | How it works |
43
+ |---|---|---|
44
+ | **ringing** | Diffusion VAE decoder halos | Radial profile of band-pass energy vs. distance from strong edges. Real edges decay fast; ringing persists 10–40 px out. |
45
+ | **noisy-flats** | AI "film grain" on solid panels | RMS residual noise inside coarse-flat regions. Real screenshots are truly flat; JPEG smooths photo flats. |
46
+ | **synthetic-noise** | Upsampled / generated noise | Spectral energy at the Nyquist ring vs. mid frequencies. Real sensor noise stays hot at the highest frequencies; synthetic noise dies off. |
47
+
48
+ An image is flagged if **any** signal fires; the fired signals are reported
49
+ along with the raw feature values.
50
+
51
+
52
+ ## Quick start
53
+
54
+ ```bash
55
+ python -m venv .venv
56
+ .venv/bin/pip install -r requirements.txt
57
+
58
+ # Analyse a single image
59
+ .venv/bin/python -m minosse path/to/image.png
60
+
61
+ # Analyse multiple images with visual proof sheets
62
+ .venv/bin/python -m minosse --proof results/ image1.png image2.jpg
63
+ ```
64
+
65
+ ### `--proof` output
66
+
67
+ The `--proof` flag writes `<name>_proof.png`: a side-by-side sheet with
68
+ the original image next to one panel per fired signal, with the artifact
69
+ evidence burned in as a **red heatmap**.
70
+
71
+ Heat is scaled absolutely (calibrated to the detection thresholds), so
72
+ clean images render dark — the glow itself is the proof. For ringing,
73
+ the map only shows oscillation in regions that *should* be smooth (8–40 px
74
+ from strong edges, locally flat at coarse scale), so legitimate texture
75
+ doesn't light up.
76
+
77
+ ## Python API
78
+
79
+ ```python
80
+ from minosse import analyze, analyze_path, Result
81
+
82
+ # From a file path
83
+ r = analyze_path("image.png")
84
+ print(r.verdict) # "likely AI-generated/edited" or "likely clean"
85
+ print(r.reasons) # e.g. ["ringing", "synthetic-noise"]
86
+ print(r.decay) # ringing feature value
87
+ print(r.vhf_mf) # spectral ratio feature value
88
+ print(r.flat_noise) # flat-region noise feature value
89
+ print(r.resid_std) # overall residual noise
90
+
91
+ # From a PIL Image
92
+ from PIL import Image
93
+ img = Image.open("image.png")
94
+ r = analyze(img)
95
+ ```
96
+
97
+ ### `Result` fields
98
+
99
+ | Field | Type | Description |
100
+ |---|---|---|
101
+ | `verdict` | `str` | `"likely AI-generated/edited"` or `"likely clean"` |
102
+ | `reasons` | `list[str]` | Fired signal names: `ringing`, `noisy-flats`, `synthetic-noise` |
103
+ | `score` | `float` | Confidence score `[0, 1]` (0.75 per firing signal, capped at 1.0) |
104
+ | `decay` | `float` | Ringing persistence: mid-distance / near-edge band energy |
105
+ | `halo_vs_bg` | `float` | Mid-distance energy vs. image background |
106
+ | `vhf_mf` | `float` | Spectral ratio: Nyquist ring / mid frequencies |
107
+ | `resid_std` | `float` | Standard deviation of the noise residual |
108
+ | `flat_noise` | `float` | RMS residual inside coarse-flat regions |
109
+
110
+ ### Visual proof from Python
111
+
112
+ ```python
113
+ from minosse import analyze, evidence
114
+ from PIL import Image
115
+
116
+ img = Image.open("image.png")
117
+ r = analyze(img)
118
+ sheet = evidence.render(img, r)
119
+ sheet.save("proof.png")
120
+ ```
121
+
122
+ ## Project structure
123
+
124
+ ```
125
+ minosse/
126
+ ├── minosse/
127
+ │ ├── __init__.py # Public API: analyze, analyze_path, Result
128
+ │ ├── __main__.py # python -m minosse entry point
129
+ │ ├── cli.py # Argument parsing and CLI loop
130
+ │ ├── detector.py # Feature extraction + threshold logic
131
+ │ └── evidence.py # Visual proof sheet renderer
132
+ ├── results/ # Default --proof output directory
133
+ ├── requirements.txt # numpy, pillow, scipy
134
+ └── README.md
135
+ ```
136
+
137
+ ## How it works (briefly)
138
+
139
+ 1. **Preprocessing** — the image is converted to RGB and downscaled if
140
+ its longest edge exceeds 1600 px.
141
+ 2. **Feature extraction** (`detector._features()`) — three independent
142
+ signal-processing pipelines compute scalar features from the
143
+ grayscale luminance.
144
+ 3. **Thresholding** — each feature is compared against a tuned threshold;
145
+ if it exceeds the threshold the corresponding signal is flagged.
146
+ 4. **Verdict** — if any signal fires, the image is marked likely
147
+ AI-generated/edited; otherwise it's likely clean.
148
+
149
+ For a deeper explanation of each signal, see [docs/signals.md](docs/signals.md).
150
+
151
+ ## Caveats
152
+
153
+ The thresholds are calibrated on a small set of examples and should be
154
+ re-validated as more AI samples are added. See [docs/tuning.md](docs/tuning.md)
155
+ for instructions on re-tuning.
156
+
157
+ ## License
158
+
159
+ MIT
@@ -0,0 +1,11 @@
1
+ minosse/__init__.py,sha256=2QKb-KV8pdF7Q1C2I0xsoXBUTZKE8PwNODsbwee-nxk,52
2
+ minosse/__main__.py,sha256=ee5vE0xcUZM3fPmxicvcw-IJXkm6nOwxARREHBWpF8Q,47
3
+ minosse/_version.py,sha256=n_5vdJsPNu7wZ57LGuRL585uvll-hiuvZUBWzdG0RQU,520
4
+ minosse/cli.py,sha256=JPoUPqq0fPmS2TKH2Rvf4Puu7uOfAv-frDDfrl1NNsM,1450
5
+ minosse/detector.py,sha256=rvud6lZ3kYKeL3bggDY-P3aug0591w1RnIpiProLcNA,4353
6
+ minosse/evidence.py,sha256=ByLLSjyjmJXaZyL_gkU-KS56HKIWcuilGKcvtNVC4Vc,4175
7
+ minosse-0.1.0.dist-info/METADATA,sha256=10LoUd2C_LQCD9Yn3IvakAyAUeEKlpAXFjtEjudY6jI,5919
8
+ minosse-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
9
+ minosse-0.1.0.dist-info/entry_points.txt,sha256=hqd_YmuzOB1tOABMTB-C1ijKkjdgrTp0PbOY20xWumM,45
10
+ minosse-0.1.0.dist-info/licenses/LICENSE,sha256=RnUiRM7llD6-Twf5z-GgfFyZ-XrR_IBEq2Ryrlvp8ZM,1071
11
+ minosse-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ minosse = minosse.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Carlo Esposito
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.