prpy 0.2.7__tar.gz → 0.2.8__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 (51) hide show
  1. {prpy-0.2.7 → prpy-0.2.8}/MANIFEST.in +2 -0
  2. {prpy-0.2.7 → prpy-0.2.8}/PKG-INFO +2 -1
  3. {prpy-0.2.7 → prpy-0.2.8}/prpy/numpy/image_ops.c +79 -31
  4. {prpy-0.2.7 → prpy-0.2.8}/prpy.egg-info/PKG-INFO +2 -1
  5. {prpy-0.2.7 → prpy-0.2.8}/prpy.egg-info/SOURCES.txt +0 -5
  6. {prpy-0.2.7 → prpy-0.2.8}/prpy.egg-info/requires.txt +1 -0
  7. {prpy-0.2.7 → prpy-0.2.8}/pyproject.toml +1 -1
  8. {prpy-0.2.7 → prpy-0.2.8}/tests/test_numpy_image.py +1 -1
  9. {prpy-0.2.7 → prpy-0.2.8}/tests/test_tensorflow.py +1 -1
  10. {prpy-0.2.7 → prpy-0.2.8}/tests/test_torch.py +1 -1
  11. prpy-0.2.7/.github/workflows/main.yml +0 -40
  12. prpy-0.2.7/prpy/numpy/.DS_Store +0 -0
  13. prpy-0.2.7/prpy/numpy/image_ops.cpython-310-darwin.so +0 -0
  14. prpy-0.2.7/prpy/numpy/spam.cpython-310-darwin.so +0 -0
  15. prpy-0.2.7/tests/.DS_Store +0 -0
  16. {prpy-0.2.7 → prpy-0.2.8}/.gitignore +0 -0
  17. {prpy-0.2.7 → prpy-0.2.8}/LICENSE +0 -0
  18. {prpy-0.2.7 → prpy-0.2.8}/README.md +0 -0
  19. {prpy-0.2.7 → prpy-0.2.8}/prpy/__init__.py +0 -0
  20. {prpy-0.2.7 → prpy-0.2.8}/prpy/constants.py +0 -0
  21. {prpy-0.2.7 → prpy-0.2.8}/prpy/ffmpeg/__init__.py +0 -0
  22. {prpy-0.2.7 → prpy-0.2.8}/prpy/ffmpeg/probe.py +0 -0
  23. {prpy-0.2.7 → prpy-0.2.8}/prpy/ffmpeg/readwrite.py +0 -0
  24. {prpy-0.2.7 → prpy-0.2.8}/prpy/ffmpeg/utils.py +0 -0
  25. {prpy-0.2.7 → prpy-0.2.8}/prpy/helpers.py +0 -0
  26. {prpy-0.2.7 → prpy-0.2.8}/prpy/numpy/__init__.py +0 -0
  27. {prpy-0.2.7 → prpy-0.2.8}/prpy/numpy/face.py +0 -0
  28. {prpy-0.2.7 → prpy-0.2.8}/prpy/numpy/image.py +0 -0
  29. {prpy-0.2.7 → prpy-0.2.8}/prpy/numpy/metric.py +0 -0
  30. {prpy-0.2.7 → prpy-0.2.8}/prpy/numpy/signal.py +0 -0
  31. {prpy-0.2.7 → prpy-0.2.8}/prpy/numpy/stride_tricks.py +0 -0
  32. {prpy-0.2.7 → prpy-0.2.8}/prpy/tensorflow/__init__.py +0 -0
  33. {prpy-0.2.7 → prpy-0.2.8}/prpy/tensorflow/image.py +0 -0
  34. {prpy-0.2.7 → prpy-0.2.8}/prpy/tensorflow/loss.py +0 -0
  35. {prpy-0.2.7 → prpy-0.2.8}/prpy/tensorflow/lr_schedule.py +0 -0
  36. {prpy-0.2.7 → prpy-0.2.8}/prpy/tensorflow/model_saver.py +0 -0
  37. {prpy-0.2.7 → prpy-0.2.8}/prpy/tensorflow/nan.py +0 -0
  38. {prpy-0.2.7 → prpy-0.2.8}/prpy/tensorflow/optimizer.py +0 -0
  39. {prpy-0.2.7 → prpy-0.2.8}/prpy/tensorflow/signal.py +0 -0
  40. {prpy-0.2.7 → prpy-0.2.8}/prpy/torch/__init__.py +0 -0
  41. {prpy-0.2.7 → prpy-0.2.8}/prpy/torch/model_saver.py +0 -0
  42. {prpy-0.2.7 → prpy-0.2.8}/prpy.egg-info/dependency_links.txt +0 -0
  43. {prpy-0.2.7 → prpy-0.2.8}/prpy.egg-info/top_level.txt +0 -0
  44. {prpy-0.2.7 → prpy-0.2.8}/setup.cfg +0 -0
  45. {prpy-0.2.7 → prpy-0.2.8}/setup.py +0 -0
  46. {prpy-0.2.7 → prpy-0.2.8}/tests/conftest.py +0 -0
  47. {prpy-0.2.7 → prpy-0.2.8}/tests/test_ffmpeg.py +0 -0
  48. {prpy-0.2.7 → prpy-0.2.8}/tests/test_numpy_face.py +0 -0
  49. {prpy-0.2.7 → prpy-0.2.8}/tests/test_numpy_metric.py +0 -0
  50. {prpy-0.2.7 → prpy-0.2.8}/tests/test_numpy_signal.py +0 -0
  51. {prpy-0.2.7 → prpy-0.2.8}/tests/test_numpy_stride_tricks.py +0 -0
