risk-network 0.0.4b1__tar.gz → 0.0.4b2__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.
Files changed (38) hide show
  1. {risk_network-0.0.4b1 → risk_network-0.0.4b2}/MANIFEST.in +4 -0
  2. {risk_network-0.0.4b1 → risk_network-0.0.4b2}/PKG-INFO +1 -1
  3. {risk_network-0.0.4b1 → risk_network-0.0.4b2}/risk/__init__.py +1 -1
  4. {risk_network-0.0.4b1 → risk_network-0.0.4b2}/risk_network.egg-info/PKG-INFO +1 -1
  5. {risk_network-0.0.4b1 → risk_network-0.0.4b2}/risk_network.egg-info/SOURCES.txt +1 -6
  6. risk_network-0.0.4b1/tests/test_load_annotations.py +0 -75
  7. risk_network-0.0.4b1/tests/test_load_graph.py +0 -50
  8. risk_network-0.0.4b1/tests/test_load_neighborhoods.py +0 -61
  9. risk_network-0.0.4b1/tests/test_load_network.py +0 -74
  10. risk_network-0.0.4b1/tests/test_load_plotter.py +0 -317
  11. {risk_network-0.0.4b1 → risk_network-0.0.4b2}/LICENSE +0 -0
  12. {risk_network-0.0.4b1 → risk_network-0.0.4b2}/README.md +0 -0
  13. {risk_network-0.0.4b1 → risk_network-0.0.4b2}/pyproject.toml +0 -0
  14. {risk_network-0.0.4b1 → risk_network-0.0.4b2}/risk/annotations/__init__.py +0 -0
  15. {risk_network-0.0.4b1 → risk_network-0.0.4b2}/risk/annotations/annotations.py +0 -0
  16. {risk_network-0.0.4b1 → risk_network-0.0.4b2}/risk/annotations/io.py +0 -0
  17. {risk_network-0.0.4b1 → risk_network-0.0.4b2}/risk/constants.py +0 -0
  18. {risk_network-0.0.4b1 → risk_network-0.0.4b2}/risk/log/__init__.py +0 -0
  19. {risk_network-0.0.4b1 → risk_network-0.0.4b2}/risk/log/console.py +0 -0
  20. {risk_network-0.0.4b1 → risk_network-0.0.4b2}/risk/log/params.py +0 -0
  21. {risk_network-0.0.4b1 → risk_network-0.0.4b2}/risk/neighborhoods/__init__.py +0 -0
  22. {risk_network-0.0.4b1 → risk_network-0.0.4b2}/risk/neighborhoods/community.py +0 -0
  23. {risk_network-0.0.4b1 → risk_network-0.0.4b2}/risk/neighborhoods/domains.py +0 -0
  24. {risk_network-0.0.4b1 → risk_network-0.0.4b2}/risk/neighborhoods/neighborhoods.py +0 -0
  25. {risk_network-0.0.4b1 → risk_network-0.0.4b2}/risk/network/__init__.py +0 -0
  26. {risk_network-0.0.4b1 → risk_network-0.0.4b2}/risk/network/geometry.py +0 -0
  27. {risk_network-0.0.4b1 → risk_network-0.0.4b2}/risk/network/graph.py +0 -0
  28. {risk_network-0.0.4b1 → risk_network-0.0.4b2}/risk/network/io.py +0 -0
  29. {risk_network-0.0.4b1 → risk_network-0.0.4b2}/risk/network/plot.py +0 -0
  30. {risk_network-0.0.4b1 → risk_network-0.0.4b2}/risk/risk.py +0 -0
  31. {risk_network-0.0.4b1 → risk_network-0.0.4b2}/risk/stats/__init__.py +0 -0
  32. {risk_network-0.0.4b1 → risk_network-0.0.4b2}/risk/stats/permutation.py +0 -0
  33. {risk_network-0.0.4b1 → risk_network-0.0.4b2}/risk/stats/stats.py +0 -0
  34. {risk_network-0.0.4b1 → risk_network-0.0.4b2}/risk_network.egg-info/dependency_links.txt +0 -0
  35. {risk_network-0.0.4b1 → risk_network-0.0.4b2}/risk_network.egg-info/requires.txt +0 -0
  36. {risk_network-0.0.4b1 → risk_network-0.0.4b2}/risk_network.egg-info/top_level.txt +0 -0
  37. {risk_network-0.0.4b1 → risk_network-0.0.4b2}/setup.cfg +0 -0
  38. {risk_network-0.0.4b1 → risk_network-0.0.4b2}/setup.py +0 -0
