Repository: biopython/biopython

Issue:
parse_pdb_header() fails to collect missing all residues from REMARK 465 lines; misses negative numbered
### Setup

I am reporting a problem with Biopython version 1.78.

Biopython version, Python version, and operating system where happened to be running as follows:

```bash
3.7.8 | packaged by conda-forge | (default, Jul 23 2020, 03:54:19) 
[GCC 7.5.0]
CPython
Linux-4.19.112+-x86_64-with-debian-buster-sid
1.78
```

### Expected behaviour

There should be 12 residues missing for Chain R in PDB file 7C7A.

In the 7c7a header, it is clearly seen that there are 12 lines for missing residues in `REMARK 465` :

```txt
REMARK 465       A R   -11                                                      
REMARK 465       C R   -10                                                      
REMARK 465       U R    -9                                                      
REMARK 465       U R    -8                                                      
REMARK 465       U R    -7                                                      
REMARK 465       U R    -6                                                      
REMARK 465       U R    -5                                                      
REMARK 465       A R    -4                                                      
REMARK 465       A R    -3                                                      
REMARK 465       C R    -2                                                      
REMARK 465       C R     8                                                      
REMARK 465       A R     9 
```

### Actual behaviour

Biopython shows 2 residues missing for Chain R  in PDB file 7C7A.  
Just 8 and 9 get parsed out, see the results at bottom of 'Steps to reproduce'.

**The ones missing are all negative numbers. So that probably is part of the issue.**



### Steps to reproduce
Retrieve PDB file 7c7a.

An option for doing that in Jupyter:
```python
#get structures
import os
files_needed = ["7C7A.pdb"]
for file_needed in files_needed:
    if not os.path.isfile(file_needed):
        #os.system(f"curl -OL https://files.rcsb.org/download/{file_needed}.gz") #version of next line that works outside Jupyter/IPython
        !curl -OL https://files.rcsb.org/download/{file_needed}.gz # gives more feedback in Jupyter
        # os.system(f"gunzip {file_needed}.gz") #version of next line that works outside Jupyter/IPYthon
        !gunzip {file_needed}.gz
```
Run:

```python
import Bio
print(Bio.__version__)
from Bio.PDB import *
h =parse_pdb_header('7C7A.pdb')
h
```

Biopython shows what is below (I use `...` to leave out other parts not pertinent):

```text
1.78
...
'missing_residues': [{'model': None,
...
{'model': None,
   'res_name': 'GLY',
   'chain': 'L',
   'ssseq': 201,
   'insertion': None},
  {'model': None,
   'res_name': 'C',
   'chain': 'R',
   'ssseq': 8,
   'insertion': None},
  {'model': None,
   'res_name': 'A',
   'chain': 'R',
   'ssseq': 9,
   'insertion': None}],
...
```



Discussion:
Hi @fomightez,

Thanks for reporting this. Seems like the regular expression used by `parse_pdb_header` doesn't catch negative residue numbers. See the 4th line in the `pattern` variable ([line 153](https://github.com/biopython/biopython/blob/master/Bio/PDB/parse_pdb_header.py#L153)). 

Would you be willing to provide a patch yourself?
Since you pointed out where the issue is, I'd be happy to try and patch this myself.
Great, thanks, let me know if you have any questions about contributing. Also, it would be great to add a test to make sure this specific case does not give problems in the future.

Resolving patch:
diff --git a/Bio/PDB/parse_pdb_header.py b/Bio/PDB/parse_pdb_header.py
--- a/Bio/PDB/parse_pdb_header.py
+++ b/Bio/PDB/parse_pdb_header.py
@@ -150,7 +150,7 @@ def _parse_remark_465(line):
         (\d+\s[\sA-Z][\sA-Z][A-Z] |   # Either model number + residue name
             [A-Z]{1,3})               # Or only residue name with 1 (RNA) to 3 letters
         \s ([A-Za-z0-9])              # A single character chain
-        \s+(\d+[A-Za-z]?)$            # Residue number: A digit followed by an optional
+        \s+(-?\d+[A-Za-z]?)$          # Residue number: A digit followed by an optional
                                       # insertion code (Hetero-flags make no sense in
                                       # context with missing res)
         """,


Test patch:
diff --git a/Tests/test_PDB_parse_pdb_header.py b/Tests/test_PDB_parse_pdb_header.py
--- a/Tests/test_PDB_parse_pdb_header.py
+++ b/Tests/test_PDB_parse_pdb_header.py
@@ -97,6 +97,18 @@ def test_parse_remark_465(self):
             },
         )
 
+        info = _parse_remark_465("U R    -9")  # Based on PDB id: 7c7a
+        self.assertEqual(
+            info,
+            {
+                "model": None,
+                "res_name": "U",
+                "chain": "R",
+                "ssseq": -9,
+                "insertion": None,
+            },
+        )
+
         info = _parse_remark_465("2 GLU B   276B")
         self.assertEqual(
             info,