tomwer 1.4.4__py3-none-any.whl → 1.4.5__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.
- tomwer/core/process/reconstruction/nabu/nabucommon.py +43 -21
- tomwer/core/process/reconstruction/nabu/utils.py +2 -14
- tomwer/core/resourcemanager.py +33 -0
- tomwer/gui/reconstruction/axis/ControlWidget.py +1 -1
- tomwer/gui/visualization/volumeviewer.py +19 -3
- tomwer/version.py +1 -1
- {tomwer-1.4.4.dist-info → tomwer-1.4.5.dist-info}/METADATA +1 -1
- {tomwer-1.4.4.dist-info → tomwer-1.4.5.dist-info}/RECORD +12 -11
- {tomwer-1.4.4.dist-info → tomwer-1.4.5.dist-info}/WHEEL +1 -1
- {tomwer-1.4.4.dist-info → tomwer-1.4.5.dist-info}/LICENSE +0 -0
- {tomwer-1.4.4.dist-info → tomwer-1.4.5.dist-info}/entry_points.txt +0 -0
- {tomwer-1.4.4.dist-info → tomwer-1.4.5.dist-info}/top_level.txt +0 -0
@@ -15,6 +15,8 @@ import sys
|
|
15
15
|
import numpy
|
16
16
|
from silx.io.url import DataUrl
|
17
17
|
from silx.io.utils import open as open_hdf5
|
18
|
+
from silx.gui.utils import concurrent
|
19
|
+
|
18
20
|
from sluurp.executor import submit as submit_to_slurm_cluster
|
19
21
|
from sluurp.job import SBatchScriptJob
|
20
22
|
from tomoscan.io import HDF5File
|
@@ -41,6 +43,8 @@ from tomwer.core.scan.nxtomoscan import NXtomoScan
|
|
41
43
|
from tomwer.core.scan.scanbase import TomwerScanBase
|
42
44
|
from tomwer.core.utils.slurm import get_slurm_script_name, is_slurm_available
|
43
45
|
from tomwer.core.volume.volumefactory import VolumeFactory
|
46
|
+
from tomwer.core.resourcemanager import HDF5VolumeManager
|
47
|
+
from tomwer.core.volume.hdf5volume import HDF5VolumeIdentifier
|
44
48
|
|
45
49
|
from . import settings, utils
|
46
50
|
|
@@ -279,25 +283,43 @@ class _NabuBaseReconstructor:
|
|
279
283
|
success=True,
|
280
284
|
config=config_to_dump,
|
281
285
|
)
|
282
|
-
elif self.target
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
286
|
+
elif self.target in (Target.LOCAL, Target.SLURM):
|
287
|
+
axis = config_to_dump["reconstruction"].get("slice_plane", "XY")
|
288
|
+
if file_format in ("hdf5", "h5", "hdf"):
|
289
|
+
vol_identifiers = utils.get_recons_volume_identifier(
|
290
|
+
file_prefix=config_to_dump["output"]["file_prefix"],
|
291
|
+
location=config_to_dump["output"]["location"],
|
292
|
+
slice_index=None,
|
293
|
+
scan=self.scan,
|
294
|
+
file_format=file_format,
|
295
|
+
axis=axis,
|
296
|
+
)
|
297
|
+
# release potential resource lockers (like the HDF5 volume viewer)
|
298
|
+
for vol_identifier in vol_identifiers:
|
299
|
+
if isinstance(vol_identifier, HDF5VolumeIdentifier):
|
300
|
+
concurrent.submitToQtMainThread(
|
301
|
+
HDF5VolumeManager.release_resource,
|
302
|
+
vol_identifier.file_path,
|
303
|
+
)
|
304
|
+
if self.target is Target.LOCAL:
|
305
|
+
_logger.info(f"run {info} for {self.scan} with {config_to_dump}")
|
306
|
+
return self._run_nabu_locally(
|
307
|
+
conf_file=config_file,
|
308
|
+
file_format=file_format,
|
309
|
+
config_to_dump=config_to_dump,
|
310
|
+
axis=axis,
|
311
|
+
)
|
312
|
+
elif self.target is Target.SLURM:
|
313
|
+
_logger.info(
|
314
|
+
f"run {info} on slurm for {self.scan.path} with {config_to_dump}"
|
315
|
+
)
|
316
|
+
return self._run_nabu_on_slurm(
|
317
|
+
conf_file=config_file,
|
318
|
+
config_to_dump=config_to_dump,
|
319
|
+
cluster_config=self.cluster_config.to_dict(),
|
320
|
+
process_name=process_name,
|
321
|
+
info=info,
|
322
|
+
)
|
301
323
|
else:
|
302
324
|
raise ValueError(f"{self.target} is not recognized as a valid target")
|
303
325
|
|
@@ -362,7 +384,7 @@ class _NabuBaseReconstructor:
|
|
362
384
|
self._process.kill()
|
363
385
|
outs, errs = self._process.communicate()
|
364
386
|
|
365
|
-
|
387
|
+
recons_vol_identifiers = utils.get_recons_volume_identifier(
|
366
388
|
file_prefix=config_to_dump["output"]["file_prefix"],
|
367
389
|
location=config_to_dump["output"]["location"],
|
368
390
|
slice_index=None,
|
@@ -372,7 +394,7 @@ class _NabuBaseReconstructor:
|
|
372
394
|
)
|
373
395
|
return ResultsLocalRun(
|
374
396
|
success=not nabu_std_err_has_error(errs),
|
375
|
-
results_identifiers=
|
397
|
+
results_identifiers=recons_vol_identifiers,
|
376
398
|
std_out=outs,
|
377
399
|
std_err=errs,
|
378
400
|
config=config_to_dump, # config_slices,
|
@@ -12,6 +12,7 @@ from nabu.pipeline.fullfield.nabu_config import (
|
|
12
12
|
)
|
13
13
|
from silx.utils.enum import Enum as _Enum
|
14
14
|
|
15
|
+
from tomoscan.identifier import VolumeIdentifier
|
15
16
|
import tomwer.version
|
16
17
|
from tomwer.core.process.reconstruction.nabu.plane import NabuPlane
|
17
18
|
from tomwer.core.scan.edfscan import EDFTomoScan
|
@@ -100,7 +101,7 @@ def get_recons_volume_identifier(
|
|
100
101
|
scan: TomwerScanBase,
|
101
102
|
slice_index: int | None,
|
102
103
|
axis: NabuPlane,
|
103
|
-
) -> tuple:
|
104
|
+
) -> tuple[VolumeIdentifier]:
|
104
105
|
"""
|
105
106
|
return tuple of DataUrl for existings slices
|
106
107
|
"""
|
@@ -160,19 +161,6 @@ def get_recons_volume_identifier(
|
|
160
161
|
),
|
161
162
|
)
|
162
163
|
|
163
|
-
# case of the multitiff. Not handled by tomwer
|
164
|
-
# elif file_format == "tiff":
|
165
|
-
# # for single frame tiff nabu uses another convention by saving it 'directly'.
|
166
|
-
# volumes = (
|
167
|
-
# MultiTIFFVolume(
|
168
|
-
# file_path=os.path.join(
|
169
|
-
# location,
|
170
|
-
# file_prefix,
|
171
|
-
# ".".join([file_prefix, file_format]),
|
172
|
-
# ),
|
173
|
-
# ),
|
174
|
-
# )
|
175
|
-
|
176
164
|
else:
|
177
165
|
raise ValueError(f"file format not managed: {file_format}")
|
178
166
|
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class BaseResourceObserver:
|
2
|
+
"""base class of the resource observer"""
|
3
|
+
|
4
|
+
def release_resource(self, resource):
|
5
|
+
raise NotImplementedError("Base class")
|
6
|
+
|
7
|
+
|
8
|
+
class _DummyResourceManager:
|
9
|
+
"""
|
10
|
+
Simple resource manager. It holds a set of observer tha must release a resource when requested
|
11
|
+
Observers must implement the 'BaseResourceObserver'
|
12
|
+
"""
|
13
|
+
|
14
|
+
def __init__(self):
|
15
|
+
self._observers = set()
|
16
|
+
|
17
|
+
def register(self, observer):
|
18
|
+
"""Register a process to be notified when the resource should be released."""
|
19
|
+
if observer not in self._observers:
|
20
|
+
self._observers.add(observer)
|
21
|
+
|
22
|
+
def unregister(self, observer):
|
23
|
+
"""Unregister a process so it won't be notified anymore."""
|
24
|
+
if observer in self._observers:
|
25
|
+
self._observers.remove(observer)
|
26
|
+
|
27
|
+
def release_resource(self, resource: str):
|
28
|
+
"""Force all observer to release the given resource"""
|
29
|
+
for observer in self._observers:
|
30
|
+
observer.release_resource(resource)
|
31
|
+
|
32
|
+
|
33
|
+
HDF5VolumeManager = _DummyResourceManager()
|
@@ -211,7 +211,7 @@ class _PositionInfoWidget(qt.QWidget):
|
|
211
211
|
else:
|
212
212
|
value = float(self._relativePositionQLE.text())
|
213
213
|
# make sure we only emit the signal if the value changed (and when the Qline has been edited).
|
214
|
-
if self._axis_params.relative_cor_value
|
214
|
+
if isinstance(self._axis_params.relative_cor_value, (type(None), str)) or (
|
215
215
|
self._axis_params is not None
|
216
216
|
and not numpy.isclose(
|
217
217
|
value, self._axis_params.relative_cor_value, atol=1e-3
|
@@ -6,6 +6,8 @@ import h5py
|
|
6
6
|
|
7
7
|
import numpy
|
8
8
|
import numpy.lib.npyio
|
9
|
+
|
10
|
+
import os
|
9
11
|
import silx
|
10
12
|
from silx.gui import qt
|
11
13
|
from silx.gui.colors import Colormap
|
@@ -18,6 +20,7 @@ from tomwer.core.volume.volumefactory import VolumeFactory
|
|
18
20
|
from tomwer.core.scan.scanbase import TomwerScanBase
|
19
21
|
from tomwer.gui.visualization.reconstructionparameters import ReconstructionParameters
|
20
22
|
from tomwer.core.volume.hdf5volume import HDF5Volume
|
23
|
+
from tomwer.core.resourcemanager import BaseResourceObserver, HDF5VolumeManager
|
21
24
|
from tomoscan.identifier import VolumeIdentifier
|
22
25
|
|
23
26
|
from silx.gui.widgets.WaitingOverlay import WaitingOverlay
|
@@ -215,9 +218,9 @@ class _TomoApplicationContext(DataViewHooks):
|
|
215
218
|
return self.__defaultColormapDialog
|
216
219
|
|
217
220
|
|
218
|
-
class VolumeViewer(qt.QMainWindow):
|
221
|
+
class VolumeViewer(qt.QMainWindow, BaseResourceObserver):
|
219
222
|
def __init__(self, parent):
|
220
|
-
|
223
|
+
super().__init__(parent)
|
221
224
|
|
222
225
|
self._volume_loaded_in_background = (None, None)
|
223
226
|
# store the volume loaded in the background and the thread used for it as (volume_identifier, thread)
|
@@ -259,6 +262,8 @@ class VolumeViewer(qt.QMainWindow):
|
|
259
262
|
loading data on the fly and avoid loading everything into memory for
|
260
263
|
hdf5."""
|
261
264
|
self.__last_mode = None
|
265
|
+
# register the viewer as observer of the HDF5 volume (handle resource conflict)
|
266
|
+
HDF5VolumeManager.register(self)
|
262
267
|
|
263
268
|
def _close_h5_file(self):
|
264
269
|
if self._h5_file is not None:
|
@@ -266,9 +271,21 @@ class VolumeViewer(qt.QMainWindow):
|
|
266
271
|
self._h5_file = None
|
267
272
|
|
268
273
|
def close(self):
|
274
|
+
HDF5VolumeManager.unregister(self)
|
269
275
|
self._close_h5_file()
|
270
276
|
super().close()
|
271
277
|
|
278
|
+
def release_resource(self, resource: str):
|
279
|
+
if self._h5_file is not None and resource in (
|
280
|
+
self._h5_file.filename,
|
281
|
+
os.path.abspath(self._h5_file.filename),
|
282
|
+
):
|
283
|
+
_logger.warning(
|
284
|
+
f"{resource} about to be overwrite. Closing resource and cleaning plot."
|
285
|
+
)
|
286
|
+
self._close_h5_file()
|
287
|
+
self.clear()
|
288
|
+
|
272
289
|
def setScan(self, scan):
|
273
290
|
self.clear()
|
274
291
|
if scan is None:
|
@@ -276,7 +293,6 @@ class VolumeViewer(qt.QMainWindow):
|
|
276
293
|
|
277
294
|
elif len(scan.latest_vol_reconstructions) == 0:
|
278
295
|
_logger.warning(f"No reconstructed volume for {scan}")
|
279
|
-
self.clear()
|
280
296
|
self._infoWidget.setScan(scan)
|
281
297
|
else:
|
282
298
|
self._set_volumes(volumes=scan.latest_vol_reconstructions)
|
tomwer/version.py
CHANGED
@@ -220,7 +220,7 @@ orangecontrib/tomwer/widgets/visualization/tests/__init__.py,sha256=47DEQpj8HBSa
|
|
220
220
|
tomwer/__init__.py,sha256=GeLSeY4__z-HQZu1y4ptZ5Y1OeXFvG8kuEwWXhkeaMA,360
|
221
221
|
tomwer/__main__.py,sha256=7tCADiS4u7k1PCxFhlRAcYSIOpxQTGUTx8sCEQ-hi1E,8707
|
222
222
|
tomwer/utils.py,sha256=7h7dEgKAEUmQ43jkULvC1B9Adl55nkCty-SEKUKCl4U,7008
|
223
|
-
tomwer/version.py,sha256=
|
223
|
+
tomwer/version.py,sha256=YCgU5UeQQFVVtrqoIyPtoYMjDfnF7xbfD1Fop0kLlSA,4386
|
224
224
|
tomwer/app/__init__.py,sha256=RYMB2YhbQaoMXC8W-oOyfZ_Y1vmHD7L13YkKeAMuShM,85
|
225
225
|
tomwer/app/axis.py,sha256=OhDgMj_gS-45PnjKBTyOCOkmZ1Iy-Tb6Dj66mzQg0sU,5827
|
226
226
|
tomwer/app/canvas.py,sha256=sM368nniUwbQXLA-oNCg1iNwMMol_ZGTKbiw8WsC4yw,1539
|
@@ -256,6 +256,7 @@ tomwer/app/stitching/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hS
|
|
256
256
|
tomwer/app/stitching/common.py,sha256=TCXNnAz2MSJrrogB5iWptktxq-g5ED2FUt_jMnDXpnQ,14723
|
257
257
|
tomwer/core/__init__.py,sha256=ry3BZxaZzsbjymW1wqPcKROzJO9ePkiiqWp1JRVOrS8,72
|
258
258
|
tomwer/core/futureobject.py,sha256=YQE6zROyDFu2PYBI7hxZKpn-K_FqZQ9xGSIyqMErK5U,5114
|
259
|
+
tomwer/core/resourcemanager.py,sha256=VLNnVodMa-HOMZhN2qpUR_L6phJ8IHPUEPYMxuW6VHg,1067
|
259
260
|
tomwer/core/settings.py,sha256=57qD44LU_3eB50rD7RHdg5nBweiHerzWbXHcBUna6gY,4089
|
260
261
|
tomwer/core/signal.py,sha256=I5TUcyeBZzrEh1SFGs-ylJSL1aBs41ZFb3IJo3O_55c,6115
|
261
262
|
tomwer/core/tomwer_object.py,sha256=rIaD1QlN3Q0cR5h9Sap1Whn4lawu5z9zZ1KspdhYbg0,2023
|
@@ -334,14 +335,14 @@ tomwer/core/process/reconstruction/darkref/settings.py,sha256=35jliuOIjMKTOJjgn4
|
|
334
335
|
tomwer/core/process/reconstruction/nabu/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
335
336
|
tomwer/core/process/reconstruction/nabu/castvolume.py,sha256=4VEVnJxWfG8_Ku2uhUSQAgGfvP_08mXswmC3bHU7E4M,9317
|
336
337
|
tomwer/core/process/reconstruction/nabu/helical.py,sha256=gauUkoPiShvnvMQrCQXv28g0yLe-GceML5kYMSXmNIg,1997
|
337
|
-
tomwer/core/process/reconstruction/nabu/nabucommon.py,sha256
|
338
|
+
tomwer/core/process/reconstruction/nabu/nabucommon.py,sha256=-rXMJmLL3e7BCF4keT7JcKmC5SuJQsCrvIDCYcbum48,24519
|
338
339
|
tomwer/core/process/reconstruction/nabu/nabuscores.py,sha256=e5tRG1QtmVAdXb8KHMTMtBXA3KQXqKKcqipY2HzMURg,25275
|
339
340
|
tomwer/core/process/reconstruction/nabu/nabuslices.py,sha256=WAmNlfyRWL0zhEE8jMjMg7WyzPM4BcBA6bWpaH8O53A,32101
|
340
341
|
tomwer/core/process/reconstruction/nabu/nabuvolume.py,sha256=Fn0tkPDTDJQaMEJA4LzpPp0ZtG4MIu9YN82-jesxRKo,20052
|
341
342
|
tomwer/core/process/reconstruction/nabu/plane.py,sha256=366gWprWw8Rlob8jsMn753CqgudruvvVauU0ihH2lU4,400
|
342
343
|
tomwer/core/process/reconstruction/nabu/settings.py,sha256=3AJpxVQbJziw4v6F26Ppz8Q9vc9ZNepTWygqpCAbIEM,955
|
343
344
|
tomwer/core/process/reconstruction/nabu/target.py,sha256=59BbpzpBRVKEpZowcZVgdAj1GLKZGVJ52s3XA7ouLPs,122
|
344
|
-
tomwer/core/process/reconstruction/nabu/utils.py,sha256=
|
345
|
+
tomwer/core/process/reconstruction/nabu/utils.py,sha256=ymORZjJR7YkQ0VBujDyptF0xXzkj1dCwo29NjXvhA2E,16140
|
345
346
|
tomwer/core/process/reconstruction/nabu/test/test_castvolume.py,sha256=v7USCU3z0zeW4TPs-slNJJei6t6B_UeqeJG5MV9qRII,3881
|
346
347
|
tomwer/core/process/reconstruction/nabu/test/test_nabu_utils.py,sha256=8HpR_1iLkE32GYYaOyS84SR7_5nh1e48E3W4N34666g,15347
|
347
348
|
tomwer/core/process/reconstruction/nabu/test/test_nabunormalization.py,sha256=5rHDwUROEBlsEFsZoy8mmcdRpziEetF9g_oFyxlvS6k,6010
|
@@ -545,7 +546,7 @@ tomwer/gui/reconstruction/axis/AxisSettingsWidget.py,sha256=7GpHteE2j9RUo-jUQoJd
|
|
545
546
|
tomwer/gui/reconstruction/axis/AxisWidget.py,sha256=nkORZHLo8D3Sk8j-8QVr9l3Qse66s-Nv-tB6kCkSMTY,19794
|
546
547
|
tomwer/gui/reconstruction/axis/CalculationWidget.py,sha256=E9zwJ2dNnc7KGH1QHRZE2eElIzj_r4GbPCIG_hn2yhw,8628
|
547
548
|
tomwer/gui/reconstruction/axis/CompareImages.py,sha256=jqzlMBU_tfIdR1yHuZa8Avt8Te0EPBkjI6R4TY6kIlw,11425
|
548
|
-
tomwer/gui/reconstruction/axis/ControlWidget.py,sha256=
|
549
|
+
tomwer/gui/reconstruction/axis/ControlWidget.py,sha256=w2kNLuIhQhGT3KpEYWWq4XJks3Kr7R2aOPCTKz4je4U,10709
|
549
550
|
tomwer/gui/reconstruction/axis/EstimatedCORWidget.py,sha256=dL9SCjPEZ7MQLGZP0gZARb-rxD8YYSl-gcFyuN14jwg,17081
|
550
551
|
tomwer/gui/reconstruction/axis/EstimatedCorComboBox.py,sha256=Hq8o6X5-iVMqltGcpy2E5OpCP5nNV353Mepsf_R7zVY,4434
|
551
552
|
tomwer/gui/reconstruction/axis/InputWidget.py,sha256=UhEWL3aJG56PfkI7DKw-sBd6hDsRJ2NnQ0XN0nBmM6g,13052
|
@@ -668,7 +669,7 @@ tomwer/gui/visualization/scanoverview.py,sha256=boXasH759jc8p9v43nFqq6oxFGDMn8gg
|
|
668
669
|
tomwer/gui/visualization/sinogramviewer.py,sha256=M91xw63ptfG1erPt6jvFH5uqmV6KUm1B4xreco6Ixto,8380
|
669
670
|
tomwer/gui/visualization/tomoobjoverview.py,sha256=8tsVhTZw0upDSYzE1W8VXCWMBmSFcEP7SlcGA6Nl6ig,1995
|
670
671
|
tomwer/gui/visualization/volumeoverview.py,sha256=k0OLdw5dvbDlWFnk6I8TlrJu03dmKToCZVEtmAh_U9M,2496
|
671
|
-
tomwer/gui/visualization/volumeviewer.py,sha256=
|
672
|
+
tomwer/gui/visualization/volumeviewer.py,sha256=CVfRj_azmrfoSztjX3l6nnQ9Yoyk9yhNbREoXRRq7eY,17648
|
672
673
|
tomwer/gui/visualization/diffviewer/__init__.py,sha256=rZ7qOTfAChU3FouCdkZllXT9ZZqTdo1XhLZMfmOqUAE,39
|
673
674
|
tomwer/gui/visualization/diffviewer/diffviewer.py,sha256=_MOApAnImO7cYg0-Oz8EKfJt0hWeDqusYLZ_gBrRRdk,20556
|
674
675
|
tomwer/gui/visualization/diffviewer/shiftwidget.py,sha256=7gD_mo9P7HXklD416BScUnrQNDDqXkItgkzehqBam00,19608
|
@@ -903,9 +904,9 @@ tomwer/tests/orangecontrib/tomwer/widgets/visualization/tests/test_volume_viewer
|
|
903
904
|
tomwer/tests/test_ewoks/test_conversion.py,sha256=a8cEWbErXiFCAkaapi0jeEoRKYxcFQCoa-Jr_u77_OM,3656
|
904
905
|
tomwer/tests/test_ewoks/test_single_node_execution.py,sha256=YBUHfiAnkciv_kjj7biC5fOs7c7ofNImM_azGMn4LZM,2813
|
905
906
|
tomwer/tests/test_ewoks/test_workflows.py,sha256=Eq80eexf5NVL7SzvwctLOaUeuQ8V3vDiFiHgbJA4Yb8,4871
|
906
|
-
tomwer-1.4.
|
907
|
-
tomwer-1.4.
|
908
|
-
tomwer-1.4.
|
909
|
-
tomwer-1.4.
|
910
|
-
tomwer-1.4.
|
911
|
-
tomwer-1.4.
|
907
|
+
tomwer-1.4.5.dist-info/LICENSE,sha256=62p1wL0n9WMTu8x2YDv0odYgTqeSvTd9mQ0v6Mq7lzE,1876
|
908
|
+
tomwer-1.4.5.dist-info/METADATA,sha256=0NdM0A0KYCcrCLxW5E546hD9VL2otACgkkf8UvJH6ao,13377
|
909
|
+
tomwer-1.4.5.dist-info/WHEEL,sha256=A3WOREP4zgxI0fKrHUG8DC8013e3dK3n7a6HDbcEIwE,91
|
910
|
+
tomwer-1.4.5.dist-info/entry_points.txt,sha256=py3ZUWvGnWGc5c7Yhw_uBTm8Fmew0BDw3aQZnWMBNZI,500
|
911
|
+
tomwer-1.4.5.dist-info/top_level.txt,sha256=Yz5zKh0FPiImtzHYcPuztG1AO8-6KEpUWgoChGbA0Ys,21
|
912
|
+
tomwer-1.4.5.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|