Skip to content

Molecule filter

filter_by_chains(file_content, chains, retain_categories=['chem_comp'], chain_id_source='auth')

Filter a PDBx/mmCIF file by chain IDs.

Parameters:

Name Type Description Default
file_content str

Raw PDBx/mmCIF file content.

required
chains Iterable[str]

Chain IDs to select.

required
retain_categories Iterable[str]

Additional categories to keep verbatim.

['chem_comp']
chain_id_source str

Chain ID field to match, either "auth" (auth_asym_id, default) or "label" (label_asym_id).

'auth'

Returns:

Name Type Description
str str

Filtered PDBx/mmCIF file content.

Source code in src/rnapolis/molecule_filter.py
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
def filter_by_chains(
    file_content: str,
    chains: Iterable[str],
    retain_categories: Iterable[str] = ["chem_comp"],
    chain_id_source: str = "auth",
) -> str:
    """Filter a PDBx/mmCIF file by chain IDs.

    Args:
        file_content (str): Raw PDBx/mmCIF file content.
        chains (Iterable[str]): Chain IDs to select.
        retain_categories (Iterable[str]): Additional categories to keep verbatim.
        chain_id_source (str): Chain ID field to match, either ``"auth"``
            (``auth_asym_id``, default) or ``"label"`` (``label_asym_id``).

    Returns:
        str: Filtered PDBx/mmCIF file content.
    """
    data = read_cif(file_content)
    if chain_id_source == "auth":
        auth_asym_ids = set(chains)
        asym_ids = select_ids(
            data, "atom_site", "label_asym_id", "auth_asym_id", auth_asym_ids
        )
    elif chain_id_source == "label":
        asym_ids = set(chains)
        auth_asym_ids = select_ids(
            data, "atom_site", "auth_asym_id", "label_asym_id", asym_ids
        )
    else:
        raise ValueError("chain_id_source must be 'auth' or 'label'")

    entity_ids = select_ids(data, "struct_asym", "entity_id", "id", asym_ids)
    return filter_cif(data, entity_ids, asym_ids, auth_asym_ids, retain_categories)

filter_by_entity_ids(file_content, entity_ids, retain_categories=['chem_comp'])

Filter a PDBx/mmCIF file to a given set of entity IDs.

Parameters:

Name Type Description Default
file_content str

Raw PDBx/mmCIF file content.

required
entity_ids Iterable[str]

Entity IDs to include.

required
retain_categories Iterable[str]

Additional categories to keep verbatim.

['chem_comp']

Returns:

Name Type Description
str str

Filtered PDBx/mmCIF file content.

Source code in src/rnapolis/molecule_filter.py
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
def filter_by_entity_ids(
    file_content: str,
    entity_ids: Iterable[str],
    retain_categories: Iterable[str] = ["chem_comp"],
) -> str:
    """Filter a PDBx/mmCIF file to a given set of entity IDs.

    Args:
        file_content (str): Raw PDBx/mmCIF file content.
        entity_ids (Iterable[str]): Entity IDs to include.
        retain_categories (Iterable[str]): Additional categories to keep verbatim.

    Returns:
        str: Filtered PDBx/mmCIF file content.
    """
    data = read_cif(file_content)
    entity_ids = set(entity_ids)
    asym_ids = select_ids(data, "struct_asym", "id", "entity_id", entity_ids)
    auth_asym_ids = select_ids(
        data, "atom_site", "auth_asym_id", "label_asym_id", asym_ids
    )
    return filter_cif(data, entity_ids, asym_ids, auth_asym_ids, retain_categories)

filter_by_poly_types(file_content, entity_poly_types=['polyribonucleotide', 'polydeoxyribonucleotide', 'polydeoxyribonucleotide/polyribonucleotide hybrid'], retain_categories=['chem_comp'])

Filter a PDBx/mmCIF file to nucleic-acid entities with selected polymer types.

By default this keeps only RNA, DNA and RNA/DNA hybrid entities based on the _entity_poly.type field.

Parameters:

Name Type Description Default
file_content str

Raw PDBx/mmCIF file content.

required
entity_poly_types Iterable[str]

