Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ProtPeptigram/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def configure_logging(level: str = "info", log_to_file: bool = False) -> logging
logger.setLevel(log_level)
return logger


#add console logging
def save_console_log(output_dir,file_name: str = "prot-peptigram.log"):
"""
Saves console logs to a specified file.
Expand Down
3 changes: 2 additions & 1 deletion ProtPeptigram/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ def run_pipeline(
specific_proteins = None
if protein_list and os.path.exists(protein_list):
specific_proteins = read_protein_list(protein_list)
console.print(f"Using {len(specific_proteins)} proteins from provided list.", style="bold")
console.log(f"Using {len(specific_proteins)} proteins from provided list.", style="bold")

# 1. Initialize the data processor
processor = PeptideDataProcessor()
Expand Down Expand Up @@ -180,6 +180,7 @@ def run_pipeline(
try:
fig, _ = viz.plot_peptigram(
[prot],
groups = unique_samples,
group_by='Sample',
color_by='protein',
figsize=(14, 12),
Expand Down
235 changes: 120 additions & 115 deletions ProtPeptigram/viz.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,14 +191,17 @@ def plot_peptigram(

peptide_groups.columns = ['Peptide', 'Protein', 'Start',
'End', 'Mean_Intensity', 'Std_Intensity', 'Count', 'Groups']

# Determine groups to plot
if groups is None:
all_groups = []
for g in peptide_groups['Groups']:
all_groups.extend(g)
groups = sorted(list(set(all_groups)))


# groups = sorted(list({g for group_list in peptide_groups['Groups'] for g in group_list}))
# print(f"Groups to plot: {groups}")
# print(f"Proteins to plot: {peptide_groups}")

# Configure styling for publication-quality
plt.style.use('default')
plt.rcParams['font.family'] = 'sans-serif'
Expand Down Expand Up @@ -480,119 +483,121 @@ def plot_peptigram(
lambda x: group in x)]

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Skipping peptides with out-of-bounds indices may silently drop data.

Currently, peptides outside the plotting range are skipped without notification, which can hinder debugging. Please add a warning or indicator when peptides are omitted for this reason.

Suggested implementation:

+                    space_start = max(0, start - xlim[0])
+                    space_end = min(end - xlim[0], xlim[1] - xlim[0])
+
+                    if space_start >= spaces.shape[1] or space_end >= spaces.shape[1]:
+                        import warnings
+                        warnings.warn(
+                            f"Peptide ({start}, {end}) at height {height} is out of plotting range ({xlim[0]}, {xlim[1]}) and will be skipped.",
+                            UserWarning
+                        )
+                        continue

If the warnings module is already imported at the top of the file, you can remove the inline import warnings and use the existing import. Otherwise, for best practice, add import warnings at the top of the file.

if group_peptides.empty:
ax.set_visible(False)
continue

# group_peptides['Length'] = group_peptides['End'] - group_peptides['Start']
group_peptides.loc[:, 'Length'] = group_peptides['End'] - \
group_peptides['Start']

# Calculate maximum height needed
max_height = self._calculate_plot_height(group_peptides, xlim)

# Initialize space tracking array
spaces = np.zeros((max_height, int(xlim[1] - xlim[0] + 1)))

# Sort peptides by start position and length
group_peptides = group_peptides.sort_values(
['Start', 'End'], ascending=[True, False])

# Plot each peptide
for idx, peptide in group_peptides.iterrows():
start = int(peptide['Start'])
end = int(peptide['End'])
protein_id = peptide['Protein']

# Get base color for this protein
base_color = protein_to_color[protein_id]
final_color = base_color

# Apply coloring based on selection
if color_by_protein_and_intensity:
# Get intensity value and normalize using per-protein normalization
intensity_val = peptide['Mean_Intensity']

# Get normalization for this protein
if protein_id in protein_intensity_norms:
norm = protein_intensity_norms[protein_id]
intensity_normalized = norm(intensity_val)
else:
# Fallback to global normalization
intensity_normalized = plt.Normalize(
min_intensity_val, max_intensity_val)(intensity_val)

# Get the appropriate colormap for this protein
intensity_cmap = intensity_cmap_dict[protein_id]

# Get color from the protein's intensity colormap
if intensity_normalized < 0:
intensity_normalized = 0
elif intensity_normalized > 1:
intensity_normalized = 1

final_color = intensity_cmap(intensity_normalized)

elif color_by == 'intensity':
intensity_val = peptide['Mean_Intensity']

# Use per-protein normalization and colormap
if protein_id in protein_intensity_norms:
norm = protein_intensity_norms[protein_id]
intensity_normalized = norm(intensity_val)
else:
# Fallback to global normalization
intensity_normalized = plt.Normalize(
min_intensity_val, max_intensity_val)(intensity_val)

# Use the protein's assigned colormap
intensity_cmap = intensity_cmap_dict[protein_id]
final_color = intensity_cmap(intensity_normalized)

elif color_by == 'count':
count_val = peptide['Count']
count_normalized = plt.Normalize(
1, group_peptides['Count'].max())(count_val)
final_color = plt.cm.Blues(count_normalized)

elif color_by == 'length':
length_val = peptide['End'] - peptide['Start']
length_normalized = plt.Normalize(
group_peptides['Length'].min(), group_peptides['Length'].max())(length_val)
final_color = plt.cm.Greens(length_normalized)

# Find available space for this peptide
for height in range(max_height):
if start < xlim[0]:
start = xlim[0]
if end > xlim[1]:
end = xlim[1]

space_start = max(0, start - xlim[0])
space_end = min(end - xlim[0], xlim[1] - xlim[0])

if space_start >= spaces.shape[1] or space_end >= spaces.shape[1]:
continue

space_needed = spaces[height, space_start:space_end+1]
if np.sum(space_needed) == 0: # Space is available
spaces[height, space_start:space_end+1] = 1
#peptide visualization
ax.plot(
[start, end],
[-height-0.4, -height-0.4],
linewidth=2.5,
solid_capstyle='round',
color=final_color,
alpha=0.95,
path_effects=[
path_effects.withStroke(
linewidth=3.0,
foreground=(0, 0, 0, 0.2),
alpha=0.3
)
]
)
break
#making empty plot if no peptides in group
ax.set_xlim(xlim)
ax.set_ylim(-1, 0) # Keep consistent with non-empty plots (negative y-axis)
max_height = 1
else:
# group_peptides['Length'] = group_peptides['End'] - group_peptides['Start']
group_peptides.loc[:, 'Length'] = group_peptides['End'] - \
group_peptides['Start']

# Calculate maximum height needed
max_height = self._calculate_plot_height(group_peptides, xlim)

# Initialize space tracking array
spaces = np.zeros((max_height, int(xlim[1] - xlim[0] + 1)))

# Sort peptides by start position and length
group_peptides = group_peptides.sort_values(
['Start', 'End'], ascending=[True, False])

# Plot each peptide
for idx, peptide in group_peptides.iterrows():
start = int(peptide['Start'])
end = int(peptide['End'])
protein_id = peptide['Protein']

# Get base color for this protein
base_color = protein_to_color[protein_id]
final_color = base_color

# Apply coloring based on selection
if color_by_protein_and_intensity:
# Get intensity value and normalize using per-protein normalization
intensity_val = peptide['Mean_Intensity']

# Get normalization for this protein
if protein_id in protein_intensity_norms:
norm = protein_intensity_norms[protein_id]
intensity_normalized = norm(intensity_val)
else:
# Fallback to global normalization
intensity_normalized = plt.Normalize(
min_intensity_val, max_intensity_val)(intensity_val)

# Get the appropriate colormap for this protein
intensity_cmap = intensity_cmap_dict[protein_id]

# Get color from the protein's intensity colormap
if intensity_normalized < 0:
intensity_normalized = 0
elif intensity_normalized > 1:
intensity_normalized = 1

final_color = intensity_cmap(intensity_normalized)

elif color_by == 'intensity':
intensity_val = peptide['Mean_Intensity']

# Use per-protein normalization and colormap
if protein_id in protein_intensity_norms:
norm = protein_intensity_norms[protein_id]
intensity_normalized = norm(intensity_val)
else:
# Fallback to global normalization
intensity_normalized = plt.Normalize(
min_intensity_val, max_intensity_val)(intensity_val)

# Use the protein's assigned colormap
intensity_cmap = intensity_cmap_dict[protein_id]
final_color = intensity_cmap(intensity_normalized)

elif color_by == 'count':
count_val = peptide['Count']
count_normalized = plt.Normalize(
1, group_peptides['Count'].max())(count_val)
final_color = plt.cm.Blues(count_normalized)

elif color_by == 'length':
length_val = peptide['End'] - peptide['Start']
length_normalized = plt.Normalize(
group_peptides['Length'].min(), group_peptides['Length'].max())(length_val)
final_color = plt.cm.Greens(length_normalized)

# Find available space for this peptide
for height in range(max_height):
if start < xlim[0]:
start = xlim[0]
if end > xlim[1]:
end = xlim[1]

space_start = max(0, start - xlim[0])
space_end = min(end - xlim[0], xlim[1] - xlim[0])

if space_start >= spaces.shape[1] or space_end >= spaces.shape[1]:
continue

space_needed = spaces[height, space_start:space_end+1]
if np.sum(space_needed) == 0: # Space is available
spaces[height, space_start:space_end+1] = 1
#peptide visualization
ax.plot(
[start, end],
[-height-0.4, -height-0.4],
linewidth=2.5,
solid_capstyle='round',
color=final_color,
alpha=0.95,
path_effects=[
path_effects.withStroke(
linewidth=3.0,
foreground=(0, 0, 0, 0.2),
alpha=0.3
)
]
)
break

# Set plot limits and labels
ax.set_ylim(-max_height, 0)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
ProtPeptigram provides a comprehensive visualization platform for mapping immunopeptides to their source proteins across different biological samples. This tool can enables to identify peptide coverage patterns, analyze density distributions, and compare peptide presentations between experimental conditions.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (typo): Grammatical error: 'can enables to identify' should be corrected.

Consider revising to 'can enable identification of', 'enables identification of', or 'can enable users to identify' for clarity and correctness.

Suggested change
ProtPeptigram provides a comprehensive visualization platform for mapping immunopeptides to their source proteins across different biological samples. This tool can enables to identify peptide coverage patterns, analyze density distributions, and compare peptide presentations between experimental conditions.
ProtPeptigram provides a comprehensive visualization platform for mapping immunopeptides to their source proteins across different biological samples. This tool enables identification of peptide coverage patterns, analysis of density distributions, and comparison of peptide presentations between experimental conditions.


<p align="center">
<img src="https://github.com/Sanpme66/ProtPeptigram/blob/main/example/prot-peptigram_P63260.png" alt="ProtPeptigram Visualization Example" width="700"/>
<img src="https://github.com/Sanpme66/ProtPeptigram/blob/main/example/prot-peptigram_P60710.png" alt="ProtPeptigram Visualization Example" width="700"/>
</p>

## Features
Expand Down
Binary file added example/prot-peptigram_P60710.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.