pyaging 0.0.7__tar.gz → 0.0.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.
Potentially problematic release.
This version of pyaging might be problematic. Click here for more details.
- {pyaging-0.0.7 → pyaging-0.0.8}/PKG-INFO +1 -1
- {pyaging-0.0.7 → pyaging-0.0.8}/pyaging/predict/_pred.py +6 -21
- {pyaging-0.0.7 → pyaging-0.0.8}/pyaging/predict/_pred_utils.py +61 -182
- {pyaging-0.0.7 → pyaging-0.0.8}/pyproject.toml +1 -1
- {pyaging-0.0.7 → pyaging-0.0.8}/LICENSE +0 -0
- {pyaging-0.0.7 → pyaging-0.0.8}/README.md +0 -0
- {pyaging-0.0.7 → pyaging-0.0.8}/pyaging/__init__.py +0 -0
- {pyaging-0.0.7 → pyaging-0.0.8}/pyaging/data/__init__.py +0 -0
- {pyaging-0.0.7 → pyaging-0.0.8}/pyaging/data/_data.py +0 -0
- {pyaging-0.0.7 → pyaging-0.0.8}/pyaging/logger/__init__.py +0 -0
- {pyaging-0.0.7 → pyaging-0.0.8}/pyaging/logger/_logger.py +0 -0
- {pyaging-0.0.7 → pyaging-0.0.8}/pyaging/models/__init__.py +0 -0
- {pyaging-0.0.7 → pyaging-0.0.8}/pyaging/models/_models.py +0 -0
- {pyaging-0.0.7 → pyaging-0.0.8}/pyaging/predict/__init__.py +0 -0
- {pyaging-0.0.7 → pyaging-0.0.8}/pyaging/predict/_postprocessing.py +0 -0
- {pyaging-0.0.7 → pyaging-0.0.8}/pyaging/predict/_preprocessing.py +0 -0
- {pyaging-0.0.7 → pyaging-0.0.8}/pyaging/preprocess/__init__.py +0 -0
- {pyaging-0.0.7 → pyaging-0.0.8}/pyaging/preprocess/_preprocess.py +0 -0
- {pyaging-0.0.7 → pyaging-0.0.8}/pyaging/preprocess/_preprocess_utils.py +0 -0
- {pyaging-0.0.7 → pyaging-0.0.8}/pyaging/utils/__init__.py +0 -0
- {pyaging-0.0.7 → pyaging-0.0.8}/pyaging/utils/_utils.py +0 -0
|
@@ -66,9 +66,10 @@ def predict_age(
|
|
|
66
66
|
silence_logger("predict_age")
|
|
67
67
|
logger.first_info("Starting predict_age function")
|
|
68
68
|
|
|
69
|
-
# Ensure clock_names is a list
|
|
69
|
+
# Ensure clock_names is a list with lowercase names
|
|
70
70
|
if isinstance(clock_names, str):
|
|
71
71
|
clock_names = [clock_names]
|
|
72
|
+
clock_names = [clock_name.lower() for clock_name in clock_names]
|
|
72
73
|
|
|
73
74
|
# Set device for PyTorch operations
|
|
74
75
|
device = set_torch_device(logger)
|
|
@@ -77,7 +78,6 @@ def predict_age(
|
|
|
77
78
|
logger.info(f"🕒 Processing clock: {clock_name}", indent_level=1)
|
|
78
79
|
|
|
79
80
|
# Load and prepare the clock
|
|
80
|
-
clock_name = clock_name.lower()
|
|
81
81
|
(
|
|
82
82
|
features,
|
|
83
83
|
reference_feature_values,
|
|
@@ -108,16 +108,6 @@ def predict_age(
|
|
|
108
108
|
indent_level=2,
|
|
109
109
|
)
|
|
110
110
|
|
|
111
|
-
# Filter features and then extract data matrix
|
|
112
|
-
x_numpy = filter_features_and_extract_data(
|
|
113
|
-
adata, preprocessing, features, logger, indent_level=2
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
# Convert numpy array to tensor
|
|
117
|
-
x_tensor = convert_numpy_array_to_tensor(
|
|
118
|
-
x_numpy, device, logger, indent_level=2
|
|
119
|
-
)
|
|
120
|
-
|
|
121
111
|
# Initialize and configure the model
|
|
122
112
|
clock_model = initialize_model(
|
|
123
113
|
clock_name, features, weight_dict, device, logger, indent_level=2
|
|
@@ -125,10 +115,10 @@ def predict_age(
|
|
|
125
115
|
|
|
126
116
|
# Perform age prediction using the model
|
|
127
117
|
predicted_ages_tensor = predict_ages_with_model(
|
|
128
|
-
clock_model,
|
|
118
|
+
clock_model, adata, features, device, logger, indent_level=2
|
|
129
119
|
)
|
|
130
120
|
|
|
131
|
-
# Convert
|
|
121
|
+
# Convert torch tensor to numpy array
|
|
132
122
|
predicted_ages = convert_tensor_to_numpy_array(
|
|
133
123
|
predicted_ages_tensor, logger, indent_level=2
|
|
134
124
|
)
|
|
@@ -145,15 +135,10 @@ def predict_age(
|
|
|
145
135
|
# Add predicted ages to adata
|
|
146
136
|
add_pred_ages_adata(adata, predicted_ages, clock_name, logger, indent_level=2)
|
|
147
137
|
|
|
148
|
-
# Load all clocks metadata
|
|
149
|
-
all_clock_metadata = load_clock_metadata(dir, logger, indent_level=2)
|
|
150
|
-
|
|
151
138
|
# Add clock metadata to adata object
|
|
152
|
-
add_clock_metadata_adata(
|
|
153
|
-
adata, clock_name, all_clock_metadata, logger, indent_level=2
|
|
154
|
-
)
|
|
139
|
+
add_clock_metadata_adata(adata, clock_name, dir, logger, indent_level=2)
|
|
155
140
|
|
|
156
|
-
#
|
|
141
|
+
# Return adata to original size and number of features
|
|
157
142
|
adata = filter_missing_features(adata, logger, indent_level=2)
|
|
158
143
|
|
|
159
144
|
# Flush memory
|
|
@@ -5,6 +5,7 @@ from torch.utils.data import TensorDataset, DataLoader
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
import pandas as pd
|
|
7
7
|
import anndata
|
|
8
|
+
from anndata.experimental.pytorch import AnnLoader
|
|
8
9
|
import ntpath
|
|
9
10
|
from urllib.request import urlretrieve
|
|
10
11
|
|
|
@@ -342,6 +343,7 @@ def initialize_model(
|
|
|
342
343
|
"bitage",
|
|
343
344
|
"zhangmortality",
|
|
344
345
|
"zhangen",
|
|
346
|
+
"zhangblup",
|
|
345
347
|
"leecpc",
|
|
346
348
|
"leerpc",
|
|
347
349
|
"leerefinedrpc",
|
|
@@ -349,6 +351,7 @@ def initialize_model(
|
|
|
349
351
|
"thompsonmultitissue",
|
|
350
352
|
"petkovichblood",
|
|
351
353
|
"stubbsmultitissue",
|
|
354
|
+
"linblood",
|
|
352
355
|
]:
|
|
353
356
|
model = LinearModel(len(features))
|
|
354
357
|
elif clock_name in [
|
|
@@ -385,6 +388,7 @@ def initialize_model(
|
|
|
385
388
|
raise ValueError(f"Clock '{clock_name}' is not supported.")
|
|
386
389
|
|
|
387
390
|
model.load_state_dict(weight_dict)
|
|
391
|
+
model.to(torch.float64)
|
|
388
392
|
model = model.to(device)
|
|
389
393
|
model.eval()
|
|
390
394
|
return model
|
|
@@ -456,14 +460,6 @@ def preprocess_data(
|
|
|
456
460
|
)
|
|
457
461
|
return adata
|
|
458
462
|
|
|
459
|
-
# Skip if it has already found the preprocessing layer
|
|
460
|
-
if f"X_{preprocessing}" in adata.layers:
|
|
461
|
-
logger.info(
|
|
462
|
-
f"Layer with {preprocessing} preprocessing is already in adata",
|
|
463
|
-
indent_level=3,
|
|
464
|
-
)
|
|
465
|
-
return adata
|
|
466
|
-
|
|
467
463
|
# Move to adata.X for preprocessing
|
|
468
464
|
adata.X = (
|
|
469
465
|
adata.layers["X_imputed"].copy()
|
|
@@ -502,7 +498,6 @@ def preprocess_data(
|
|
|
502
498
|
else:
|
|
503
499
|
logger.error(f"Preprocessing function {preprocessing} not found.")
|
|
504
500
|
raise ValueError()
|
|
505
|
-
adata.layers[f"X_{preprocessing}"] = adata.X.copy()
|
|
506
501
|
|
|
507
502
|
return adata
|
|
508
503
|
|
|
@@ -601,7 +596,12 @@ def postprocess_data(
|
|
|
601
596
|
|
|
602
597
|
@progress("Predict ages with model")
|
|
603
598
|
def predict_ages_with_model(
|
|
604
|
-
model: torch.nn.Module,
|
|
599
|
+
model: torch.nn.Module,
|
|
600
|
+
adata: torch.Tensor,
|
|
601
|
+
features: List[str],
|
|
602
|
+
device: str,
|
|
603
|
+
logger,
|
|
604
|
+
indent_level: int = 2,
|
|
605
605
|
) -> torch.Tensor:
|
|
606
606
|
"""
|
|
607
607
|
Predict biological ages using a trained model and input data.
|
|
@@ -609,16 +609,23 @@ def predict_ages_with_model(
|
|
|
609
609
|
This function takes a machine learning model and input data, and returns predictions made by the model.
|
|
610
610
|
It's primarily used for estimating biological ages based on various biological markers. The function
|
|
611
611
|
assumes that the model is already trained and the data is preprocessed according to the model's requirements.
|
|
612
|
-
|
|
612
|
+
A dataloader is used because of possible memory constraints.
|
|
613
613
|
|
|
614
614
|
Parameters
|
|
615
615
|
----------
|
|
616
616
|
model : torch.nn.Module
|
|
617
617
|
A pre-trained machine learning model that can make predictions.
|
|
618
618
|
|
|
619
|
-
|
|
620
|
-
The
|
|
621
|
-
|
|
619
|
+
adata : anndata.AnnData
|
|
620
|
+
The AnnData object containing the dataset. Its `.X` attribute is expected to be a matrix where rows
|
|
621
|
+
correspond to samples and columns correspond to features.
|
|
622
|
+
|
|
623
|
+
features : list of str
|
|
624
|
+
A list of feature names to be included in the output array. Only these features from the AnnData object will
|
|
625
|
+
be extracted for age prediction.
|
|
626
|
+
|
|
627
|
+
device : str
|
|
628
|
+
Device to move AnnData to during inference. Eithe 'cpu' or 'cuda'.
|
|
622
629
|
|
|
623
630
|
logger : Logger
|
|
624
631
|
A logger object for logging the progress or any relevant information during the prediction process.
|
|
@@ -642,41 +649,26 @@ def predict_ages_with_model(
|
|
|
642
649
|
Examples
|
|
643
650
|
--------
|
|
644
651
|
>>> model = load_pretrained_model()
|
|
645
|
-
>>> predictions = predict_ages_with_model(model,
|
|
652
|
+
>>> predictions = predict_ages_with_model(model, adata, features, 'cpu', logger)
|
|
646
653
|
>>> print(predictions[:5])
|
|
647
654
|
[34.5, 29.3, 47.8, 50.1, 42.6]
|
|
648
655
|
|
|
649
656
|
"""
|
|
650
|
-
#
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
predictions = []
|
|
666
|
-
with torch.no_grad():
|
|
667
|
-
for batch in main_tqdm(
|
|
668
|
-
dataloader, indent_level=indent_level + 1, logger=logger
|
|
669
|
-
):
|
|
670
|
-
batch_data = batch[0]
|
|
671
|
-
batch_pred = model(batch_data)
|
|
672
|
-
predictions.append(batch_pred)
|
|
673
|
-
|
|
674
|
-
# Concatenate all batch predictions
|
|
675
|
-
predictions = torch.cat(predictions)
|
|
676
|
-
else:
|
|
677
|
-
# If data size is not greater than 1000, predict directly
|
|
678
|
-
with torch.no_grad():
|
|
679
|
-
predictions = model(data)
|
|
657
|
+
# Create an AnnLoader
|
|
658
|
+
use_cuda = device == "cuda"
|
|
659
|
+
dataloader = AnnLoader(adata, batch_size=1024, use_cuda=use_cuda)
|
|
660
|
+
|
|
661
|
+
# Use the AnnLoader for batched prediction
|
|
662
|
+
predictions = []
|
|
663
|
+
with torch.no_grad():
|
|
664
|
+
for batch in main_tqdm(
|
|
665
|
+
dataloader, indent_level=indent_level + 1, logger=logger
|
|
666
|
+
):
|
|
667
|
+
batch_pred = model(batch[:, features].X)
|
|
668
|
+
predictions.append(batch_pred)
|
|
669
|
+
|
|
670
|
+
# Concatenate all batch predictions
|
|
671
|
+
predictions = torch.cat(predictions)
|
|
680
672
|
return predictions
|
|
681
673
|
|
|
682
674
|
|
|
@@ -726,133 +718,6 @@ def convert_tensor_to_numpy_array(
|
|
|
726
718
|
return tensor.cpu().detach().numpy().flatten()
|
|
727
719
|
|
|
728
720
|
|
|
729
|
-
@progress("Convert numpy array to tensor")
|
|
730
|
-
def convert_numpy_array_to_tensor(
|
|
731
|
-
array: np.ndarray, device: str, logger, indent_level: int = 2
|
|
732
|
-
) -> torch.Tensor:
|
|
733
|
-
"""
|
|
734
|
-
Convert a NumPy array to a tensor.
|
|
735
|
-
|
|
736
|
-
This utility function takes a NumPy array and converts it into PyTorch tensor. It is useful in scenarios
|
|
737
|
-
where NumPy arrays need to be processed or analyzed using PyTorch-based tools or functions. The function also
|
|
738
|
-
moves the tensor to the device being used.
|
|
739
|
-
|
|
740
|
-
Parameters
|
|
741
|
-
----------
|
|
742
|
-
tensor : torch.Tensor
|
|
743
|
-
The PyTorch tensor to be converted. This can be a tensor of any shape.
|
|
744
|
-
|
|
745
|
-
device : str
|
|
746
|
-
Device to move tensor to after. Eithe 'cpu' or 'cuda'.
|
|
747
|
-
|
|
748
|
-
logger : Logger
|
|
749
|
-
A logger object for logging the progress or any relevant information during the conversion process.
|
|
750
|
-
|
|
751
|
-
indent_level : int, optional
|
|
752
|
-
The indentation level for logging messages, by default 2.
|
|
753
|
-
|
|
754
|
-
Returns
|
|
755
|
-
-------
|
|
756
|
-
numpy_array : ndarray
|
|
757
|
-
A 1D NumPy array equivalent to the input tensor.
|
|
758
|
-
|
|
759
|
-
Notes
|
|
760
|
-
-----
|
|
761
|
-
This function detaches the tensor from the computation graph, so it should be used with caution if the tensor
|
|
762
|
-
is part of a PyTorch computational graph, such as when dealing with gradients in training neural networks.
|
|
763
|
-
|
|
764
|
-
The flattening to a 1D array may lose the original shape information of the tensor, which should be considered
|
|
765
|
-
if the shape is important for further processing.
|
|
766
|
-
|
|
767
|
-
Examples
|
|
768
|
-
--------
|
|
769
|
-
>>> numpy_array = np.array([[1, 2], [3, 4]])
|
|
770
|
-
>>> tensor = convert_numpy_array_to_tensor(numpy_array, logger)
|
|
771
|
-
>>> tensor
|
|
772
|
-
torch.tensor([1, 2, 3, 4])
|
|
773
|
-
|
|
774
|
-
"""
|
|
775
|
-
tensor = torch.tensor(array, dtype=torch.float32)
|
|
776
|
-
tensor = tensor.to(device)
|
|
777
|
-
return tensor
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
@progress("Filter features and extract data matrix")
|
|
781
|
-
def filter_features_and_extract_data(
|
|
782
|
-
adata: anndata.AnnData,
|
|
783
|
-
preprocessing: str,
|
|
784
|
-
features: List[str],
|
|
785
|
-
logger,
|
|
786
|
-
indent_level: int = 2,
|
|
787
|
-
) -> np.ndarray:
|
|
788
|
-
"""
|
|
789
|
-
Filter features from an AnnData object
|
|
790
|
-
|
|
791
|
-
This function processes an AnnData object, which is commonly used in bioinformatics for storing large
|
|
792
|
-
gene expression datasets. It extracts the data matrix corresponding to a specified set of features (genes or
|
|
793
|
-
other genomic elements) and converts this subset into a numpy array.
|
|
794
|
-
|
|
795
|
-
Parameters
|
|
796
|
-
----------
|
|
797
|
-
adata : anndata.AnnData
|
|
798
|
-
The AnnData object containing the dataset. Its `.X` attribute is expected to be a matrix where rows
|
|
799
|
-
correspond to samples and columns correspond to features.
|
|
800
|
-
|
|
801
|
-
preprocessing : str
|
|
802
|
-
The name of the preprocessing method used (if any). Used to search adata.layers for data matrix.
|
|
803
|
-
|
|
804
|
-
features : list of str
|
|
805
|
-
A list of feature names to be included in the output array. Only these features from the AnnData object will
|
|
806
|
-
be extracted and converted.
|
|
807
|
-
|
|
808
|
-
logger : Logger
|
|
809
|
-
A logger object for logging progress or relevant information during the conversion process.
|
|
810
|
-
|
|
811
|
-
indent_level : int, optional
|
|
812
|
-
The indentation level for logging messages, by default 2.
|
|
813
|
-
|
|
814
|
-
Returns
|
|
815
|
-
-------
|
|
816
|
-
numpy_array : ndarray
|
|
817
|
-
A Numpy array containing the filtered data from the AnnData object, with dtype set to float32.
|
|
818
|
-
|
|
819
|
-
Notes
|
|
820
|
-
-----
|
|
821
|
-
The AnnData object is a central data structure in many bioinformatics pipelines. It efficiently handles
|
|
822
|
-
large datasets typical in genomics and transcriptomics. This function facilitates the integration of
|
|
823
|
-
AnnData-based workflows with PyTorch models and functions.
|
|
824
|
-
|
|
825
|
-
The dtype of the returned tensor is set to float32, which is a common choice for numerical operations in
|
|
826
|
-
PyTorch, especially in the context of machine learning models.
|
|
827
|
-
|
|
828
|
-
Examples
|
|
829
|
-
--------
|
|
830
|
-
>>> adata = anndata.AnnData(np.random.rand(5, 10))
|
|
831
|
-
>>> features = ['gene1', 'gene2', 'gene3']
|
|
832
|
-
>>> numpy_array = filter_features_and_extract_data(adata, None, features, logger)
|
|
833
|
-
>>> numpy_array.shape
|
|
834
|
-
array([5, 3])
|
|
835
|
-
|
|
836
|
-
"""
|
|
837
|
-
if preprocessing is not None:
|
|
838
|
-
layer_name = f"X_{preprocessing}"
|
|
839
|
-
elif "X_imputed" in adata.layers:
|
|
840
|
-
layer_name = "X_imputed"
|
|
841
|
-
else:
|
|
842
|
-
layer_name = "X_original"
|
|
843
|
-
# x_numpy = np.array(adata[:, features].layers[layer_name])
|
|
844
|
-
layer_data = adata[:, features].layers[layer_name]
|
|
845
|
-
|
|
846
|
-
if not isinstance(layer_data, np.ndarray):
|
|
847
|
-
layer_data = (
|
|
848
|
-
layer_data.toarray()
|
|
849
|
-
if hasattr(layer_data, "toarray")
|
|
850
|
-
else np.array(layer_data, dtype=np.float32)
|
|
851
|
-
)
|
|
852
|
-
|
|
853
|
-
return layer_data
|
|
854
|
-
|
|
855
|
-
|
|
856
721
|
@progress("Add predicted ages to adata")
|
|
857
722
|
def add_pred_ages_adata(
|
|
858
723
|
adata: anndata.AnnData,
|
|
@@ -959,7 +824,19 @@ def filter_missing_features(
|
|
|
959
824
|
>>> adata = filter_missing_features(adata, logger)
|
|
960
825
|
|
|
961
826
|
"""
|
|
962
|
-
|
|
827
|
+
n_missing_features = sum(adata.var["percent_na"] == 1)
|
|
828
|
+
if n_missing_features > 0:
|
|
829
|
+
logger.info(
|
|
830
|
+
f"Removing {n_missing_features} added features",
|
|
831
|
+
indent_level=indent_level + 1,
|
|
832
|
+
)
|
|
833
|
+
adata = adata[:, adata.var["percent_na"] < 1].copy()
|
|
834
|
+
else:
|
|
835
|
+
logger.info(
|
|
836
|
+
"No missing features, so adata size did not change",
|
|
837
|
+
indent_level=indent_level + 1,
|
|
838
|
+
)
|
|
839
|
+
|
|
963
840
|
return adata
|
|
964
841
|
|
|
965
842
|
|
|
@@ -967,7 +844,7 @@ def filter_missing_features(
|
|
|
967
844
|
def add_clock_metadata_adata(
|
|
968
845
|
adata: anndata.AnnData,
|
|
969
846
|
clock_name: str,
|
|
970
|
-
|
|
847
|
+
dir: str,
|
|
971
848
|
logger,
|
|
972
849
|
indent_level: int = 2,
|
|
973
850
|
) -> None:
|
|
@@ -990,9 +867,8 @@ def add_clock_metadata_adata(
|
|
|
990
867
|
The name of the aging clock. The metadata associated with this clock will be added to
|
|
991
868
|
the AnnData object.
|
|
992
869
|
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
the specified clock from this dictionary.
|
|
870
|
+
dir: str
|
|
871
|
+
The directory to deposit the downloaded file. Defaults to "pyaging_data".
|
|
996
872
|
|
|
997
873
|
logger : Logger
|
|
998
874
|
A logger object for logging the progress or relevant information during the operation.
|
|
@@ -1011,18 +887,21 @@ def add_clock_metadata_adata(
|
|
|
1011
887
|
here ensures that all relevant information about the aging clocks used in the analysis is
|
|
1012
888
|
preserved alongside the dataset.
|
|
1013
889
|
|
|
1014
|
-
It is important to ensure that the `clock_name` exists within the `all_clock_metadata` dictionary to
|
|
1015
|
-
avoid key errors.
|
|
1016
|
-
|
|
1017
890
|
Examples
|
|
1018
891
|
--------
|
|
1019
892
|
>>> adata = anndata.AnnData(np.random.rand(5, 10))
|
|
1020
|
-
>>>
|
|
1021
|
-
>>> add_clock_metadata_adata(adata, 'horvath2013', all_clock_metadata, logger)
|
|
893
|
+
>>> add_clock_metadata_adata(adata, 'horvath2013', 'pyaging_data', logger)
|
|
1022
894
|
>>> adata.uns['horvath2013_metadata']
|
|
1023
895
|
{'description': 'Horvath’s 2013 epigenetic clock', 'reference': 'Horvath, S. (2013)'}
|
|
1024
896
|
|
|
1025
897
|
"""
|
|
898
|
+
|
|
899
|
+
# Load all clocks metadata
|
|
900
|
+
url = f"https://pyaging.s3.amazonaws.com/clocks/metadata/all_clock_metadata.pt"
|
|
901
|
+
download(url, dir, logger, indent_level=indent_level)
|
|
902
|
+
all_clock_metadata = torch.load(f"{dir}/all_clock_metadata.pt")
|
|
903
|
+
|
|
904
|
+
# Add clock metadata to adata.uns
|
|
1026
905
|
adata.uns[f"{clock_name}_metadata"] = all_clock_metadata[clock_name]
|
|
1027
906
|
|
|
1028
907
|
|
|
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
|