Allowed values of _entity_poly.type.

['polyribonucleotide', 'polydeoxyribonucleotide', 'polydeoxyribonucleotide/polyribonucleotide hybrid']
retain_categories Iterable[str]

Additional categories to keep verbatim.

['chem_comp']

Returns:

Name Type Description
str str

Filtered PDBx/mmCIF file content.

Source code in src/rnapolis/molecule_filter.py
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
def filter_by_poly_types(
    file_content: str,
    entity_poly_types: Iterable[str] = [
        "polyribonucleotide",
        "polydeoxyribonucleotide",
        "polydeoxyribonucleotide/polyribonucleotide hybrid",
    ],
    retain_categories: Iterable[str] = ["chem_comp"],
) -> str:
    """Filter a PDBx/mmCIF file to nucleic-acid entities with selected polymer types.

    By default this keeps only RNA, DNA and RNA/DNA hybrid entities based on the
    ``_entity_poly.type`` field.

    Args:
        file_content (str): Raw PDBx/mmCIF file content.
        entity_poly_types (Iterable[str]): Allowed values of ``_entity_poly.type``.
        retain_categories (Iterable[str]): Additional categories to keep verbatim.

    Returns:
        str: Filtered PDBx/mmCIF file content.
    """
    data = read_cif(file_content)
    entity_ids = select_ids(
        data, "entity_poly", "entity_id", "type", set(entity_poly_types)
    )
    asym_ids = select_ids(data, "struct_asym", "id", "entity_id", entity_ids)
    auth_asym_ids = select_ids(
        data, "atom_site", "auth_asym_id", "label_asym_id", asym_ids
    )
    return filter_cif(data, entity_ids, asym_ids, auth_asym_ids, retain_categories)

filter_cif(data, entity_ids, asym_ids, auth_asym_ids, retain_categories)

Build a filtered PDBx/mmCIF text containing only selected entities and categories.

Parameters:

Name Type Description Default
data list[DataContainer]

Parsed mmCIF data containers.

required
entity_ids list[str]

Entity IDs to keep.

required
asym_ids list[str]

Asymmetric unit IDs (struct_asym.id / label_asym_id) to keep.

required
auth_asym_ids list[str]

Author chain IDs (auth_asym_id) to keep.

required
retain_categories list[str]

Additional categories to copy unchanged into the output.

required

Returns:

Name Type Description
str str

Filtered PDBx/mmCIF file content.

Source code in src/rnapolis/molecule_filter.py
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
def filter_cif(
    data: list[DataContainer],
    entity_ids: list[str],
    asym_ids: list[str],
    auth_asym_ids: list[str],
    retain_categories: list[str],
) -> str:
    """Build a filtered PDBx/mmCIF text containing only selected entities and categories.

    Args:
        data: Parsed mmCIF data containers.
        entity_ids: Entity IDs to keep.
        asym_ids: Asymmetric unit IDs (``struct_asym.id`` / ``label_asym_id``) to keep.
        auth_asym_ids: Author chain IDs (``auth_asym_id``) to keep.
        retain_categories: Additional categories to copy unchanged into the output.

    Returns:
        str: Filtered PDBx/mmCIF file content.
    """
    links = load_pdbx_item_linked_group_list()
    categories_with_entity_id = [("entity", "id")] + [
        (link.child_category_id, link.child_name)
        for link in links["entity"]
        if link.parent_name == "id"
    ]
    categories_with_asym_id = [("struct_asym", "id")] + [
        (link.child_category_id, link.child_name)
        for link in links["struct_asym"]
        if link.parent_name == "id"
    ]
    categories_with_auth_asym_id = [("atom_site", "auth_asym_id")] + [
        (link.child_category_id, link.child_name)
        for link in links["atom_site"]
        if link.parent_name == "auth_asym_id"
    ]

    output = DataContainer("rnapolis")

    for table, ids in (
        (categories_with_entity_id, entity_ids),
        (categories_with_asym_id, asym_ids),
        (categories_with_auth_asym_id, auth_asym_ids),
    ):
        for category, field_name in table:
            attributes, rows = select_category_by_id(data, category, field_name, ids)

            if attributes and rows:
                obj = DataCategory(category, attributes, rows)
                output.append(obj)

    for category in retain_categories:
        obj = data[0].getObj(category)
        if obj:
            output.append(obj)

    with tempfile.NamedTemporaryFile("rt+") as tmp:
        adapter = IoAdapterPy()
        adapter.writeFile(tmp.name, [output])
        tmp.seek(0)
        return tmp.read()