@@ -4,4 +4,6 @@ recursive-include prpy/tensorflow *
4
4
  recursive-include prpy/torch *
5
5
  recursive-include prpy/ffmpeg *
6
6
  recursive-include tests *
7
+ global-exclude .DS_Store
7
8
  prune **/__pycache__
9
+ prune .github/workflows
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: prpy
3
- Version: 0.2.7
3
+ Version: 0.2.8
4
4
  Summary: Collection of Python utils for signal, image, and video processing
5
5
  Author-email: Philipp Rouast <philipp@rouast.com>
6
6
  License: MIT License
@@ -35,6 +35,7 @@ Requires-Dist: torch==2.1.0; extra == "torch"
35
35
  Provides-Extra: test
36
36
  Requires-Dist: pytest; extra == "test"
37
37
  Requires-Dist: flake8; extra == "test"
38
+ Requires-Dist: psutil; extra == "test"
38
39
 
39
40
 
40
41
  # prpy
@@ -18,8 +18,11 @@
18
18
  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
19
  // SOFTWARE.
20
20
 
21
+ #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
22
+
21
23
  #include <Python.h>
22
24
  #include <numpy/arrayobject.h>
25
+ #include <stdlib.h>
23
26
 
24
27
  #define DIV_ROUND_CLOSEST(n, d) ((((n) < 0) == ((d) < 0)) ? (((n) + (d)/2)/(d)) : (((n) - (d)/2)/(d)))
25
28
 