@@ -5,6 +5,10 @@ recursive-include risk *.py
5
5
  include README.md
6
6
  include LICENSE
7
7
 
8
+ # Exclude the tests directory
9
+ exclude tests/
10
+ exclude tests/*
11
+
8
12
  # Exclude compiled files or any other unwanted files
9
13
  exclude risk/**/*.c
10
14
  exclude risk/**/*.so
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: risk-network
3
- Version: 0.0.4b1
3
+ Version: 0.0.4b2
4
4
  Summary: A Python package for biological network analysis
5
5
  Author: Ira Horecka
6
6
  Author-email: Ira Horecka <ira89@icloud.com>
@@ -10,4 +10,4 @@ RISK: RISK Infers Spatial Kinship
10
10
 
11
11
  from risk.risk import RISK
12
12
 
13
- __version__ = "0.0.4-beta.1"
13
+ __version__ = "0.0.4-beta.2"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: risk-network
3
- Version: 0.0.4b1
3
+ Version: 0.0.4b2
4
4
  Summary: A Python package for biological network analysis
5
5
  Author: Ira Horecka
6
6
  Author-email: Ira Horecka <ira89@icloud.com>
@@ -28,9 +28,4 @@ risk_network.egg-info/PKG-INFO
28
28
  risk_network.egg-info/SOURCES.txt
29
29
  risk_network.egg-info/dependency_links.txt
30
30
  risk_network.egg-info/requires.txt