load_pdbx_item_linked_group_list()

Load linked item groups from the PDBx/mmCIF dictionary.

Returns:

Type Description
dict[str, set[Link]]

Mapping from parent category ID to a set of link definitions.

Source code in src/rnapolis/molecule_filter.py
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
def load_pdbx_item_linked_group_list() -> dict[str, set["Link"]]:
    """Load linked item groups from the PDBx/mmCIF dictionary.

    Returns:
        Mapping from parent category ID to a set of link definitions.
    """
    dictionary = os.path.join(
        os.path.abspath(os.path.dirname(__file__)), "mmcif_pdbx_v50.dic"
    )
    adapter = IoAdapterPy()
    data = adapter.readFile(dictionary)
    obj = data[0].getObj("pdbx_item_linked_group_list")
    links = defaultdict(set)

    if obj:
        for row in obj.getRowList():
            row_dict = dict(zip(obj.getAttributeList(), row))
            child_category_id = row_dict["child_category_id"]
            child_name = row_dict["child_name"].split(".")[1]
            parent_name = row_dict["parent_name"].split(".")[1]
            parent_category_id = row_dict["parent_category_id"]
            links[parent_category_id].add(
                Link(parent_category_id, parent_name, child_category_id, child_name)
            )

    return links

main()

Command-line entry point for the molecule_filter tool.

The CLI:

  • reads a PDBx/mmCIF file,
  • filters its content by polymer types (e.g. RNA/DNA), entity IDs or chain IDs,
  • optionally retains extra metadata categories,
  • can convert the filtered structure to PDB format.

This is useful for preparing structural data for downstream analyses by removing components that are not relevant for a given task.

Source code in src/rnapolis/molecule_filter.py
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
def main():
    """Command-line entry point for the ``molecule_filter`` tool.

    The CLI:

    - reads a PDBx/mmCIF file,
    - filters its content by polymer types (e.g. RNA/DNA), entity IDs or chain IDs,
    - optionally retains extra metadata categories,
    - can convert the filtered structure to PDB format.

    This is useful for preparing structural data for downstream analyses by
    removing components that are not relevant for a given task.
    """
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--filter-by-poly-types",
        help=f"filter by entity poly types, possible values: {', '.join(ENTITY_POLY_TYPES)}",
        action="append",
        default=[],
    )
    parser.add_argument(
        "--filter-by-entity-ids",
        help="filter by entity IDs, e.g. 1, 2, 3",
        action="append",
        default=[],
    )
    parser.add_argument(
        "--filter-by-chains",
        help="filter by chain IDs (auth_asym_id by default), e.g. A, B, C",
        action="append",
        default=[],
    )
    parser.add_argument(
        "--chain-id-source",
        choices=("auth", "label"),
        default="auth",
        help="chain ID field used by --filter-by-chains: auth or label (default: auth)",
    )
    parser.add_argument(
        "--retain-categories",
        help="categories to retain in the output file default: chem_comp",
        action="append",
        default=["chem_comp"],
    )
    parser.add_argument(
        "--pdb", help="change output format to PDB", action="store_true"
    )
    parser.add_argument("path", help="path to a PDBx/mmCIF file")
    args = parser.parse_args()

    file = handle_input_file(args.path)
    content = None

    if args.filter_by_poly_types:
        content = filter_by_poly_types(
            file.read(),
            entity_poly_types=args.filter_by_poly_types,
            retain_categories=args.retain_categories,
        )
    elif args.filter_by_entity_ids:
        content = filter_by_entity_ids(
            file.read(),
            entity_ids=args.filter_by_entity_ids,
            retain_categories=args.retain_categories,
        )
    elif args.filter_by_chains:
        content = filter_by_chains(
            file.read(),
            chains=args.filter_by_chains,
            retain_categories=args.retain_categories,
            chain_id_source=args.chain_id_source,
        )
    else:
        parser.print_help()

    if content:
        if args.pdb:
            df = parse_cif_atoms(content)
            print(write_pdb(df))
        else:
            print(content)