@@ -50,16 +53,24 @@ resample_bilinear_op(PyObject* self, PyObject* args) {
50
53
 
51
54
  // Get the dimensions of the input array
52
55
  npy_intp* dims = PyArray_DIMS(input_array);
53
- int n_frames = dims[0];
54
- int height = dims[1];
55
- int width = dims[2];
56
+ int n_frames = (int)dims[0];
57
+ int height = (int)dims[1];
58
+ int width = (int)dims[2];
56
59
 
57
60
  // Get the strides
58
61
  npy_intp* input_strides = PyArray_STRIDES(input_array);
59
62
 
63
+ // Allocate memory for the positions and weights
64
+ int* pos_flat = malloc(new_width * new_height * 2 * sizeof(int));
65
+ float* weights_flat = malloc(new_width * new_height * 2 * 2 * sizeof(float));
66
+ if (!pos_flat || !weights_flat) {
67
+ PyErr_SetString(PyExc_MemoryError, "Unable to allocate memory for position and weights arrays");
68
+ free(pos_flat);
69
+ free(weights_flat);
70
+ return NULL;
71
+ }
72
+
60
73
  // Precompute the positions and weights
61
- int pos[new_width][new_height][2];
62
- float weights[new_width][new_height][2][2];
63
74
  for (int y = 0; y < new_height; ++y) {
64
75
  for (int x = 0; x < new_width; ++x) {
65
76
  // Determine the position of the current pixel in the input image
@@ -74,13 +85,14 @@ resample_bilinear_op(PyObject* self, PyObject* args) {
74
85
  float dx1 = 1.0f - dx;
75
86
  float dy1 = 1.0f - dy;
76
87
  // Store the positions
77
- pos[x][y][0] = x0;
78
- pos[x][y][1] = y0;
88
+ int flat_idx = (x * new_height + y) * 2;
89
+ pos_flat[flat_idx + 0] = x0;
90
+ pos_flat[flat_idx + 1] = y0;
79
91
  // Store the weights
80
- weights[x][y][0][0] = (int)(dx1 * dy1 * 256.0f);
81
- weights[x][y][0][1] = (int)(dx * dy1 * 256.0f);
82
- weights[x][y][1][0] = (int)(dx1 * dy * 256.0f);
83
- weights[x][y][1][1] = (int)(dx * dy * 256.0f);
92
+ weights_flat[(flat_idx + 0) * 2 + 0] = dx1 * dy1 * 256.0f;
93
+ weights_flat[(flat_idx + 0) * 2 + 1] = dx * dy1 * 256.0f;
94
+ weights_flat[(flat_idx + 1) * 2 + 0] = dx1 * dy * 256.0f;
95
+ weights_flat[(flat_idx + 1) * 2 + 1] = dx * dy * 256.0f;
84
96
  }
85
97
  }
86
98
 
@@ -103,29 +115,40 @@ resample_bilinear_op(PyObject* self, PyObject* args) {
103
115
  // Resize the frame using bilinear resampling
104
116
  for (int y = 0; y < new_height; ++y) {
105
117
  for (int x = 0; x < new_width; ++x) {
106
- unsigned char* p1 = current_frame + pos[x][y][1] * input_strides[1] + pos[x][y][0] * input_strides[2];
118
+ // Get position coordinates
119
+ int pos_idx = (x * new_height + y) * 2;
120
+ int x0 = pos_flat[pos_idx + 0];
121
+ int y0 = pos_flat[pos_idx + 1];
122
+
123
+ // Get weights
124
+ int weights_idx = ((x * new_height + y) * 2) * 2;
125
+ float w00 = weights_flat[weights_idx + 0];
126
+ float w01 = weights_flat[weights_idx + 1];
127
+ float w10 = weights_flat[weights_idx + 2];
128
+ float w11 = weights_flat[weights_idx + 3];
129
+
130
+ // Access pixels
131
+ unsigned char* p1 = current_frame + y0 * input_strides[1] + x0 * input_strides[2];
107
132
  unsigned char* p2 = p1 + input_strides[2];
108
133
  unsigned char* p3 = p1 + input_strides[1];
109
134
  unsigned char* p4 = p3 + input_strides[2];
110
135
 
111
136
  // Make sure that p2 and p4 are within bounds
112
- if (pos[x][y][0] + 1 >= width) {
137
+ if (x0 + 1 >= width) {
113
138
  p2 = p1;
114
139
  p4 = p3;
115
140
  }
116
141
  // Make sure that p3 and p4 are within bounds
117
- if (pos[x][y][1] + 1 >= height) {
142
+ if (y0 + 1 >= height) {
118
143
  p3 = p1;
119
144
  p4 = p2;
120
145
  }
121
146
 
122
147
  // Calculate the weighted sum of pixels (for each color channel)
123
- int outr = p1[0] * weights[x][y][0][0] + p2[0] * weights[x][y][0][1] +
124
- p3[0] * weights[x][y][1][0] + p4[0] * weights[x][y][1][1];
125
- int outg = p1[1] * weights[x][y][0][0] + p2[1] * weights[x][y][0][1] +
126
- p3[1] * weights[x][y][1][0] + p4[1] * weights[x][y][1][1];
127
- int outb = p1[2] * weights[x][y][0][0] + p2[2] * weights[x][y][0][1] +
128
- p3[2] * weights[x][y][1][0] + p4[2] * weights[x][y][1][1];
148
+ int outr = p1[0] * w00 + p2[0] * w01 + p3[0] * w10 + p4[0] * w11;
149
+ int outg = p1[1] * w00 + p2[1] * w01 + p3[1] * w10 + p4[1] * w11;
150
+ int outb = p1[2] * w00 + p2[2] * w01 + p3[2] * w10 + p4[2] * w11;
151
+
129
152
  // Save
130
153
  int idx = i * new_height * new_width * 3 + y * new_width * 3 + x * 3;
131
154
  output_data[idx + 0] = (unsigned char)(outr >> 8);
@@ -135,6 +158,10 @@ resample_bilinear_op(PyObject* self, PyObject* args) {
135
158
  }
136
159
  }
137
160
 
161
+ // Free allocated memory
162
+ free(pos_flat);
163
+ free(weights_flat);
164
+
138
165
  return PyArray_Return(output_array_np);
139
166
  }
140
167
 
@@ -165,9 +192,9 @@ resample_box_op(PyObject* self, PyObject* args) {
165
192
 
166
193
  // Get the dimensions of the input array
167
194
  npy_intp* dims = PyArray_DIMS(input_array);
168
- int n_frames = dims[0];
169
- int height = dims[1];
170
- int width = dims[2];
195
+ int n_frames = (int)dims[0];
196
+ int height = (int)dims[1];
197
+ int width = (int)dims[2];
171
198
 
172
199
  // Make sure that we are downsampling
173
200
  if (height / new_height < 2 || width / new_width < 2) {
@@ -183,18 +210,32 @@ resample_box_op(PyObject* self, PyObject* args) {
183
210
  PyArrayObject* output_array_np = (PyArrayObject*)PyArray_EMPTY(4, output_dims, NPY_UINT8, 0);
184
211
  if (output_array_np == NULL) {
185
212
  Py_DECREF(output_array_np);
213
+ PyErr_SetString(PyExc_MemoryError, "Unable to allocate output array");
186
214
  return NULL;
187
215
  }
188
216
 
189
217
  // Get a pointer to the output data
190
218
  unsigned char* output_data = (unsigned char*)PyArray_DATA(output_array_np);
191
219
 
220
+ // Allocate memory for start and end indices for rows and columns and num_pixels
221
+ int* start_x = (int*)malloc(new_width * sizeof(int));
222
+ int* end_x = (int*)malloc(new_width * sizeof(int));
223
+ int* start_y = (int*)malloc(new_height * sizeof(int));
224
+ int* end_y = (int*)malloc(new_height * sizeof(int));
225
+ int* num_pixels_flat = malloc(new_width * new_height * sizeof(int));
226
+ if (!start_x || !end_x || !start_y || !end_y || !num_pixels_flat) {
227
+ // Clean up allocated memory before returning error
228
+ free(start_x);
229
+ free(end_x);
230
+ free(start_y);
231
+ free(end_y);
232
+ free(num_pixels_flat);
233
+ PyErr_SetString(PyExc_MemoryError, "Unable to allocate memory for indices");
234
+ Py_DECREF(output_array_np);
235
+ return NULL;
236
+ }
237
+
192
238
  // Precompute start and end indices for rows and columns and num_pixels
193
- int start_x[new_width];
194
- int end_x[new_width];
195
- int start_y[new_height];
196
- int end_y[new_height];
197
- int num_pixels_[new_width][new_height];
198
239
  for (int x = 0; x < new_width; ++x) {
199
240
  start_x[x] = DIV_ROUND_CLOSEST(x * width, new_width);
200
241
  end_x[x] = DIV_ROUND_CLOSEST((x + 1) * width, new_width);
@@ -203,7 +244,7 @@ resample_box_op(PyObject* self, PyObject* args) {
203
244
  start_y[y] = DIV_ROUND_CLOSEST(y * height, new_height);
204
245
  end_y[y] = DIV_ROUND_CLOSEST((y + 1) * height, new_height);
205
246
  for (int x = 0; x < new_width; ++x) {
206
- num_pixels_[x][y] = (end_y[y] - start_y[y]) * (end_x[x] - start_x[x]);
247
+ num_pixels_flat[y * new_width + x] = (end_y[y] - start_y[y]) * (end_x[x] - start_x[x]);
207
248
  }
208
249
  }
209
250
 
@@ -216,6 +257,7 @@ resample_box_op(PyObject* self, PyObject* args) {
216
257
  for (int x = 0; x < new_width; ++x) {
217
258
  // Compute the mean of pixels in the input image block
218
259
  int sum_r = 0, sum_g = 0, sum_b = 0;
260
+ int num_pixels = num_pixels_flat[y * new_width + x];
219
261
  for (int src_y = start_y[y]; src_y < end_y[y]; ++src_y) {
220
262
  for (int src_x = start_x[x]; src_x < end_x[x]; ++src_x) {
221
263
  unsigned char* pixel = current_frame + src_y * input_strides[1] + src_x * input_strides[2];
@@ -225,7 +267,6 @@ resample_box_op(PyObject* self, PyObject* args) {
225
267
  }
226
268
  }
227
269
  // Calculate the average value of the pixels
228
- int num_pixels = num_pixels_[x][y];
229
270
  unsigned char avg_r = (unsigned char)(sum_r / num_pixels);
230
271
  unsigned char avg_g = (unsigned char)(sum_g / num_pixels);
231
272
  unsigned char avg_b = (unsigned char)(sum_b / num_pixels);
@@ -238,6 +279,13 @@ resample_box_op(PyObject* self, PyObject* args) {
238
279
  }
239
280
  }
240
281
 
282
+ // Free allocated memory
283
+ free(start_x);
284
+ free(end_x);
285
+ free(start_y);
286
+ free(end_y);
287
+ free(num_pixels_flat);
288
+
241
289
  return PyArray_Return(output_array_np);
242
290
  }
243
291
 
@@ -273,7 +321,7 @@ reduce_roi_op(PyObject* self, PyObject* args) {
273
321
 
274
322
  // Get the dimensions of the input video array
275
323
  npy_intp* video_dims = PyArray_DIMS(input_array_video);
276
- int n_frames = video_dims[0];
324
+ int n_frames = (int)video_dims[0];
277
325
 
278
326
  // Create an output array
279
327
  npy_intp output_dims[2] = {n_frames, 3};
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: prpy
3
- Version: 0.2.7
3
+ Version: 0.2.8
4
4
  Summary: Collection of Python utils for signal, image, and video processing
5
5
  Author-email: Philipp Rouast <philipp@rouast.com>
6
6
  License: MIT License
@@ -35,6 +35,7 @@ Requires-Dist: torch==2.1.0; extra == "torch"
35
35
  Provides-Extra: test
36
36
  Requires-Dist: pytest; extra == "test"
37
37
  Requires-Dist: flake8; extra == "test"
38
+ Requires-Dist: psutil; extra == "test"
38
39
 
39
40
 
40
41
  # prpy
@@ -4,7 +4,6 @@ MANIFEST.in
4
4
  README.md
5
5
  pyproject.toml
6
6
  setup.py
7
- .github/workflows/main.yml
8
7
  prpy/__init__.py
9
8
  prpy/constants.py
10
9
  prpy/helpers.py
@@ -17,15 +16,12 @@ prpy/ffmpeg/__init__.py
17
16
  prpy/ffmpeg/probe.py
18
17
  prpy/ffmpeg/readwrite.py
19
18
  prpy/ffmpeg/utils.py
20
- prpy/numpy/.DS_Store
21
19
  prpy/numpy/__init__.py
22
20
  prpy/numpy/face.py
23
21
  prpy/numpy/image.py
24
22
  prpy/numpy/image_ops.c
25
- prpy/numpy/image_ops.cpython-310-darwin.so
26
23
  prpy/numpy/metric.py
27
24
  prpy/numpy/signal.py
28
- prpy/numpy/spam.cpython-310-darwin.so
29
25
  prpy/numpy/stride_tricks.py
30
26
  prpy/tensorflow/__init__.py
31
27
  prpy/tensorflow/image.py
@@ -37,7 +33,6 @@ prpy/tensorflow/optimizer.py
37
33
  prpy/tensorflow/signal.py
38
34
  prpy/torch/__init__.py
39
35
  prpy/torch/model_saver.py
40
- tests/.DS_Store
41
36
  tests/conftest.py
42
37
  tests/test_ffmpeg.py
43
38
  tests/test_numpy_face.py
@@ -22,6 +22,7 @@ numpy
22
22
  [test]
23
23
  pytest
24
24
  flake8
25
+ psutil
25
26
 
26
27
  [torch]
27
28
  torch==2.1.0
@@ -28,7 +28,7 @@ numpy = ["numpy", "scipy", "Pillow", "opencv-python", "scikit-learn"]
28
28
  numpy_min = ["numpy", "scipy", "Pillow"]
29
29
  tensorflow = ["tensorflow==2.12.1", "numpy"]
30
30
  torch = ["torch==2.1.0"]
31
- test = ["pytest", "flake8"]
31
+ test = ["pytest", "flake8", "psutil"]
32
32
 
33
33
  [project.urls]
34
34
  Repository = "https://github.com/prouast/prpy.git"
@@ -128,7 +128,7 @@ def test_resample_box_only_downsampling():
128
128
  (3, [[3, 4, 7, 12], [3, 5, 7, 12], [3, 5, 6, 10]])])
129
129
  def test_reduce_roi(scenario):
130
130
  n_frames = scenario[0]
131
- roi = np.asarray(scenario[1])
131
+ roi = np.asarray(scenario[1]).astype(np.int64)
132
132
  video = np.random.uniform(size=(n_frames, 12, 8, 3), low=0, high=255).astype(np.uint8)
133
133
  out = reduce_roi(video=video, roi=roi)
134
134
  exp = np.asarray([np.mean(video[i, roi[i,1]:roi[i,3], roi[i,0]:roi[i,2]], axis=(0,1)) for i in range(n_frames)])
@@ -203,7 +203,7 @@ from prpy.tensorflow.model_saver import Candidate, ModelSaver
203
203
 
204
204
  def test_candidate():
205
205
  cand = Candidate(score=0.5, dir='testdir', filename='test')
206
- assert cand.filepath == 'testdir/test'
206
+ assert cand.filepath == os.path.join('testdir', 'test')
207
207
  assert cand.score == 0.5
208
208
 
209
209
  @pytest.mark.parametrize("save_format", ["tf", "h5"])
@@ -33,7 +33,7 @@ import torch
33
33
 
34
34
  def test_candidate():
35
35
  cand = Candidate(score=0.5, dir='testdir', filename='test')
36
- assert cand.filepath == 'testdir/test'
36
+ assert cand.filepath == os.path.join('testdir', 'test')
37
37
  assert cand.score == 0.5
38
38
 
39
39
  @pytest.mark.parametrize("save_optimizer", [False, True])
@@ -1,40 +0,0 @@
1
- name: Tests
2
-
3
- on:
4
- push:
5
- branches: ["main"]
6
- pull_request:
7
- branches: ["main"]
8
-
9
- jobs:
10
- build:
11
-
12
- runs-on: [ubuntu-latest]
13
- strategy:
14
- matrix:
15
- python-version: ["3.8", "3.9", "3.10"]
16
-
17
- steps:
18
- - name: Set up Python ${{ matrix.python-version }}
19
- uses: actions/setup-python@v4
20
- with:
21
- python-version: ${{ matrix.python-version }}
22
- - name: Install ffmpeg
23
- uses: Iamshankhadeep/setup-ffmpeg@v1.2
24
- with:
25
- version: "4.4"
26
- token: ${{ secrets.GITHUB_TOKEN }}
27
- - name: Checkout prpy
28
- uses: actions/checkout@v3
29
- with:
30
- token: ${{ secrets.GITHUB_TOKEN }}
31
- - name: Install prpy and dependencies
32
- run: |
33
- python -m pip install --upgrade pip
34
- python -m pip install ".[ffmpeg,numpy,tensorflow,torch,test]"
35
- - name: Lint with flake8
36
- run: |
37
- flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
38
- - name: Test with pytest
39
- run: |
40
- pytest
Binary file
Binary file
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes