small-fish-gui 1.0.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.
- small_fish_gui/__init__.py +41 -0
- small_fish_gui/__main__.py +15 -0
- small_fish_gui/gui/__init__.py +29 -0
- small_fish_gui/gui/animation.py +30 -0
- small_fish_gui/gui/general_help_screenshot.png +0 -0
- small_fish_gui/gui/help_module.py +256 -0
- small_fish_gui/gui/layout.py +184 -0
- small_fish_gui/gui/mapping_help_screenshot.png +0 -0
- small_fish_gui/gui/prompts.py +338 -0
- small_fish_gui/gui/segmentation_help_screenshot.png +0 -0
- small_fish_gui/gui/test.py +4 -0
- small_fish_gui/interface/__init__.py +10 -0
- small_fish_gui/interface/image.py +38 -0
- small_fish_gui/interface/output.py +42 -0
- small_fish_gui/interface/parameters.py +2 -0
- small_fish_gui/interface/testing.py +8 -0
- small_fish_gui/pipeline/_colocalisation.py +266 -0
- small_fish_gui/pipeline/_custom_errors.py +2 -0
- small_fish_gui/pipeline/_detection_visualisation.py +139 -0
- small_fish_gui/pipeline/_preprocess.py +272 -0
- small_fish_gui/pipeline/_segmentation.py +334 -0
- small_fish_gui/pipeline/_signaltonoise.py +219 -0
- small_fish_gui/pipeline/actions.py +127 -0
- small_fish_gui/pipeline/detection.py +640 -0
- small_fish_gui/pipeline/main.py +80 -0
- small_fish_gui/pipeline/test.py +116 -0
- small_fish_gui/start.py +7 -0
- small_fish_gui/utils.py +55 -0
- small_fish_gui-1.0.0.dist-info/METADATA +57 -0
- small_fish_gui-1.0.0.dist-info/RECORD +32 -0
- small_fish_gui-1.0.0.dist-info/WHEEL +4 -0
- small_fish_gui-1.0.0.dist-info/licenses/LICENSE +24 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#### New version for main.py
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import PySimpleGUI as sg
|
|
4
|
+
from ..gui import hub_prompt
|
|
5
|
+
from .actions import add_detection, save_results, compute_colocalisation
|
|
6
|
+
|
|
7
|
+
#'Global' parameters
|
|
8
|
+
user_parameters = dict() # Very important object containg all choice from user that will influence the behavior of the main loop.
|
|
9
|
+
acquisition_id = -1
|
|
10
|
+
result_df = pd.DataFrame()
|
|
11
|
+
cell_result_df = pd.DataFrame()
|
|
12
|
+
coloc_df = pd.DataFrame()
|
|
13
|
+
segmentation_done = False
|
|
14
|
+
cytoplasm_label = None
|
|
15
|
+
nucleus_label = None
|
|
16
|
+
|
|
17
|
+
while True : #Break this loop to close small_fish
|
|
18
|
+
try :
|
|
19
|
+
event, values = hub_prompt(result_df, segmentation_done)
|
|
20
|
+
|
|
21
|
+
if event == 'Add detection' :
|
|
22
|
+
|
|
23
|
+
new_result_df, new_cell_result_df, acquisition_id, user_parameters, segmentation_done, cytoplasm_label, nucleus_label = add_detection(
|
|
24
|
+
user_parameters=user_parameters,
|
|
25
|
+
segmentation_done=segmentation_done,
|
|
26
|
+
acquisition_id=acquisition_id,
|
|
27
|
+
cytoplasm_label = cytoplasm_label,
|
|
28
|
+
nucleus_label = nucleus_label,
|
|
29
|
+
)
|
|
30
|
+
result_df = pd.concat([result_df, new_result_df], axis=0)
|
|
31
|
+
cell_result_df = pd.concat([cell_result_df, new_cell_result_df], axis=0)
|
|
32
|
+
|
|
33
|
+
elif event == 'Save results' :
|
|
34
|
+
save_results(
|
|
35
|
+
result_df=result_df,
|
|
36
|
+
cell_result_df=cell_result_df,
|
|
37
|
+
coloc_df=coloc_df
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
elif event == 'Compute colocalisation' :
|
|
41
|
+
result_tables = values.setdefault('result_table', []) #Contains the lines selected by the user on the sum-up array.
|
|
42
|
+
|
|
43
|
+
res_coloc= compute_colocalisation(
|
|
44
|
+
result_tables,
|
|
45
|
+
result_dataframe=result_df
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
coloc_df = pd.concat(
|
|
49
|
+
[coloc_df,res_coloc],
|
|
50
|
+
axis= 0)
|
|
51
|
+
|
|
52
|
+
elif event == "Reset results" :
|
|
53
|
+
result_df = pd.DataFrame()
|
|
54
|
+
cell_result_df = pd.DataFrame()
|
|
55
|
+
coloc_df = pd.DataFrame()
|
|
56
|
+
acquisition_id = -1
|
|
57
|
+
segmentation_done = False
|
|
58
|
+
cytoplasm_label = None
|
|
59
|
+
nucleus_label = None
|
|
60
|
+
|
|
61
|
+
elif event == "Reset segmentation" :
|
|
62
|
+
segmentation_done = False
|
|
63
|
+
cytoplasm_label = None
|
|
64
|
+
nucleus_label = None
|
|
65
|
+
|
|
66
|
+
elif event == "Delete acquisitions" :
|
|
67
|
+
#TODO
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
elif event == "Batch detection" :
|
|
71
|
+
#TODO
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
else :
|
|
75
|
+
break
|
|
76
|
+
|
|
77
|
+
except Exception as error :
|
|
78
|
+
sg.popup(str(error))
|
|
79
|
+
raise error
|
|
80
|
+
quit()
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
def yielder(n) :
|
|
2
|
+
for i in range(n) :
|
|
3
|
+
yield i
|
|
4
|
+
|
|
5
|
+
def constructor(n) :
|
|
6
|
+
def inner() :
|
|
7
|
+
return yielder(n)
|
|
8
|
+
return inner
|
|
9
|
+
|
|
10
|
+
#test
|
|
11
|
+
|
|
12
|
+
N = 10
|
|
13
|
+
|
|
14
|
+
gen1 = yielder(N)
|
|
15
|
+
|
|
16
|
+
a = constructor(10)
|
|
17
|
+
|
|
18
|
+
print(type(a))
|
|
19
|
+
|
|
20
|
+
gen2 = a()
|
|
21
|
+
gen3 = a()
|
|
22
|
+
|
|
23
|
+
print('gen1 : ', list(gen1))
|
|
24
|
+
print('gen2 : ', list(gen2))
|
|
25
|
+
print('gen3 : ', list(gen3))
|
|
26
|
+
print('gen3 : ', list(gen3))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
#Napari test
|
|
30
|
+
|
|
31
|
+
import napari
|
|
32
|
+
import numpy as np
|
|
33
|
+
from napari.utils.events import Event
|
|
34
|
+
from napari.layers import Points
|
|
35
|
+
|
|
36
|
+
seed = 0
|
|
37
|
+
points_number = 500
|
|
38
|
+
rand_gen = np.random.default_rng(seed=seed)
|
|
39
|
+
|
|
40
|
+
im_shape = (10,1000,1000)
|
|
41
|
+
scale = (1,1,1)
|
|
42
|
+
|
|
43
|
+
im = np.zeros(shape=im_shape)
|
|
44
|
+
|
|
45
|
+
points = np.array(
|
|
46
|
+
list(zip(
|
|
47
|
+
rand_gen.integers(0,im_shape[0],size=points_number), #Z
|
|
48
|
+
rand_gen.integers(0,im_shape[1],size=points_number), #Y
|
|
49
|
+
rand_gen.integers(0,im_shape[2],size=points_number), #X
|
|
50
|
+
)),
|
|
51
|
+
dtype= int
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
spots_number = rand_gen.integers(0,20,points_number, dtype=int)
|
|
55
|
+
id_list = np.arange(points_number)
|
|
56
|
+
|
|
57
|
+
Viewer = napari.Viewer(title= "test", show=False)
|
|
58
|
+
Viewer.add_image(im),
|
|
59
|
+
cluster_layer = Viewer.add_points(
|
|
60
|
+
points,
|
|
61
|
+
size = 10,
|
|
62
|
+
scale=scale,
|
|
63
|
+
face_color= 'blue',
|
|
64
|
+
opacity= 0.7,
|
|
65
|
+
symbol= 'diamond',
|
|
66
|
+
name= 'foci',
|
|
67
|
+
features= {"spot_number" : spots_number, "id" : id_list},
|
|
68
|
+
feature_defaults= {"spot_number" : 0, "id" : -1})
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class Points_callback :
|
|
72
|
+
"""
|
|
73
|
+
Custom class to handle points number evolution during Napari run.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
def __init__(self, points, next_id) -> None:
|
|
77
|
+
self.points = points
|
|
78
|
+
self.next_id = next_id
|
|
79
|
+
self._set_callback()
|
|
80
|
+
|
|
81
|
+
def __str__(self) -> str:
|
|
82
|
+
string = 'Points_callback object state :\ncurrent_points_number : {0}\ncurrnet_id : {1}'.format(self.current_points_number, self.next_id)
|
|
83
|
+
return string
|
|
84
|
+
|
|
85
|
+
def get_points(self) :
|
|
86
|
+
return self.points
|
|
87
|
+
|
|
88
|
+
def get_next_id(self) :
|
|
89
|
+
return self.next_id
|
|
90
|
+
|
|
91
|
+
def _set_callback(self) :
|
|
92
|
+
def callback(event:Event) :
|
|
93
|
+
|
|
94
|
+
old_points = self.get_points()
|
|
95
|
+
new_points:Points = event.source.data
|
|
96
|
+
features = event.source.features
|
|
97
|
+
|
|
98
|
+
current_point_number = len(old_points)
|
|
99
|
+
next_id = self.get_next_id()
|
|
100
|
+
new_points_number = len(new_points)
|
|
101
|
+
|
|
102
|
+
if new_points_number > current_point_number :
|
|
103
|
+
features.at[new_points_number - 1, "id"] = next_id
|
|
104
|
+
self.next_id += 1
|
|
105
|
+
|
|
106
|
+
#preparing next callback
|
|
107
|
+
self.points = new_points
|
|
108
|
+
self._set_callback()
|
|
109
|
+
self.callback = callback
|
|
110
|
+
|
|
111
|
+
live_points_number = len(points)
|
|
112
|
+
next_point_id = id_list[-1] + 1
|
|
113
|
+
_callback = Points_callback(points=points, next_id=next_point_id)
|
|
114
|
+
Viewer.show(block=False)
|
|
115
|
+
points_callback = Viewer.layers[1].events.data.connect((_callback, 'callback'))
|
|
116
|
+
napari.run()
|
small_fish_gui/start.py
ADDED
small_fish_gui/utils.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
|
|
3
|
+
def check_parameter(**kwargs):
|
|
4
|
+
"""Check dtype of the function's parameters.
|
|
5
|
+
|
|
6
|
+
Parameters
|
|
7
|
+
----------
|
|
8
|
+
kwargs : Type or Tuple[Type]
|
|
9
|
+
Map of each parameter with its expected dtype.
|
|
10
|
+
|
|
11
|
+
Returns
|
|
12
|
+
-------
|
|
13
|
+
_ : bool
|
|
14
|
+
Assert if the array is well formatted.
|
|
15
|
+
|
|
16
|
+
"""
|
|
17
|
+
# get the frame and the parameters of the function
|
|
18
|
+
frame = inspect.currentframe().f_back
|
|
19
|
+
_, _, _, values = inspect.getargvalues(frame)
|
|
20
|
+
|
|
21
|
+
# compare each parameter with its expected dtype
|
|
22
|
+
for arg in kwargs:
|
|
23
|
+
expected_dtype = kwargs[arg]
|
|
24
|
+
parameter = values[arg]
|
|
25
|
+
if not isinstance(parameter, expected_dtype):
|
|
26
|
+
actual = "'{0}'".format(type(parameter).__name__)
|
|
27
|
+
if isinstance(expected_dtype, tuple):
|
|
28
|
+
target = ["'{0}'".format(x.__name__) for x in expected_dtype]
|
|
29
|
+
target = "(" + ", ".join(target) + ")"
|
|
30
|
+
else:
|
|
31
|
+
target = expected_dtype.__name__
|
|
32
|
+
raise TypeError("Parameter {0} should be a {1}. It is a {2} "
|
|
33
|
+
"instead.".format(arg, target, actual))
|
|
34
|
+
|
|
35
|
+
return True
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def compute_anisotropy_coef(voxel_size) :
|
|
39
|
+
"""
|
|
40
|
+
Returns tuple (anisotropy_z, anisotropy_y, 1)
|
|
41
|
+
voxel_size : tuple (z,y,x).
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
if not isinstance(voxel_size, (tuple, list)) : raise TypeError("Expected voxel_size tuple or list")
|
|
45
|
+
if len(voxel_size) == 2 : is_3D = False
|
|
46
|
+
elif len(voxel_size) == 3 : is_3D = True
|
|
47
|
+
else : raise ValueError("Expected 2D or 3D voxel, {0} element(s) found".format(len(voxel_size)))
|
|
48
|
+
|
|
49
|
+
if is_3D :
|
|
50
|
+
z_anisotropy = voxel_size[0] / voxel_size [2]
|
|
51
|
+
xy_anisotropy = voxel_size[1] / voxel_size [2]
|
|
52
|
+
return (z_anisotropy, xy_anisotropy, 1)
|
|
53
|
+
|
|
54
|
+
else :
|
|
55
|
+
return (voxel_size[0] / voxel_size[1], 1)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: small_fish_gui
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Small Fish is a python application for the analysis of smFish images. It provides a ready to use graphical interface to combine famous python packages for cell analysis without any need for coding.
|
|
5
|
+
Project-URL: Homepage, https://github.com/2Echoes/small_fish
|
|
6
|
+
Project-URL: Issues, https://github.com/2Echoes/small_fish/issues
|
|
7
|
+
Author-email: Slimani Floric <floric.slimani@live.com>
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# Small Fish
|
|
16
|
+
**Small Fish** is a python application for the analysis of smFish images. It provides a ready to use graphical interface to combine famous python packages for cell analysis without any need for coding.
|
|
17
|
+
|
|
18
|
+
Cell segmentation is peformed using *cellpose* (published work) : https://github.com/MouseLand/cellpose
|
|
19
|
+
|
|
20
|
+
Spot detection is performed via *big-fish* (published work) : https://github.com/fish-quant/big-fish
|
|
21
|
+
|
|
22
|
+
Time stacks are not yet supported.
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
It is higly recommanded to create a specific [conda](https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html) or [virtual](https://docs.python.org/3.6/library/venv.html) environnement to install small fish.
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
conda create -n small_fish python=3.8
|
|
30
|
+
activate small_fish
|
|
31
|
+
```
|
|
32
|
+
Then download the small_fish package :
|
|
33
|
+
```bash
|
|
34
|
+
pip install small_fish
|
|
35
|
+
```
|
|
36
|
+
<b> (Recommended) </b> Results visualisation is achieved through *Napari* which you can install with :
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install napari[all]
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Run Small fish
|
|
43
|
+
|
|
44
|
+
First activate your python environnement :
|
|
45
|
+
```bash
|
|
46
|
+
activate small_fish
|
|
47
|
+
```
|
|
48
|
+
Then launch Small fish :
|
|
49
|
+
```bash
|
|
50
|
+
python -m small_fish
|
|
51
|
+
```
|
|
52
|
+
## Developpement
|
|
53
|
+
|
|
54
|
+
Optional features to include in future versions :
|
|
55
|
+
- batch processing
|
|
56
|
+
- time stack (which would include cell tracking)
|
|
57
|
+
- 3D segmentation
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
small_fish_gui/__init__.py,sha256=aAh4brM-eTSnFOWpYSFgMIqJzbjJX45RHIWi7ppl_nA,1941
|
|
2
|
+
small_fish_gui/__main__.py,sha256=scEcvIpJ4F5uvHnKycg4vPjXXQv4gjkf4FL5MyLxeUs,460
|
|
3
|
+
small_fish_gui/start.py,sha256=HTbzsVcaMji1BZnWyjfn3bDGIQeXGG4zFzvCBepqFvA,108
|
|
4
|
+
small_fish_gui/utils.py,sha256=tSoMb8N69WdKTtMItPb1DYZiIAz1mjI26BCKJAi6vuc,1798
|
|
5
|
+
small_fish_gui/gui/__init__.py,sha256=178HC3t2z4EnP0iBnMcaP_pyh5xHwOkEE6p3WJwBQeU,911
|
|
6
|
+
small_fish_gui/gui/animation.py,sha256=6_Y15_NzJ_TYBYseu3sSKaVkYRp2UCsVClAWOk3dESY,714
|
|
7
|
+
small_fish_gui/gui/general_help_screenshot.png,sha256=X4E6Td5f04K-pBUPDaBJRAE3D5b8fuEdiAUKhkIDr-0,54210
|
|
8
|
+
small_fish_gui/gui/help_module.py,sha256=pVSpGwhkI3bNr1EgBjnd-SukPTx6wcuFnAyyVNMZmWo,9608
|
|
9
|
+
small_fish_gui/gui/layout.py,sha256=E_Z87S1YUR1gx2447OM0156dKXbzMA_q9joCO4hsZd8,7644
|
|
10
|
+
small_fish_gui/gui/mapping_help_screenshot.png,sha256=HcuRh5TYciUogUasza5vZ_QSshaiHsskQK23mh9vQS8,34735
|
|
11
|
+
small_fish_gui/gui/prompts.py,sha256=mlw9NT5k3I8kTMB7w6W6QETbSIav_A7RhCmMVD7NFV0,12279
|
|
12
|
+
small_fish_gui/gui/segmentation_help_screenshot.png,sha256=rbSgIydT0gZtfMh1qk4mdMbEIyCaakvHmxa2eOrLwO0,118944
|
|
13
|
+
small_fish_gui/gui/test.py,sha256=Pf-GW9AgW-0VL1mFbYtqRvPAaa8DgwCThv2dDUHCcmU,156
|
|
14
|
+
small_fish_gui/interface/__init__.py,sha256=PB86R4Y9kV80aGZ-vP0ZW2KeaCwGbBbCtFCmbN2yl28,275
|
|
15
|
+
small_fish_gui/interface/image.py,sha256=X1L7S5svxUwdoDcI3QM1PbN-c4Nz5w30hixq3IgqSn8,1130
|
|
16
|
+
small_fish_gui/interface/output.py,sha256=wqXJHk-PzqZwYr8NHLg9jcEJlZQXZk8R76aeWcTxsEw,1337
|
|
17
|
+
small_fish_gui/interface/parameters.py,sha256=lUugD-4W2TZyJF3TH1q70TlktEYhhPtcPCrvxm5Dk50,36
|
|
18
|
+
small_fish_gui/interface/testing.py,sha256=MY5-GcPOUHagcrwR8A7QOjAmjZIDVC8Wz3NibLe3KQw,321
|
|
19
|
+
small_fish_gui/pipeline/_colocalisation.py,sha256=-zz_kvghpMSaBhQT0moHvwC6ee92S-oq-j9qZWqgZMY,9677
|
|
20
|
+
small_fish_gui/pipeline/_custom_errors.py,sha256=tQ-AUhgzIFpK30AZiQQrtHCHyGVRDdAoIjzL0Fk-1pA,43
|
|
21
|
+
small_fish_gui/pipeline/_detection_visualisation.py,sha256=CNxCQpiCzC9Uk-2RqSuTp55Glf1URCL_s8zidwljY9Y,5774
|
|
22
|
+
small_fish_gui/pipeline/_preprocess.py,sha256=AlB86KuCA0UKA0XbTgXTJbg92Az34hzba_hsVs-uZl0,10035
|
|
23
|
+
small_fish_gui/pipeline/_segmentation.py,sha256=IaeqsjwIweKH9LEm6qbDqssGW7R0MRZSh7PtF4etIEI,12772
|
|
24
|
+
small_fish_gui/pipeline/_signaltonoise.py,sha256=7A9t7xu7zghI6cr201Ldm-LjJ5NOuP56VSeJ8KIzcUo,8497
|
|
25
|
+
small_fish_gui/pipeline/actions.py,sha256=n6lr_LqV4PT5uw6KfovehUuUK-yTJ66G-IhY2uhHF_k,5472
|
|
26
|
+
small_fish_gui/pipeline/detection.py,sha256=l4G2UHJmrzIg_alAge2GY0h5D34N_oqWzfgjfe2SI04,26032
|
|
27
|
+
small_fish_gui/pipeline/main.py,sha256=4_Qk-NRhjOEsgN7T35NXOHj4OdDmZGS5kVeKde-yCiI,2593
|
|
28
|
+
small_fish_gui/pipeline/test.py,sha256=w4ZMGDmUDXxVgWTlZ2TKw19W8q5gcE9gLMKe0SWnRrw,2827
|
|
29
|
+
small_fish_gui-1.0.0.dist-info/METADATA,sha256=1E3_IdeRT3wf_hl4DRqWssW-DfF1UBwl9TgAilhfOjs,1991
|
|
30
|
+
small_fish_gui-1.0.0.dist-info/WHEEL,sha256=zEMcRr9Kr03x1ozGwg5v9NQBKn3kndp6LSoSlVg-jhU,87
|
|
31
|
+
small_fish_gui-1.0.0.dist-info/licenses/LICENSE,sha256=-iFy8VGBYs5VsHglKpk4D-hxqQ2jMJaqmfq_ulIzDks,1303
|
|
32
|
+
small_fish_gui-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
BSD 2-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023, Floric Slimani
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
16
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
17
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
18
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
19
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
20
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
21
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
22
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
23
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
24
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|