A utility wrapper to extract_xyz_matrix_from_pdb_residue_range.
           This accepts PDB file lines and a loop.json file (a defined Rosetta format) and returns a pandas dataframe of
           the X, Y, Z coordinates for the requested atom types for all residues in all loops defined by the loop.json
           file. The dataframe is indexed by a string identifying the PDB residue and atom type.

           loop_json_contents should be a Python dict read in from a loop.json file e.g. json.loads(file_contents).

def extract_xyz_matrix_from_loop_json(pdb_lines, parsed_loop_json_contents, atoms_of_interest = backbone_atoms, expected_num_residues = None, expected_num_residue_atoms = None, allow_overlaps = False, include_all_columns = False):
        '''A utility wrapper to extract_xyz_matrix_from_pdb_residue_range.
           This accepts PDB file lines and a loop.json file (a defined Rosetta format) and returns a pandas dataframe of
           the X, Y, Z coordinates for the requested atom types for all residues in all loops defined by the loop.json
           file. The dataframe is indexed by a string identifying the PDB residue and atom type.

           loop_json_contents should be a Python dict read in from a loop.json file e.g. json.loads(file_contents).'''

        # Create one dataframe per loop segment
        dataframes = []
        for loop_set in parsed_loop_json_contents['LoopSet']:
            start_pdb_residue_id = PDB.ChainResidueID2String(loop_set['start']['chainID'], str(loop_set['start']['resSeq']) + loop_set['start']['iCode'])
            stop_pdb_residue_id = PDB.ChainResidueID2String(loop_set['stop']['chainID'], str(loop_set['stop']['resSeq']) + loop_set['stop']['iCode'])
            dataframes.append(PDB.extract_xyz_matrix_from_pdb_residue_range(pdb_lines, start_pdb_residue_id = start_pdb_residue_id, stop_pdb_residue_id = stop_pdb_residue_id, atoms_of_interest = atoms_of_interest, expected_num_residues = None, expected_num_residue_atoms = expected_num_residue_atoms, include_all_columns = include_all_columns))

        # Concatenate the dataframes
        dataframe = pandas.concat(dataframes, verify_integrity = (allow_overlaps == False)) # note: the pandas documentation notes that verify_integrity is relatively expensive
        if expected_num_residues != None and expected_num_residue_atoms != None:
            assert(dataframe.shape[0] == expected_num_residues * expected_num_residue_atoms)
        return dataframe