gemmi-protools 0.1.16__py3-none-any.whl → 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.

Potentially problematic release.


This version of gemmi-protools might be problematic. Click here for more details.

@@ -1,139 +0,0 @@
1
- """
2
- @Author: Luo Jiejian
3
- """
4
- import json
5
- import os
6
- import pathlib
7
- import shutil
8
- import subprocess
9
- import tempfile
10
- import uuid
11
- from typing import Optional, Union
12
-
13
- import pandas as pd
14
- from typeguard import typechecked
15
-
16
- from gemmi_protools.io.reader import StructureParser
17
-
18
-
19
- @typechecked
20
- def _read_model(model_file: Union[str, pathlib.Path]):
21
- st = StructureParser()
22
- st.load_from_file(model_file)
23
- st.set_default_model()
24
- return st
25
-
26
-
27
- @typechecked
28
- def dockq_score(query_model: Union[str, pathlib.Path],
29
- native_model: Union[str, pathlib.Path],
30
- mapping: Optional[str] = None):
31
- dockq_program = shutil.which("DockQ")
32
- if dockq_program is None:
33
- raise RuntimeError("DockQ is need")
34
-
35
- q_st = _read_model(query_model)
36
- n_st = _read_model(native_model)
37
-
38
- tmp_dir = os.path.join(tempfile.gettempdir(), str(uuid.uuid4()))
39
- os.makedirs(tmp_dir)
40
-
41
- result_file = os.path.join(tmp_dir, "result.json")
42
- q_file = os.path.join(tmp_dir, "q.pdb")
43
- n_file = os.path.join(tmp_dir, "n.pdb")
44
- q_st.to_pdb(q_file, write_minimal_pdb=True)
45
- n_st.to_pdb(n_file, write_minimal_pdb=True)
46
- if mapping is None:
47
- cid = "".join(n_st.chain_ids)
48
- mapping = cid + ":" + cid
49
-
50
- _command = "%s --mapping %s --json %s %s %s" % (dockq_program, mapping, result_file, q_file, n_file)
51
- metrics = ['DockQ', 'F1', 'chain1', 'chain2']
52
-
53
- try:
54
- _ = subprocess.run(_command, shell=True, check=True,
55
- stdout=subprocess.PIPE, stderr=subprocess.PIPE,
56
- timeout=300.0)
57
- except subprocess.CalledProcessError as e:
58
- # Handle errors in the called executable
59
- msg = e.stderr.decode()
60
- outputs = pd.DataFrame(columns=metrics)
61
- except Exception as e:
62
- # Handle other exceptions such as file not found or permissions issues
63
- msg = str(e)
64
- outputs = pd.DataFrame(columns=metrics)
65
- else:
66
- with open(result_file, "r") as fin:
67
- vals = json.load(fin)
68
- msg = "Finished"
69
- result = []
70
- for v in vals["best_result"].values():
71
- result.append(v)
72
- outputs = pd.DataFrame(result)[metrics]
73
- finally:
74
- if os.path.isdir(tmp_dir):
75
- shutil.rmtree(tmp_dir)
76
-
77
- return dict(value=outputs,
78
- msg=msg,
79
- mapping=mapping,
80
- model=query_model,
81
- native=native_model
82
- )
83
-
84
-
85
- def dockq_score_interface(query_model: Union[str, pathlib.Path],
86
- native_model: Union[str, pathlib.Path],
87
- chains_a: str,
88
- chains_b: str):
89
- ppi_if = chains_a + "@" + chains_b
90
- chs_a = list(chains_a)
91
- chs_b = list(chains_b)
92
-
93
- # if multiple chains, merge to one
94
- q_st = _read_model(query_model)
95
- n_st = _read_model(native_model)
96
-
97
- for c in chs_a + chs_b:
98
- if c not in q_st.chain_ids:
99
- raise RuntimeError("Chain %s is not in the query model: %s" % (c, query_model))
100
-
101
- for c in chs_a + chs_b:
102
- if c not in n_st.chain_ids:
103
- raise RuntimeError("Chain %s is not in the native model: %s" % (c, native_model))
104
-
105
- if len(chs_a) > 1:
106
- q_st.merge_chains(chs_a)
107
- n_st.merge_chains(chs_a)
108
-
109
- if len(chs_b) > 1:
110
- q_st.merge_chains(chs_b)
111
- n_st.merge_chains(chs_b)
112
-
113
- tmp_dir = os.path.join(tempfile.gettempdir(), str(uuid.uuid4()))
114
- os.makedirs(tmp_dir)
115
-
116
- q_file = os.path.join(tmp_dir, "qm.pdb")
117
- n_file = os.path.join(tmp_dir, "nm.pdb")
118
- q_st.to_pdb(q_file, write_minimal_pdb=True)
119
- n_st.to_pdb(n_file, write_minimal_pdb=True)
120
-
121
- chs = chs_a[0] + chs_b[0]
122
- result = dockq_score(q_file, n_file, mapping="%s:%s" % (chs, chs))
123
-
124
- if len(result["value"]) > 0:
125
- q_score = round(result["value"].iloc[0]["DockQ"], 4)
126
- f1 = round(result["value"].iloc[0]["F1"], 4)
127
- else:
128
- q_score = ""
129
- f1 = ""
130
-
131
- if os.path.isdir(tmp_dir):
132
- shutil.rmtree(tmp_dir)
133
-
134
- return dict(DockQ=q_score,
135
- F1=f1,
136
- interface=ppi_if,
137
- model=query_model,
138
- native=native_model
139
- )
@@ -1,274 +0,0 @@
1
- """
2
- @Author: Luo Jiejian
3
- @Date: 2025/1/21
4
- """
5
- import gzip
6
- import io
7
- import os
8
- import pathlib
9
- import re
10
- import shutil
11
- import subprocess
12
- import time
13
- import uuid
14
- from typing import Union
15
-
16
- import openmm
17
- import pdbfixer
18
- from openmm import app
19
- from typeguard import typechecked
20
-
21
- from gemmi_protools import StructureParser
22
- from gemmi_protools.io.cif_opts import _is_cif
23
- from gemmi_protools.io.pdb_opts import _is_pdb
24
-
25
-
26
- @typechecked
27
- def _load_by_pbdfixer(path: Union[str, pathlib.Path], cpu_platform=True) -> pdbfixer.PDBFixer:
28
- """
29
-
30
- Args:
31
- path:
32
- cpu_platform: default True, if False, auto select platform
33
-
34
- Returns:
35
-
36
- """
37
- if cpu_platform:
38
- platform = openmm.Platform.getPlatformByName('CPU')
39
- else:
40
- platform = None
41
-
42
- cur_path = pathlib.Path(path)
43
- if _is_pdb(path) or _is_cif(path):
44
- s1 = cur_path.suffixes[-1]
45
- s2 = "".join(cur_path.suffixes[-2:])
46
-
47
- if s1 in [".pdb", ".cif"]:
48
- # s1 suffix
49
- fixer = pdbfixer.PDBFixer(filename=path, platform=platform)
50
- else:
51
- # s2 suffix
52
- with gzip.open(path, "rb") as gz_handle:
53
- with io.TextIOWrapper(gz_handle, encoding="utf-8") as text_io:
54
- if s2 == ".pdb.gz":
55
- fixer = pdbfixer.PDBFixer(pdbfile=text_io, platform=platform)
56
- else:
57
- fixer = pdbfixer.PDBFixer(pdbxfile=text_io, platform=platform)
58
- else:
59
- raise ValueError("Only support .cif, .cif.gz, .pdb or .pdb.gz file, but got %s" % path)
60
- return fixer
61
-
62
-
63
- @typechecked
64
- def clean_structure(input_file: Union[str, pathlib.Path],
65
- output_file: Union[str, pathlib.Path],
66
- add_missing_residue: bool = False,
67
- add_missing_atoms: str = "heavy",
68
- keep_heterogens: str = "all",
69
- replace_nonstandard: bool = True,
70
- ph: Union[float, int] = 7.0,
71
- cpu_platform=True,
72
- clean_connect=True
73
- ):
74
- """
75
-
76
- :param input_file: str, Input structure file, support file format .cif, .cif.gz, .pdb or .pdb.gz
77
- :param output_file: str, Output structure file, support file format .cif, .pdb
78
- :param add_missing_residue: default False
79
- :param add_missing_atoms: default heavy, accepted values 'all', 'heavy', 'hydrogen', 'none'
80
- all: add missing heavy and hydrogen atoms
81
- heavy: add missing heavy atoms only
82
- hydrogen: add missing hydrogen atoms only
83
- none: not add missing atoms
84
-
85
- :param keep_heterogens: default all, accepted values 'all', 'water', 'none'
86
- all: keep all heterogens
87
- water: only keep water
88
- none: remove all heterogens
89
- :param replace_nonstandard: default True, replace all non-standard residues to standard ones
90
- :param ph: default 7.0, ph values to add missing hydrogen atoms
91
- :param cpu_platform: default True to use CPU platform, if False, auto select platform
92
- :param clean_connect: default True to clean CONECT lines in output pdb
93
-
94
- :return:
95
- str, status message of fixing
96
- if successful, return Finish, otherwise message of error
97
- """
98
- assert add_missing_atoms in ['all', 'heavy', 'hydrogen', 'none']
99
- assert keep_heterogens in ['all', 'water', 'none']
100
-
101
- try:
102
- ######################################################
103
- # load structure
104
- ######################################################
105
- fixer = _load_by_pbdfixer(input_file, cpu_platform)
106
-
107
- ######################################################
108
- # check
109
- ######################################################
110
- fixer.findMissingResidues()
111
- fixer.findMissingAtoms()
112
- ratio = "%.2f" % (len(fixer.missingAtoms) / fixer.topology.getNumResidues(),)
113
-
114
- ######################################################
115
- # replace non-standard residues
116
- ######################################################
117
- if replace_nonstandard:
118
- fixer.findNonstandardResidues()
119
- fixer.replaceNonstandardResidues()
120
-
121
- ######################################################
122
- # remove heterogens
123
- ######################################################
124
- if keep_heterogens == 'none':
125
- fixer.removeHeterogens(keepWater=False)
126
- elif keep_heterogens == 'water':
127
- fixer.removeHeterogens(keepWater=True)
128
-
129
- ######################################################
130
- # missing residue
131
- ######################################################
132
- if add_missing_residue:
133
- fixer.findMissingResidues()
134
- else:
135
- fixer.missingResidues = {}
136
-
137
- ######################################################
138
- # missing atoms
139
- ######################################################
140
- fixer.findMissingAtoms()
141
- if add_missing_atoms not in ['all', 'heavy']:
142
- fixer.missingAtoms = {}
143
- fixer.missingTerminals = {}
144
- fixer.addMissingAtoms()
145
- if add_missing_atoms in ['all', 'hydrogen']:
146
- fixer.addMissingHydrogens(ph)
147
-
148
- ######################################################
149
- # output
150
- ######################################################
151
- out_dir = os.path.dirname(output_file)
152
- if not os.path.isdir(out_dir):
153
- os.makedirs(out_dir)
154
-
155
- suffix = pathlib.Path(output_file).suffix
156
- assert suffix in [".pdb", ".cif"], "output file must be .cif or .pdb"
157
-
158
- with open(output_file, 'w') as out_handle:
159
- if suffix == ".pdb":
160
- app.PDBFile.writeFile(fixer.topology, fixer.positions, out_handle, keepIds=True)
161
- else:
162
- app.PDBxFile.writeFile(fixer.topology, fixer.positions, out_handle, keepIds=True)
163
-
164
- msg_str = "Finished"
165
- except Exception as e:
166
- msg_str = str(e)
167
- ratio = "*"
168
- else:
169
- if clean_connect:
170
- output_lines = []
171
- with open(output_file, "r") as in_handle:
172
- for line in in_handle:
173
- if not re.match("CONECT", line):
174
- output_lines.append(line)
175
- with open(output_file, "w") as out_handle:
176
- print("".join(output_lines), file=out_handle)
177
-
178
- return dict(input=input_file, msg=msg_str, res_ratio_with_missing_atoms=ratio)
179
-
180
-
181
- @typechecked
182
- def repair_structure(input_file: str,
183
- output_file: str,
184
- complex_with_dna: bool = False,
185
- complex_with_rna: bool = False,
186
- timeout: Union[int, float] = 3600):
187
- """
188
-
189
- :param input_file: .pdb or .cif or .pdb.gz or .cif.gz
190
- :param output_file: .pdb file
191
- :param complex_with_dna: bool, default False, not debug yet
192
- :param complex_with_rna: bool, default False, not debug yet
193
- :param timeout: float or int
194
- :return:
195
- """
196
- ############################################################
197
- # Check and convert input_file to .pdb if not
198
- ############################################################
199
- input_file = str(pathlib.Path(input_file).expanduser().resolve())
200
- output_file = str(pathlib.Path(output_file).expanduser().resolve())
201
- # input_file and output_file can't be the same path
202
- assert input_file != output_file, "input_file and output_file can't be the same path"
203
-
204
- assert os.path.splitext(output_file)[1] == ".pdb", "output_file Not .pdb: %s" % output_file
205
- assert _is_cif(input_file) or _is_pdb(input_file), "Not .pdb or .cif or .pdb.gz or .cif.gz: %s" % input_file
206
-
207
- ############################################################
208
- # Config Path
209
- ############################################################
210
- out_dir = os.path.dirname(output_file)
211
- if not os.path.isdir(out_dir):
212
- os.makedirs(out_dir)
213
-
214
- temp_dir = os.path.join(out_dir, "_RepairTemp_%s" % str(uuid.uuid4()))
215
- if os.path.isdir(temp_dir):
216
- shutil.rmtree(temp_dir)
217
- os.makedirs(temp_dir)
218
-
219
- # for fix the filename bug of foldx
220
- # rename the input always and save to .pdb
221
- # convert to .pdb
222
- st = StructureParser()
223
- st.load_from_file(input_file)
224
- # if exist non-1-letter chain ID, rename
225
- org2new = st.make_chain_names_to_one_letter()
226
-
227
- file_name_r = "in.pdb"
228
- in_dir_r = temp_dir
229
- st.to_pdb(os.path.join(in_dir_r, file_name_r))
230
-
231
- foldx_path = shutil.which("foldx")
232
- if foldx_path is None:
233
- raise RuntimeError("path of foldx is not set or found in PATH")
234
-
235
- cwd_dir = os.getcwd()
236
-
237
- repair_cmd = [foldx_path,
238
- "-c RepairPDB",
239
- "--pdb %s" % file_name_r,
240
- "--pdb-dir %s" % in_dir_r,
241
- "--output-dir %s" % temp_dir
242
- ]
243
- if complex_with_dna:
244
- repair_cmd.append("--complexWithDNA true")
245
-
246
- if complex_with_rna:
247
- repair_cmd.append("--complexWithRNA true")
248
-
249
- command_settings = ["cd %s &&" % temp_dir] + repair_cmd + ["&& cd %s" % cwd_dir]
250
- start = time.time()
251
- try:
252
- result = subprocess.run(" ".join(command_settings), shell=True, check=True,
253
- stdout=subprocess.PIPE, stderr=subprocess.PIPE,
254
- timeout=timeout)
255
- # Return a tuple of the file name and the stdout or stderr if command fails
256
- if result.returncode == 0:
257
- msg_str = "Finished"
258
- else:
259
- msg_str = str(result.stderr)
260
- except Exception as e:
261
- msg_str = str(e)
262
- else:
263
- if msg_str == "Finished":
264
- # just keep .pdb, ignore .fxout
265
- result_file = os.path.join(temp_dir, "in_Repair.pdb")
266
- if os.path.exists(result_file):
267
- shutil.move(result_file, output_file)
268
- else:
269
- msg_str = "result file not found"
270
- finally:
271
- if os.path.isdir(temp_dir):
272
- shutil.rmtree(temp_dir)
273
- end = time.time()
274
- return dict(input=input_file, output=output_file, msg=msg_str, use_time=round(end - start, 1))
@@ -1,74 +0,0 @@
1
- """
2
- @Author: Luo Jiejian
3
- """
4
- import pathlib
5
- from typing import Union, List
6
-
7
- import numpy as np
8
- from scipy.spatial import cKDTree
9
-
10
- from gemmi_protools.io.reader import StructureParser
11
-
12
-
13
- def _ppi_atoms(struct, chains):
14
- """
15
- Load atoms for N and O of backbone and N, O, P, S of side chains, only for PPI searching
16
- :param struct:
17
- :param chains:
18
- :return:
19
- """
20
- protein_atoms = ['N', 'ND1', 'ND2', 'NE', 'NE1', 'NE2', 'NH1', 'NH2', 'NZ',
21
- 'O', 'OD1', 'OD2', 'OE1', 'OE2', 'OG', 'OG1', 'OH',
22
- 'SD', 'SG']
23
- xna_atoms = ['N1', 'N2', 'N3', 'N4', 'N6', 'N7', 'N9',
24
- 'O2', "O2'", "O3'", 'O4', "O4'", "O5'", 'O6',
25
- 'OP1', 'OP2', 'OP3', 'P']
26
- pro_chs = []
27
- xna_chs = []
28
- for c in chains:
29
- t = struct.chain_types.get(c, "")
30
- if t == "protein":
31
- pro_chs.append(c)
32
- elif t in ["dna", "rna"]:
33
- xna_chs.append(c)
34
-
35
- pro_coord, pro_id = struct.get_atom_coords(pro_chs, protein_atoms)
36
- xna_coord, xna_id = struct.get_atom_coords(xna_chs, xna_atoms)
37
- return np.concatenate([pro_coord, xna_coord], axis=0), np.concatenate([pro_id, xna_id], axis=0)
38
-
39
-
40
- def ppi_interface_residues(in_file: Union[str, pathlib.Path],
41
- chains_x: List[str],
42
- chains_y: List[str],
43
- threshold: float = 4.0):
44
- """
45
- identify PPI among protein, DNA, RNA
46
- :param in_file:
47
- :param chains_x:
48
- :param chains_y:
49
- :param threshold:
50
- :return:
51
- PPI residues of chains_x, PPI residues of chains_y
52
- """
53
-
54
- st = StructureParser()
55
- st.load_from_file(in_file)
56
- st.set_default_model()
57
- st.STRUCT.remove_alternative_conformations()
58
- st.STRUCT.remove_ligands_and_waters()
59
- st.STRUCT.remove_hydrogens()
60
- st.STRUCT.remove_empty_chains()
61
- st.update_entity()
62
-
63
- x_coord, x_id = _ppi_atoms(st, chains_x)
64
- y_coord, y_id = _ppi_atoms(st, chains_y)
65
-
66
- kd_tree_x = cKDTree(x_coord)
67
- kd_tree_y = cKDTree(y_coord)
68
-
69
- pairs = kd_tree_x.sparse_distance_matrix(kd_tree_y, threshold, output_type='coo_matrix')
70
-
71
- x_res = np.unique(x_id[pairs.row][["ch_name", 'res_num', 'res_icode', 'res_name']])
72
- y_res = np.unique(y_id[pairs.col][["ch_name", 'res_num', 'res_icode', 'res_name']])
73
-
74
- return x_res, y_res
@@ -1,29 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: gemmi_protools
3
- Version: 0.1.16
4
- Summary: An Enhanced tool to process PDB structures based on Gemmi
5
- Author: Luo Jiejian
6
- Author-email: Luo Jiejian <luojiejian12@mails.ucas.ac.cn>
7
- License-Expression: MIT
8
- Requires-Python: >=3.10
9
- Description-Content-Type: text/markdown
10
- License-File: LICENSE
11
- Requires-Dist: gemmi>=0.7.0
12
- Requires-Dist: pandas>=2.2.3
13
- Requires-Dist: typeguard>=4.1.2
14
- Requires-Dist: numpy
15
- Requires-Dist: biopython>=1.84
16
- Requires-Dist: scipy>=1.14.1
17
- Requires-Dist: dockq
18
- Requires-Dist: hmmer
19
- Dynamic: author
20
- Dynamic: license-file
21
-
22
- # An Enhanced tool to process PDB structures based on Gemmi
23
-
24
- # Install
25
- ```commandline
26
- conda create -n gp python=3.10 anarci -c bioconda
27
- conda activate gp
28
- pip install gemmi_protools
29
- ```
@@ -1,26 +0,0 @@
1
- gemmi_protools/__init__.py,sha256=hwUw-EieCG0kwzHjTjzHF9Bc3D-J5R_l6G8PCcFegkw,331
2
- gemmi_protools/data/MHC/MHC_combined.hmm,sha256=w0_vzPiEWne_d_kYmqR0OiSsCOpQioItKy3Zq-JMsH4,159451
3
- gemmi_protools/data/MHC/MHC_combined.hmm.h3f,sha256=QGG4l-v76RYtysJ5rybnz5v6VgJg2RjoQQHUVWL5jmg,45522
4
- gemmi_protools/data/MHC/MHC_combined.hmm.h3i,sha256=yn-700hoBSJB39Tj8Ia8UhSZWpYiCZFNcbnYAFNjReI,300
5
- gemmi_protools/data/MHC/MHC_combined.hmm.h3m,sha256=CvNMCsobQiX-wL7iB4CreNcbpnElE31NmmjmMR0iYVI,66174
6
- gemmi_protools/data/MHC/MHC_combined.hmm.h3p,sha256=-mK278pRedG3-KL-DtuVAQy7La9DgXg5FcP89D6X3Ck,78325
7
- gemmi_protools/io/__init__.py,sha256=F6e1xNT_7lZAWQgNIneH06o2qtWYrHNr_xPUPTwwx5E,29
8
- gemmi_protools/io/cif_opts.py,sha256=cYEhubRP2rymbwSlB3ZQPGUFQiXGb2UQ0gdXdTd3c-I,6646
9
- gemmi_protools/io/convert.py,sha256=780sQcwhslUD4Hj5UZMVlQdbicniJ6jNjncTl_7jaMk,3841
10
- gemmi_protools/io/parse_pdb_header.py,sha256=UOGMsE3-d3APhO7zaAEE0NT31n-iqt55VpDh_RPOicI,14223
11
- gemmi_protools/io/parser.py,sha256=HFc4Kovr6rxEpLAEDUtF_e-RnA5OsWMeMFjuVNm7UYY,9507
12
- gemmi_protools/io/pdb_opts.py,sha256=laUqxlecOe6goax12q8EJGZuZbHyIGsXVucMV3gVrgg,5741
13
- gemmi_protools/io/peptide.py,sha256=a2wiEutJmvhl6gDCIzzqRCbmyknk2mwgy2FZ53lXclU,750
14
- gemmi_protools/io/reader.py,sha256=2AXg1JdYT2LxL6jWVsJkLHQREwAoYR7V-g-hQVgSgGg,16237
15
- gemmi_protools/io/struct_info.py,sha256=9nBj1Zer03S8_Wks7L7uRlc9PlbfCKzoaT32pKR58X8,2769
16
- gemmi_protools/utils/__init__.py,sha256=F6e1xNT_7lZAWQgNIneH06o2qtWYrHNr_xPUPTwwx5E,29
17
- gemmi_protools/utils/align.py,sha256=wyJDawxW10kdYWEM1F_LUEc3Qo-3_I7P5hFk-r-yqgY,7432
18
- gemmi_protools/utils/dockq.py,sha256=XmMwVEy-H4p6sH_HPcDWA3TP77OWdih0fE_BQJDr4pU,4189
19
- gemmi_protools/utils/fixer.py,sha256=yP9pTJ67n7z56UFfe2-eEsS3jkJfG2lP4KAEpXxlrnE,10142
20
- gemmi_protools/utils/pdb_annot.py,sha256=nnRlLpjczhCP1ojEgsO3FuVgfsyleDZ34QxqyI8-wr0,11143
21
- gemmi_protools/utils/ppi.py,sha256=VWYsdxWwQoS1xwEYj5KB96Zz3F8r5Eyuw6NT3ReD-wc,2330
22
- gemmi_protools-0.1.16.dist-info/licenses/LICENSE,sha256=JuQvKcgj6n11y5y6nXr9rABv3gJSswc4eTCd5WZBtSY,1062
23
- gemmi_protools-0.1.16.dist-info/METADATA,sha256=wxrsd_ApvtPBm1rLrPjFCx8mTt3boSg4-7wlx6mJaZU,750
24
- gemmi_protools-0.1.16.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
25
- gemmi_protools-0.1.16.dist-info/top_level.txt,sha256=P12mYJi5O5EKIn5u-RFaWxuix431CgLacSRD7rBid_U,15
26
- gemmi_protools-0.1.16.dist-info/RECORD,,
File without changes