read_cif(file_content)

Parse PDBx/mmCIF text content into data containers using the mmCIF IO adapter.

Parameters:

Name Type Description Default
file_content str

Raw PDBx/mmCIF file content.

required

Returns:

Name Type Description
DataContainer DataContainer

Parsed data container representing the input file.

Source code in src/rnapolis/molecule_filter.py
120
121
122
123
124
125
126
127
128
129
130
131
132
133
def read_cif(file_content: str) -> DataContainer:
    """Parse PDBx/mmCIF text content into data containers using the mmCIF IO adapter.

    Args:
        file_content (str): Raw PDBx/mmCIF file content.

    Returns:
        DataContainer: Parsed data container representing the input file.
    """
    with tempfile.NamedTemporaryFile("rt+") as f:
        adapter = IoAdapterPy()
        f.write(file_content)
        f.seek(0)
        return adapter.readFile(f.name)

select_category_by_id(data, category, field_name, ids)

Filter rows of a category by ID field values.

Parameters:

Name Type Description Default
data List[DataContainer]

Parsed mmCIF data containers.

required
category str

Category name to query.

required
field_name str

Field used to filter rows.

required
ids Iterable[str]

Accepted ID values.

required

Returns:

Type Description
Tuple[List[str], List[List[str]]]

Tuple of (attribute names, filtered rows).

Source code in src/rnapolis/molecule_filter.py
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
def select_category_by_id(
    data: List[DataContainer],
    category: str,
    field_name: str,
    ids: Iterable[str],
) -> Tuple[List[str], List[List[str]]]:
    """Filter rows of a category by ID field values.

    Args:
        data (List[DataContainer]): Parsed mmCIF data containers.
        category (str): Category name to query.
        field_name (str): Field used to filter rows.
        ids (Iterable[str]): Accepted ID values.

    Returns:
        Tuple of (attribute names, filtered rows).
    """
    obj = data[0].getObj(category)
    if not obj:
        return [], []
    attributes = obj.getAttributeList()
    if field_name not in attributes:
        return attributes, []
    index = attributes.index(field_name)
    return attributes, [row for row in obj.getRowList() if row[index] in ids]

select_ids(data, category, field_name_to_extract, field_name_to_check, accepted_values)

Select IDs from a category based on a field matching accepted values.

Parameters:

Name Type Description Default
data List[DataContainer]

Parsed mmCIF data containers.

required
category str

Category name to query (e.g. "entity_poly").

required
field_name_to_extract str

Field whose values will be returned.

required
field_name_to_check str

Field used to filter rows.

required
accepted_values Iterable[str]

Allowed values for the check field.

required

Returns:

Type Description
Set[str]

Set of extracted IDs matching the filter criteria.

Source code in src/rnapolis/molecule_filter.py
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
def select_ids(
    data: List[DataContainer],
    category: str,
    field_name_to_extract: str,
    field_name_to_check: str,
    accepted_values: Iterable[str],
) -> Set[str]:
    """Select IDs from a category based on a field matching accepted values.

    Args:
        data (List[DataContainer]): Parsed mmCIF data containers.
        category (str): Category name to query (e.g. ``"entity_poly"``).
        field_name_to_extract (str): Field whose values will be returned.
        field_name_to_check (str): Field used to filter rows.
        accepted_values (Iterable[str]): Allowed values for the check field.

    Returns:
        Set of extracted IDs matching the filter criteria.
    """
    obj = data[0].getObj(category)
    if not obj:
        return set()
    attributes = obj.getAttributeList()
    if field_name_to_check not in attributes or field_name_to_extract not in attributes:
        return set()
    index_to_check = attributes.index(field_name_to_check)
    index_to_extract = attributes.index(field_name_to_extract)
    return {
        row[index_to_extract]
        for row in obj.getRowList()
        if row[index_to_check] in accepted_values
    }