31
- risk_network.egg-info/top_level.txt
32
- tests/test_load_annotations.py
33
- tests/test_load_graph.py
34
- tests/test_load_neighborhoods.py
35
- tests/test_load_network.py
36
- tests/test_load_plotter.py
31
+ risk_network.egg-info/top_level.txt
@@ -1,75 +0,0 @@
1
- """
2
- tests/test_load_annotations
3
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
- """
5
-
6
- def test_load_csv_annotation(risk_obj, network, data_path):
7
- """Test loading a CSV annotation file and associating it with a network
8
-
9
- Args:
10
- risk_obj: The RISK object instance used for loading annotations
11
- network: The network object to which annotations will be applied
12
- data_path: The base path to the directory containing the annotation files
13
-
14
- Returns:
15
- None
16
- """
17
- annotation_file = data_path / "csv" / "annotations" / "go_biological_process.csv"
18
- annotations = risk_obj.load_csv_annotation(filepath=str(annotation_file), network=network)
19
-
20
- assert annotations is not None
21
- assert len(annotations) > 0 # Check that annotations are loaded
22
-
23
-
24
- def test_load_json_annotation(risk_obj, network, data_path):
25
- """Test loading a JSON annotation file and associating it with a network
26
-
27
- Args:
28
- risk_obj: The RISK object instance used for loading annotations
29
- network: The network object to which annotations will be applied
30
- data_path: The base path to the directory containing the annotation files
31
-
32
- Returns:
33
- None
34
- """
35
- annotation_file = data_path / "json" / "annotations" / "go_biological_process.json"
36
- annotations = risk_obj.load_json_annotation(filepath=str(annotation_file), network=network)
37
-
38
- assert annotations is not None
39
- assert len(annotations) > 0 # Check that annotations are loaded
40
-
41
-
42
- def test_load_tsv_annotation(risk_obj, network, data_path):
43
- """Test loading a TSV annotation file and associating it with a network
44
-
45
- Args:
46
- risk_obj: The RISK object instance used for loading annotations
47
- network: The network object to which annotations will be applied
48
- data_path: The base path to the directory containing the annotation files
49
-
50
- Returns:
51
- None
52
- """
53
- annotation_file = data_path / "tsv" / "annotations" / "go_biological_process.tsv"
54
- annotations = risk_obj.load_tsv_annotation(filepath=str(annotation_file), network=network)
55
-
56
- assert annotations is not None
57
- assert len(annotations) > 0 # Check that annotations are loaded
58
-
59
-
60
- def test_load_excel_annotation(risk_obj, network, data_path):
61
- """Test loading an Excel annotation file and associating it with a network
62
-
63
- Args:
64
- risk_obj: The RISK object instance used for loading annotations
65
- network: The network object to which annotations will be applied
66
- data_path: The base path to the directory containing the annotation files
67
-
68
- Returns:
69
- None
70
- """
71
- annotation_file = data_path / "excel" / "annotations" / "go_biological_process.xlsx"
72
- annotations = risk_obj.load_excel_annotation(filepath=str(annotation_file), network=network)
73
-
74
- assert annotations is not None
75
- assert len(annotations) > 0 # Check that annotations are loaded
@@ -1,50 +0,0 @@
1
- """
2
- tests/test_load_graph
3
- ~~~~~~~~~~~~~~~~~~~~~
4
- """
5
-
6
- def test_load_graph(risk_obj, network, annotations):
7
- """Test loading a graph after generating neighborhoods with specific parameters
8
-
9
- Args:
10
- risk_obj: The RISK object instance used for loading neighborhoods and graphs
11
- network: The network object to be used for neighborhood and graph generation
12
- annotations: The annotations associated with the network
13
-
14
- Returns:
15
- None
16
- """
17
- # Load neighborhoods as a prerequisite
18
- neighborhoods = risk_obj.load_neighborhoods(
19
- network=network,
20
- annotations=annotations,
21
- distance_metric="louvain",
22
- louvain_resolution=8,
23
- edge_length_threshold=0.75,
24
- score_metric="stdev",
25
- null_distribution="network",
26
- num_permutations=100, # Perform 100 permutations
27
- random_seed=887,
28
- max_workers=4, # Use 4 processes
29
- )
30
-
31
- # Load the graph with the specified parameters
32
- graph = risk_obj.load_graph(
33
- network=network,
34
- annotations=annotations,
35
- neighborhoods=neighborhoods,
36
- tail="right", # Right tail for enrichment
37
- pval_cutoff=0.05, # P-value cutoff of 0.05
38
- fdr_cutoff=1.0, # FDR cutoff
39
- impute_depth=1, # Set impute depth to 1
40
- prune_threshold=0.1, # Prune threshold set to 0.1
41
- linkage_criterion="distance", # Clustering based on distance
42
- linkage_method="average", # Set linkage method to average
43
- linkage_metric="yule", # Set linkage metric to yule
44
- min_cluster_size=5, # Minimum cluster size set to 5
45
- max_cluster_size=1000, # Maximum cluster size set to 1000
46
- )
47
-
48
- assert graph is not None
49
- assert len(graph.network.nodes) > 0 # Ensure that the graph has nodes
50
- assert len(graph.network.edges) > 0 # Ensure that the graph has edges
@@ -1,61 +0,0 @@
1
- """
2
- tests/test_load_neighborhoods
3
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
- """
5
-
6
- def test_load_neighborhoods_single_process(risk_obj, network, annotations):
7
- """Test loading neighborhoods using a single process
8
-
9
- Args:
10
- risk_obj: The RISK object instance used for loading neighborhoods
11
- network: The network object to be used for neighborhood generation
12
- annotations: The annotations associated with the network
13
-
14
- Returns:
15
- None
16
- """
17
- # Load neighborhoods with 1 process
18
- neighborhoods = risk_obj.load_neighborhoods(
19
- network=network,
20
- annotations=annotations,
21
- distance_metric="markov_clustering",
22
- louvain_resolution=0.01,
23
- edge_length_threshold=0.25,
24
- score_metric="stdev",
25
- null_distribution="network",
26
- num_permutations=10, # Set to 10 permutations as requested
27
- random_seed=887,
28
- max_workers=1, # Single process
29
- )
30
-
31
- assert neighborhoods is not None
32
- assert len(neighborhoods) > 0 # Ensure neighborhoods are loaded
33
-
34
-
35
- def test_load_neighborhoods_multi_process(risk_obj, network, annotations):
36
- """Test loading neighborhoods using multiple processes
37
-
38
- Args:
39
- risk_obj: The RISK object instance used for loading neighborhoods
40
- network: The network object to be used for neighborhood generation
41
- annotations: The annotations associated with the network
42
-
43
- Returns:
44
- None
45
- """
46
- # Load neighborhoods with 4 processes
47
- neighborhoods = risk_obj.load_neighborhoods(
48
- network=network,
49
- annotations=annotations,
50
- distance_metric="markov_clustering",
51
- louvain_resolution=0.01,
52
- edge_length_threshold=0.25,
53
- score_metric="stdev",
54
- null_distribution="network",
55
- num_permutations=10, # Set to 10 permutations as requested
56
- random_seed=887,
57
- max_workers=4, # Four processes
58
- )
59
-
60
- assert neighborhoods is not None
61
- assert len(neighborhoods) > 0 # Ensure neighborhoods are loaded
@@ -1,74 +0,0 @@
1
- """
2
- tests/test_load_network
3
- ~~~~~~~~~~~~~~~~~~~~~~~
4
- """
5
-
6
- def test_load_cytoscape_network(risk_obj, data_path):
7
- """Test loading a Cytoscape network from a .cys file
8
-
9
- Args:
10
- risk_obj: The RISK object instance used for loading the network
11
- data_path: The base path to the directory containing the Cytoscape file
12
-
13
- Returns:
14
- None
15
- """
16
- cys_file = data_path / "cytoscape" / "michaelis_2023.cys"
17
- network = risk_obj.load_cytoscape_network(
18
- filepath=str(cys_file), source_label="source", target_label="target", view_name=""
19
- )
20
-
21
- assert network is not None
22
- assert len(network.nodes) > 0 # Check that the network has nodes
23
- assert len(network.edges) > 0 # Check that the network has edges
24
-
25
-
26
- def test_load_cytoscape_json_network(risk_obj, data_path):
27
- """Test loading a Cytoscape JSON network from a .cyjs file
28
-
29
- Args:
30
- risk_obj: The RISK object instance used for loading the network
31
- data_path: The base path to the directory containing the Cytoscape JSON file
32
-
33
- Returns:
34
- None
35
- """
36
- cyjs_file = data_path / "cyjs" / "michaelis_2023.cyjs"
37
- network = risk_obj.load_cytoscape_json_network(
38
- filepath=str(cyjs_file), source_label="source", target_label="target"
39
- )
40
-
41
- assert network is not None
42
- assert len(network.nodes) > 0 # Check that the network has nodes
43
- assert len(network.edges) > 0 # Check that the network has edges
44
-
45
-
46
- def test_load_gpickle_network(risk_obj, data_path):
47
- """Test loading a network from a .gpickle file
48
-
49
- Args:
50
- risk_obj: The RISK object instance used for loading the network
51
- data_path: The base path to the directory containing the gpickle file
52
-
53
- Returns:
54
- None
55
- """
56
- gpickle_file = data_path / "gpickle" / "michaelis_2023.gpickle"
57
- network = risk_obj.load_gpickle_network(filepath=str(gpickle_file))
58
-
59
- assert network is not None
60
- assert len(network.nodes) > 0 # Check that the network has nodes
61
- assert len(network.edges) > 0 # Check that the network has edges
62
-
63
-
64
- def test_load_networkx_network(risk_obj, network):
65
- """Test loading a network from a NetworkX graph object
66
-
67
- Args:
68
- risk_obj: The RISK object instance used for loading the network
69
- network: The NetworkX graph object to be loaded into the RISK network
70
-
71
- Returns:
72
- None
73
- """
74
- network = risk_obj.load_networkx
@@ -1,317 +0,0 @@
1
- """
2
- tests/test_load_plotter
3
- ~~~~~~~~~~~~~~~~~~~~~~~
4
- """
5
-
6
- def test_initialize_plotter(risk_obj, graph):
7
- """Test initializing the plotter object with a graph
8
-
9
- Args:
10
- risk_obj: The RISK object instance used for initializing the plotter
11
- graph: The graph object to be plotted
12
-
13
- Returns:
14
- None
15
- """
16
- plotter = initialize_plotter(risk_obj, graph)
17
-
18
- assert plotter is not None # Ensure the plotter is initialized
19
- assert hasattr(plotter, "graph") # Check that the plotter has a graph attribute
20
-
21
-
22
- def test_plot_network(risk_obj, graph):
23
- """Test plotting the full network using the plotter
24
-
25
- Args:
26
- risk_obj: The RISK object instance used for plotting
27
- graph: The graph object to be plotted
28
-
29
- Returns:
30
- None
31
- """
32
- plotter = initialize_plotter(risk_obj, graph)
33
- plot_network(plotter)
34
-
35
- assert plotter is not None # Ensure the plotter is initialized
36
-
37
-
38
- def test_plot_subnetwork(risk_obj, graph):
39
- """Test plotting a subnetwork using the plotter
40
-
41
- Args:
42
- risk_obj: The RISK object instance used for plotting
43
- graph: The graph object containing the subnetwork to be plotted
44
-
45
- Returns:
46
- None
47
- """
48
- plotter = initialize_plotter(risk_obj, graph)
49
- plot_subnetwork(plotter)
50
-
51
- assert plotter is not None # Ensure the plotter is initialized
52
-
53
-
54
- def test_plot_contours(risk_obj, graph):
55
- """Test plotting contours on the network using the plotter
56
-
57
- Args:
58
- risk_obj: The RISK object instance used for plotting
59
- graph: The graph object on which contours will be plotted
60
-
61
- Returns:
62
- None
63
- """
64
- plotter = initialize_plotter(risk_obj, graph)
65
- plot_contours(plotter)
66
-
67
- assert plotter is not None # Ensure the plotter is initialized
68
-
69
-
70
- def test_plot_subcontour(risk_obj, graph):
71
- """Test plotting subcontours on the network using the plotter
72
-
73
- Args:
74
- risk_obj: The RISK object instance used for plotting
75
- graph: The graph object on which subcontours will be plotted
76
-
77
- Returns:
78
- None
79
- """
80
- plotter = initialize_plotter(risk_obj, graph)
81
- plot_subcontour(plotter)
82
-
83
- assert plotter is not None # Ensure the plotter is initialized
84
-
85
-
86
- def test_plot_labels(risk_obj, graph):
87
- """Test plotting labels on the network using the plotter
88
-
89
- Args:
90
- risk_obj: The RISK object instance used for plotting
91
- graph: The graph object on which labels will be plotted
92
-
93
- Returns:
94
- None
95
- """
96
- plotter = initialize_plotter(risk_obj, graph)
97
- plot_labels(plotter)
98
-
99
- assert plotter is not None # Ensure the plotter is initialized
100
-
101
-
102
- def test_plot_sublabel(risk_obj, graph):
103
- """Test plotting a sublabel on the network using the plotter
104
-
105
- Args:
106
- risk_obj: The RISK object instance used for plotting
107
- graph: The graph object on which the sublabel will be plotted
108
-
109
- Returns:
110
- None
111
- """
112
- plotter = initialize_plotter(risk_obj, graph)
113
- plot_sublabel(plotter)
114
-
115
- assert plotter is not None # Ensure the plotter is initialized
116
-
117
-
118
- def test_display_plot(risk_obj, graph):
119
- """Test displaying the plot using the plotter
120
-
121
- Args:
122
- risk_obj: The RISK object instance used for plotting
123
- graph: The graph object to be displayed
124
-
125
- Returns:
126
- None
127
- """
128
- plotter = initialize_plotter(risk_obj, graph)
129
- display_plot(plotter)
130
-
131
- assert plotter is not None # Ensure the plotter is initialized
132
-
133
-
134
- def initialize_plotter(risk, graph):
135
- """Initialize the plotter with specified settings
136
-
137
- Args:
138
- risk: The RISK object instance used for plotting
139
- graph: The graph object to be plotted
140
-
141
- Returns:
142
- Plotter: The initialized plotter object
143
- """
144
- return risk.load_plotter(
145
- graph=graph,
146
- figsize=(15, 15),
147
- background_color="black",
148
- plot_outline=True,
149
- outline_color="white",
150
- outline_scale=1.05,
151
- )
152
-
153
-
154
- def plot_network(plotter):
155
- """Plot the full network using the plotter
156
-
157
- Args:
158
- plotter: The initialized plotter object
159
-
160
- Returns:
161
- None
162
- """
163
- plotter.plot_network(
164
- node_size=plotter.get_annotated_node_sizes(enriched_nodesize=100, nonenriched_nodesize=10),
165
- edge_width=0.0,
166
- node_color=plotter.get_annotated_node_colors(
167
- cmap="gist_rainbow",
168
- min_scale=0.5,
169
- max_scale=1.0,
170
- nonenriched_color="white",
171
- random_seed=887,
172
- ),
173
- node_edgecolor="black",
174
- edge_color="white",
175
- node_shape="o",
176
- )
177
-
178
-
179
- def plot_subnetwork(plotter):
180
- """Plot a specific subnetwork using the plotter
181
-
182
- Args:
183
- plotter: The initialized plotter object
184
-
185
- Returns:
186
- None
187
- """
188
- plotter.plot_subnetwork(
189
- nodes=[
190
- "LSM1",
191
- "LSM2",
192
- "LSM3",
193
- "LSM4",
194
- "LSM5",
195
- "LSM6",
196
- "LSM7",
197
- "PAT1",
198
- ],
199
- node_size=250,
200
- edge_width=0.0,
201
- node_color="white",
202
- node_edgecolor="black",
203
- edge_color="white",
204
- node_shape="^",
205
- )
206
-
207
-
208
- def plot_contours(plotter):
209
- """Plot contours on the network using the plotter
210
-
211
- Args:
212
- plotter: The initialized plotter object
213
-
214
- Returns:
215
- None
216
- """
217
- plotter.plot_contours(
218
- levels=5,
219
- bandwidth=0.8,
220
- grid_size=250,
221
- alpha=0.2,
222
- color=plotter.get_annotated_contour_colors(cmap="gist_rainbow", random_seed=887),
223
- )
224
-
225
-
226
- def plot_subcontour(plotter):
227
- """Plot subcontours on a specific subnetwork using the plotter
228
-
229
- Args:
230
- plotter: The initialized plotter object
231
-
232
- Returns:
233
- None
234
- """
235
- plotter.plot_subcontour(
236
- nodes=[
237
- "LSM1",
238
- "LSM2",
239
- "LSM3",
240
- "LSM4",
241
- "LSM5",
242
- "LSM6",
243
- "LSM7",
244
- "PAT1",
245
- ],
246
- levels=5,
247
- bandwidth=0.8,
248
- grid_size=250,
249
- alpha=0.2,
250
- color="white",
251
- )
252
-
253
-
254
- def plot_labels(plotter):
255
- """Plot labels on the network using the plotter
256
-
257
- Args:
258
- plotter: The initialized plotter object
259
-
260
- Returns:
261
- None
262
- """
263
- plotter.plot_labels(
264
- perimeter_scale=1.25,
265
- offset=0.10,
266
- font="Arial",
267
- fontsize=10,
268
- fontcolor=plotter.get_annotated_label_colors(cmap="gist_rainbow", random_seed=887),
269
- arrow_linewidth=1,
270
- arrow_color=plotter.get_annotated_label_colors(cmap="gist_rainbow", random_seed=887),
271
- max_words=4,
272
- min_words=2,
273
- )
274
-
275
-
276
- def plot_sublabel(plotter):
277
- """Plot a specific sublabel on the network using the plotter
278
-
279
- Args:
280
- plotter: The initialized plotter object
281
-
282
- Returns:
283
- None
284
- """
285
- plotter.plot_sublabel(
286
- nodes=[
287
- "LSM1",
288
- "LSM2",
289
- "LSM3",
290
- "LSM4",
291
- "LSM5",
292
- "LSM6",
293
- "LSM7",
294
- "PAT1",
295
- ],
296
- label="LSM1-7-PAT1 Complex",
297
- radial_position=73,
298
- perimeter_scale=1.6,
299
- offset=0.10,
300
- font="Arial",
301
- fontsize=14,
302
- fontcolor="white",
303
- arrow_linewidth=1.5,
304
- arrow_color="white",
305
- )
306
-
307
-
308
- def display_plot(plotter):
309
- """Display the plot using the plotter
310
-
311
- Args:
312
- plotter: The initialized plotter object
313
-
314
- Returns:
315
- None
316
- """
317
- plotter.show()
File without changes
File without changes
File without changes
File without changes