Skip to content
Open
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
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -98,6 +98,10 @@ virchow2 = [
"huggingface-hub>=0.27.1",
"torch>=2.0.0",
]
attentionui =[
"napari>=0.6.0",
"pyqt5>=5.15.11",
]
cobra = [
"stamp[flash-attention]",
"causal-conv1d @ git+https://github.com/KatherLab/causal-conv1d.git@52ec902314b9eda800162c73502a89f3572fc522",
Expand Down
24 changes: 24 additions & 0 deletions src/stamp/__main__.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -236,6 +236,27 @@ def _run_cli(args: argparse.Namespace) -> None:
default_slide_mpp=config.heatmaps.default_slide_mpp,
)

case "attentionui":

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

As mentioned in the other comment, this new section could be integrated into heatmaps one adding one more optional field

from stamp.heatmaps import attention_ui_

if config.attentionui is None:
raise ValueError("no attention configuration supplied")

_add_file_handle_(_logger, output_dir=config.attentionui.output_dir)
_logger.info(
"using the following configuration:\n"
f"{yaml.dump(config.attentionui.model_dump(mode='json'))}"
)
attention_ui_(
feature_dir=config.attentionui.feature_dir,
wsi_dir=config.attentionui.wsi_dir,
checkpoint_path=config.attentionui.checkpoint_path,
output_dir=config.attentionui.output_dir,
slide_paths=config.attentionui.slide_paths,
device=config.attentionui.device,
default_slide_mpp=config.attentionui.default_slide_mpp,
)

case _:
raise RuntimeError(
"unreachable: the argparser should only allow valid commands"
Expand DownExpand Up@@ -295,6 +316,9 @@ def main() -> None:
)
commands.add_parser("config", help="Print the loaded configuration")
commands.add_parser("heatmaps", help="Generate heatmaps for a trained model")
commands.add_parser(
"attentionui", help="Provides an interactive UI for exploring attention maps"
)

args = parser.parse_args()

Expand Down
3 changes: 2 additions & 1 deletion src/stamp/config.py
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
from pydantic import BaseModel, ConfigDict

from stamp.encoding.config import PatientEncodingConfig, SlideEncodingConfig
from stamp.heatmaps.config import HeatmapConfig
from stamp.heatmaps.config import HeatmapConfig, AttentionUIConfig
from stamp.modeling.config import (
AdvancedConfig,
CrossvalConfig,
Expand All@@ -24,6 +24,7 @@ class StampConfig(BaseModel):
statistics: StatsConfig | None = None

heatmaps: HeatmapConfig | None = None
attentionui: AttentionUIConfig | None = None

slide_encoding: SlideEncodingConfig | None = None

Expand Down
19 changes: 19 additions & 0 deletions src/stamp/config.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -215,6 +215,25 @@ heatmaps:
#bottomk: 5


attentionui:
output_dir: "/path/to/save/files/to"

# Directory the extracted features are saved in.
feature_dir: "/path/your/extracted/features/are/stored/in"

wsi_dir: "/path/containing/whole/slide/images/to/extract/features/from"

# Path of the model to generate the attention maps with.
checkpoint_path: "/path/to/model.ckpt"

# Slides to generate the attention maps for.
# The slide paths have to be specified relative to `wsi_dir`.
# If not specified, stamp will allow processing for all slides in `wsi_dir`.
#slide_paths:
#- slide1.svs
#- slide2.mrxs


slide_encoding:
# Encoder to use for slide encoding. Possible options are "cobra",
# "eagle", "titan", "gigapath", "chief", "prism", "madeleine".
Expand Down
52 changes: 52 additions & 0 deletions src/stamp/heatmaps/__init__.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Attention UI logic should be in a separate script, as it happens with crossval and train.

Original file line numberDiff line numberDiff line change
Expand Up@@ -314,3 +314,55 @@ def heatmaps_(

fig.savefig(slide_output_dir / f"overview-{h5_path.stem}.png")
plt.close(fig)


def attention_ui_(
*,
feature_dir: Path,
wsi_dir: Path,
checkpoint_path: Path,
output_dir: Path,
slide_paths: Iterable[Path] | None,
device: DeviceLikeType,
default_slide_mpp: SlideMPP | None,
) -> None:
try:
from stamp.heatmaps.attention_ui import show_attention_ui
except ImportError as e:
raise ImportError(
"Attention UI dependencies not installed. "
"Please reinstall stamp using `pip install 'stamp[attentionui]'`"
) from e

with torch.no_grad():
# Collect slides to generate attention maps for
if slide_paths is not None:
wsis_to_process_all = (wsi_dir / slide for slide in slide_paths)
else:
wsis_to_process_all = (
p for ext in supported_extensions for p in wsi_dir.glob(f"**/*{ext}")
)

# Check of a corresponding feature file exists
wsis_to_process = []
for wsi_path in wsis_to_process_all:
h5_path = feature_dir / wsi_path.with_suffix(".h5").name

if not h5_path.exists():
_logger.info(
f"could not find matching h5 file at {h5_path}. Skipping..."
)
continue

wsis_to_process.append(str(wsi_path))

# Launch the UI
show_attention_ui(
feature_dir,
wsis_to_process,
checkpoint_path,
output_dir,
slide_paths,
device,
default_slide_mpp,
)
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Add UI for Interactive Attention Exploration by DEschweiler · Pull Request #64 · KatherLab/STAMP · GitHub
Skip to content
Open
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
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -98,6 +98,10 @@ virchow2 = [
"huggingface-hub>=0.27.1",
"torch>=2.0.0",
]
attentionui =[
"napari>=0.6.0",
"pyqt5>=5.15.11",
]
cobra = [
"stamp[flash-attention]",
"causal-conv1d @ git+https://github.com/KatherLab/causal-conv1d.git@52ec902314b9eda800162c73502a89f3572fc522",
Expand Down
24 changes: 24 additions & 0 deletions src/stamp/__main__.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -236,6 +236,27 @@ def _run_cli(args: argparse.Namespace) -> None:
default_slide_mpp=config.heatmaps.default_slide_mpp,
)

case "attentionui":

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

As mentioned in the other comment, this new section could be integrated into heatmaps one adding one more optional field

from stamp.heatmaps import attention_ui_

if config.attentionui is None:
raise ValueError("no attention configuration supplied")

_add_file_handle_(_logger, output_dir=config.attentionui.output_dir)
_logger.info(
"using the following configuration:\n"
f"{yaml.dump(config.attentionui.model_dump(mode='json'))}"
)
attention_ui_(
feature_dir=config.attentionui.feature_dir,
wsi_dir=config.attentionui.wsi_dir,
checkpoint_path=config.attentionui.checkpoint_path,
output_dir=config.attentionui.output_dir,
slide_paths=config.attentionui.slide_paths,
device=config.attentionui.device,
default_slide_mpp=config.attentionui.default_slide_mpp,
)

case _:
raise RuntimeError(
"unreachable: the argparser should only allow valid commands"
Expand DownExpand Up@@ -295,6 +316,9 @@ def main() -> None:
)
commands.add_parser("config", help="Print the loaded configuration")
commands.add_parser("heatmaps", help="Generate heatmaps for a trained model")
commands.add_parser(
"attentionui", help="Provides an interactive UI for exploring attention maps"
)

args = parser.parse_args()

Expand Down
3 changes: 2 additions & 1 deletion src/stamp/config.py
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
from pydantic import BaseModel, ConfigDict

from stamp.encoding.config import PatientEncodingConfig, SlideEncodingConfig
from stamp.heatmaps.config import HeatmapConfig
from stamp.heatmaps.config import HeatmapConfig, AttentionUIConfig
from stamp.modeling.config import (
AdvancedConfig,
CrossvalConfig,
Expand All@@ -24,6 +24,7 @@ class StampConfig(BaseModel):
statistics: StatsConfig | None = None

heatmaps: HeatmapConfig | None = None
attentionui: AttentionUIConfig | None = None

slide_encoding: SlideEncodingConfig | None = None

Expand Down
19 changes: 19 additions & 0 deletions src/stamp/config.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -215,6 +215,25 @@ heatmaps:
#bottomk: 5


attentionui:
output_dir: "/path/to/save/files/to"

# Directory the extracted features are saved in.
feature_dir: "/path/your/extracted/features/are/stored/in"

wsi_dir: "/path/containing/whole/slide/images/to/extract/features/from"

# Path of the model to generate the attention maps with.
checkpoint_path: "/path/to/model.ckpt"

# Slides to generate the attention maps for.
# The slide paths have to be specified relative to `wsi_dir`.
# If not specified, stamp will allow processing for all slides in `wsi_dir`.
#slide_paths:
#- slide1.svs
#- slide2.mrxs


slide_encoding:
# Encoder to use for slide encoding. Possible options are "cobra",
# "eagle", "titan", "gigapath", "chief", "prism", "madeleine".
Expand Down
52 changes: 52 additions & 0 deletions src/stamp/heatmaps/__init__.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Attention UI logic should be in a separate script, as it happens with crossval and train.

Original file line numberDiff line numberDiff line change
Expand Up@@ -314,3 +314,55 @@ def heatmaps_(

fig.savefig(slide_output_dir / f"overview-{h5_path.stem}.png")
plt.close(fig)


def attention_ui_(
*,
feature_dir: Path,
wsi_dir: Path,
checkpoint_path: Path,
output_dir: Path,
slide_paths: Iterable[Path] | None,
device: DeviceLikeType,
default_slide_mpp: SlideMPP | None,
) -> None:
try:
from stamp.heatmaps.attention_ui import show_attention_ui
except ImportError as e:
raise ImportError(
"Attention UI dependencies not installed. "
"Please reinstall stamp using `pip install 'stamp[attentionui]'`"
) from e

with torch.no_grad():
# Collect slides to generate attention maps for
if slide_paths is not None:
wsis_to_process_all = (wsi_dir / slide for slide in slide_paths)
else:
wsis_to_process_all = (
p for ext in supported_extensions for p in wsi_dir.glob(f"**/*{ext}")
)

# Check of a corresponding feature file exists
wsis_to_process = []
for wsi_path in wsis_to_process_all:
h5_path = feature_dir / wsi_path.with_suffix(".h5").name

if not h5_path.exists():
_logger.info(
f"could not find matching h5 file at {h5_path}. Skipping..."
)
continue

wsis_to_process.append(str(wsi_path))

# Launch the UI
show_attention_ui(
feature_dir,
wsis_to_process,
checkpoint_path,
output_dir,
slide_paths,
device,
default_slide_mpp,
)
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add UI for Interactive Attention Exploration by DEschweiler · Pull Request #64 · KatherLab/STAMP · GitHub
Skip to content
Open
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
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -98,6 +98,10 @@ virchow2 = [
"huggingface-hub>=0.27.1",
"torch>=2.0.0",
]
attentionui =[
"napari>=0.6.0",
"pyqt5>=5.15.11",
]
cobra = [
"stamp[flash-attention]",
"causal-conv1d @ git+https://github.com/KatherLab/causal-conv1d.git@52ec902314b9eda800162c73502a89f3572fc522",
Expand Down
24 changes: 24 additions & 0 deletions src/stamp/__main__.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -236,6 +236,27 @@ def _run_cli(args: argparse.Namespace) -> None:
default_slide_mpp=config.heatmaps.default_slide_mpp,
)

case "attentionui":

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

As mentioned in the other comment, this new section could be integrated into heatmaps one adding one more optional field

from stamp.heatmaps import attention_ui_

if config.attentionui is None:
raise ValueError("no attention configuration supplied")

_add_file_handle_(_logger, output_dir=config.attentionui.output_dir)
_logger.info(
"using the following configuration:\n"
f"{yaml.dump(config.attentionui.model_dump(mode='json'))}"
)
attention_ui_(
feature_dir=config.attentionui.feature_dir,
wsi_dir=config.attentionui.wsi_dir,
checkpoint_path=config.attentionui.checkpoint_path,
output_dir=config.attentionui.output_dir,
slide_paths=config.attentionui.slide_paths,
device=config.attentionui.device,
default_slide_mpp=config.attentionui.default_slide_mpp,
)

case _:
raise RuntimeError(
"unreachable: the argparser should only allow valid commands"
Expand DownExpand Up@@ -295,6 +316,9 @@ def main() -> None:
)
commands.add_parser("config", help="Print the loaded configuration")
commands.add_parser("heatmaps", help="Generate heatmaps for a trained model")
commands.add_parser(
"attentionui", help="Provides an interactive UI for exploring attention maps"
)

args = parser.parse_args()

Expand Down
3 changes: 2 additions & 1 deletion src/stamp/config.py
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
from pydantic import BaseModel, ConfigDict

from stamp.encoding.config import PatientEncodingConfig, SlideEncodingConfig
from stamp.heatmaps.config import HeatmapConfig
from stamp.heatmaps.config import HeatmapConfig, AttentionUIConfig
from stamp.modeling.config import (
AdvancedConfig,
CrossvalConfig,
Expand All@@ -24,6 +24,7 @@ class StampConfig(BaseModel):
statistics: StatsConfig | None = None

heatmaps: HeatmapConfig | None = None
attentionui: AttentionUIConfig | None = None

slide_encoding: SlideEncodingConfig | None = None

Expand Down
19 changes: 19 additions & 0 deletions src/stamp/config.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -215,6 +215,25 @@ heatmaps:
#bottomk: 5


attentionui:
output_dir: "/path/to/save/files/to"

# Directory the extracted features are saved in.
feature_dir: "/path/your/extracted/features/are/stored/in"

wsi_dir: "/path/containing/whole/slide/images/to/extract/features/from"

# Path of the model to generate the attention maps with.
checkpoint_path: "/path/to/model.ckpt"

# Slides to generate the attention maps for.
# The slide paths have to be specified relative to `wsi_dir`.
# If not specified, stamp will allow processing for all slides in `wsi_dir`.
#slide_paths:
#- slide1.svs
#- slide2.mrxs


slide_encoding:
# Encoder to use for slide encoding. Possible options are "cobra",
# "eagle", "titan", "gigapath", "chief", "prism", "madeleine".
Expand Down
52 changes: 52 additions & 0 deletions src/stamp/heatmaps/__init__.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Attention UI logic should be in a separate script, as it happens with crossval and train.

Original file line numberDiff line numberDiff line change
Expand Up@@ -314,3 +314,55 @@ def heatmaps_(

fig.savefig(slide_output_dir / f"overview-{h5_path.stem}.png")
plt.close(fig)


def attention_ui_(
*,
feature_dir: Path,
wsi_dir: Path,
checkpoint_path: Path,
output_dir: Path,
slide_paths: Iterable[Path] | None,
device: DeviceLikeType,
default_slide_mpp: SlideMPP | None,
) -> None:
try:
from stamp.heatmaps.attention_ui import show_attention_ui
except ImportError as e:
raise ImportError(
"Attention UI dependencies not installed. "
"Please reinstall stamp using `pip install 'stamp[attentionui]'`"
) from e

with torch.no_grad():
# Collect slides to generate attention maps for
if slide_paths is not None:
wsis_to_process_all = (wsi_dir / slide for slide in slide_paths)
else:
wsis_to_process_all = (
p for ext in supported_extensions for p in wsi_dir.glob(f"**/*{ext}")
)

# Check of a corresponding feature file exists
wsis_to_process = []
for wsi_path in wsis_to_process_all:
h5_path = feature_dir / wsi_path.with_suffix(".h5").name

if not h5_path.exists():
_logger.info(
f"could not find matching h5 file at {h5_path}. Skipping..."
)
continue

wsis_to_process.append(str(wsi_path))

# Launch the UI
show_attention_ui(
feature_dir,
wsis_to_process,
checkpoint_path,
output_dir,
slide_paths,
device,
default_slide_mpp,
)
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add UI for Interactive Attention Exploration by DEschweiler · Pull Request #64 · KatherLab/STAMP · GitHub
Skip to content
Open
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
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -98,6 +98,10 @@ virchow2 = [
"huggingface-hub>=0.27.1",
"torch>=2.0.0",
]
attentionui =[
"napari>=0.6.0",
"pyqt5>=5.15.11",
]
cobra = [
"stamp[flash-attention]",
"causal-conv1d @ git+https://github.com/KatherLab/causal-conv1d.git@52ec902314b9eda800162c73502a89f3572fc522",
Expand Down
24 changes: 24 additions & 0 deletions src/stamp/__main__.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -236,6 +236,27 @@ def _run_cli(args: argparse.Namespace) -> None:
default_slide_mpp=config.heatmaps.default_slide_mpp,
)

case "attentionui":

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

As mentioned in the other comment, this new section could be integrated into heatmaps one adding one more optional field

from stamp.heatmaps import attention_ui_

if config.attentionui is None:
raise ValueError("no attention configuration supplied")

_add_file_handle_(_logger, output_dir=config.attentionui.output_dir)
_logger.info(
"using the following configuration:\n"
f"{yaml.dump(config.attentionui.model_dump(mode='json'))}"
)
attention_ui_(
feature_dir=config.attentionui.feature_dir,
wsi_dir=config.attentionui.wsi_dir,
checkpoint_path=config.attentionui.checkpoint_path,
output_dir=config.attentionui.output_dir,
slide_paths=config.attentionui.slide_paths,
device=config.attentionui.device,
default_slide_mpp=config.attentionui.default_slide_mpp,
)

case _:
raise RuntimeError(
"unreachable: the argparser should only allow valid commands"
Expand DownExpand Up@@ -295,6 +316,9 @@ def main() -> None:
)
commands.add_parser("config", help="Print the loaded configuration")
commands.add_parser("heatmaps", help="Generate heatmaps for a trained model")
commands.add_parser(
"attentionui", help="Provides an interactive UI for exploring attention maps"
)

args = parser.parse_args()

Expand Down
3 changes: 2 additions & 1 deletion src/stamp/config.py
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
from pydantic import BaseModel, ConfigDict

from stamp.encoding.config import PatientEncodingConfig, SlideEncodingConfig
from stamp.heatmaps.config import HeatmapConfig
from stamp.heatmaps.config import HeatmapConfig, AttentionUIConfig
from stamp.modeling.config import (
AdvancedConfig,
CrossvalConfig,
Expand All@@ -24,6 +24,7 @@ class StampConfig(BaseModel):
statistics: StatsConfig | None = None

heatmaps: HeatmapConfig | None = None
attentionui: AttentionUIConfig | None = None

slide_encoding: SlideEncodingConfig | None = None

Expand Down
19 changes: 19 additions & 0 deletions src/stamp/config.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -215,6 +215,25 @@ heatmaps:
#bottomk: 5


attentionui:
output_dir: "/path/to/save/files/to"

# Directory the extracted features are saved in.
feature_dir: "/path/your/extracted/features/are/stored/in"

wsi_dir: "/path/containing/whole/slide/images/to/extract/features/from"

# Path of the model to generate the attention maps with.
checkpoint_path: "/path/to/model.ckpt"

# Slides to generate the attention maps for.
# The slide paths have to be specified relative to `wsi_dir`.
# If not specified, stamp will allow processing for all slides in `wsi_dir`.
#slide_paths:
#- slide1.svs
#- slide2.mrxs


slide_encoding:
# Encoder to use for slide encoding. Possible options are "cobra",
# "eagle", "titan", "gigapath", "chief", "prism", "madeleine".
Expand Down
52 changes: 52 additions & 0 deletions src/stamp/heatmaps/__init__.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Attention UI logic should be in a separate script, as it happens with crossval and train.

Original file line numberDiff line numberDiff line change
Expand Up@@ -314,3 +314,55 @@ def heatmaps_(

fig.savefig(slide_output_dir / f"overview-{h5_path.stem}.png")
plt.close(fig)


def attention_ui_(
*,
feature_dir: Path,
wsi_dir: Path,
checkpoint_path: Path,
output_dir: Path,
slide_paths: Iterable[Path] | None,
device: DeviceLikeType,
default_slide_mpp: SlideMPP | None,
) -> None:
try:
from stamp.heatmaps.attention_ui import show_attention_ui
except ImportError as e:
raise ImportError(
"Attention UI dependencies not installed. "
"Please reinstall stamp using `pip install 'stamp[attentionui]'`"
) from e

with torch.no_grad():
# Collect slides to generate attention maps for
if slide_paths is not None:
wsis_to_process_all = (wsi_dir / slide for slide in slide_paths)
else:
wsis_to_process_all = (
p for ext in supported_extensions for p in wsi_dir.glob(f"**/*{ext}")
)

# Check of a corresponding feature file exists
wsis_to_process = []
for wsi_path in wsis_to_process_all:
h5_path = feature_dir / wsi_path.with_suffix(".h5").name

if not h5_path.exists():
_logger.info(
f"could not find matching h5 file at {h5_path}. Skipping..."
)
continue

wsis_to_process.append(str(wsi_path))

# Launch the UI
show_attention_ui(
feature_dir,
wsis_to_process,
checkpoint_path,
output_dir,
slide_paths,
device,
default_slide_mpp,
)
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Add UI for Interactive Attention Exploration by DEschweiler · Pull Request #64 · KatherLab/STAMP · GitHub
Skip to content
Open
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
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -98,6 +98,10 @@ virchow2 = [
"huggingface-hub>=0.27.1",
"torch>=2.0.0",
]
attentionui =[
"napari>=0.6.0",
"pyqt5>=5.15.11",
]
cobra = [
"stamp[flash-attention]",
"causal-conv1d @ git+https://github.com/KatherLab/causal-conv1d.git@52ec902314b9eda800162c73502a89f3572fc522",
Expand Down
24 changes: 24 additions & 0 deletions src/stamp/__main__.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -236,6 +236,27 @@ def _run_cli(args: argparse.Namespace) -> None:
default_slide_mpp=config.heatmaps.default_slide_mpp,
)

case "attentionui":

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

As mentioned in the other comment, this new section could be integrated into heatmaps one adding one more optional field

from stamp.heatmaps import attention_ui_

if config.attentionui is None:
raise ValueError("no attention configuration supplied")

_add_file_handle_(_logger, output_dir=config.attentionui.output_dir)
_logger.info(
"using the following configuration:\n"
f"{yaml.dump(config.attentionui.model_dump(mode='json'))}"
)
attention_ui_(
feature_dir=config.attentionui.feature_dir,
wsi_dir=config.attentionui.wsi_dir,
checkpoint_path=config.attentionui.checkpoint_path,
output_dir=config.attentionui.output_dir,
slide_paths=config.attentionui.slide_paths,
device=config.attentionui.device,
default_slide_mpp=config.attentionui.default_slide_mpp,
)

case _:
raise RuntimeError(
"unreachable: the argparser should only allow valid commands"
Expand DownExpand Up@@ -295,6 +316,9 @@ def main() -> None:
)
commands.add_parser("config", help="Print the loaded configuration")
commands.add_parser("heatmaps", help="Generate heatmaps for a trained model")
commands.add_parser(
"attentionui", help="Provides an interactive UI for exploring attention maps"
)

args = parser.parse_args()

Expand Down
3 changes: 2 additions & 1 deletion src/stamp/config.py
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
from pydantic import BaseModel, ConfigDict

from stamp.encoding.config import PatientEncodingConfig, SlideEncodingConfig
from stamp.heatmaps.config import HeatmapConfig
from stamp.heatmaps.config import HeatmapConfig, AttentionUIConfig
from stamp.modeling.config import (
AdvancedConfig,
CrossvalConfig,
Expand All@@ -24,6 +24,7 @@ class StampConfig(BaseModel):
statistics: StatsConfig | None = None

heatmaps: HeatmapConfig | None = None
attentionui: AttentionUIConfig | None = None

slide_encoding: SlideEncodingConfig | None = None

Expand Down
19 changes: 19 additions & 0 deletions src/stamp/config.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -215,6 +215,25 @@ heatmaps:
#bottomk: 5


attentionui:
output_dir: "/path/to/save/files/to"

# Directory the extracted features are saved in.
feature_dir: "/path/your/extracted/features/are/stored/in"

wsi_dir: "/path/containing/whole/slide/images/to/extract/features/from"

# Path of the model to generate the attention maps with.
checkpoint_path: "/path/to/model.ckpt"

# Slides to generate the attention maps for.
# The slide paths have to be specified relative to `wsi_dir`.
# If not specified, stamp will allow processing for all slides in `wsi_dir`.
#slide_paths:
#- slide1.svs
#- slide2.mrxs


slide_encoding:
# Encoder to use for slide encoding. Possible options are "cobra",
# "eagle", "titan", "gigapath", "chief", "prism", "madeleine".
Expand Down
52 changes: 52 additions & 0 deletions src/stamp/heatmaps/__init__.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Attention UI logic should be in a separate script, as it happens with crossval and train.

Original file line numberDiff line numberDiff line change
Expand Up@@ -314,3 +314,55 @@ def heatmaps_(

fig.savefig(slide_output_dir / f"overview-{h5_path.stem}.png")
plt.close(fig)


def attention_ui_(
*,
feature_dir: Path,
wsi_dir: Path,
checkpoint_path: Path,
output_dir: Path,
slide_paths: Iterable[Path] | None,
device: DeviceLikeType,
default_slide_mpp: SlideMPP | None,
) -> None:
try:
from stamp.heatmaps.attention_ui import show_attention_ui
except ImportError as e:
raise ImportError(
"Attention UI dependencies not installed. "
"Please reinstall stamp using `pip install 'stamp[attentionui]'`"
) from e

with torch.no_grad():
# Collect slides to generate attention maps for
if slide_paths is not None:
wsis_to_process_all = (wsi_dir / slide for slide in slide_paths)
else:
wsis_to_process_all = (
p for ext in supported_extensions for p in wsi_dir.glob(f"**/*{ext}")
)

# Check of a corresponding feature file exists
wsis_to_process = []
for wsi_path in wsis_to_process_all:
h5_path = feature_dir / wsi_path.with_suffix(".h5").name

if not h5_path.exists():
_logger.info(
f"could not find matching h5 file at {h5_path}. Skipping..."
)
continue

wsis_to_process.append(str(wsi_path))

# Launch the UI
show_attention_ui(
feature_dir,
wsis_to_process,
checkpoint_path,
output_dir,
slide_paths,
device,
default_slide_mpp,
)
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add UI for Interactive Attention Exploration by DEschweiler · Pull Request #64 · KatherLab/STAMP · GitHub
Skip to content
Open
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
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -98,6 +98,10 @@ virchow2 = [
"huggingface-hub>=0.27.1",
"torch>=2.0.0",
]
attentionui =[
"napari>=0.6.0",
"pyqt5>=5.15.11",
]
cobra = [
"stamp[flash-attention]",
"causal-conv1d @ git+https://github.com/KatherLab/causal-conv1d.git@52ec902314b9eda800162c73502a89f3572fc522",
Expand Down
24 changes: 24 additions & 0 deletions src/stamp/__main__.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -236,6 +236,27 @@ def _run_cli(args: argparse.Namespace) -> None:
default_slide_mpp=config.heatmaps.default_slide_mpp,
)

case "attentionui":

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

As mentioned in the other comment, this new section could be integrated into heatmaps one adding one more optional field

from stamp.heatmaps import attention_ui_

if config.attentionui is None:
raise ValueError("no attention configuration supplied")

_add_file_handle_(_logger, output_dir=config.attentionui.output_dir)
_logger.info(
"using the following configuration:\n"
f"{yaml.dump(config.attentionui.model_dump(mode='json'))}"
)
attention_ui_(
feature_dir=config.attentionui.feature_dir,
wsi_dir=config.attentionui.wsi_dir,
checkpoint_path=config.attentionui.checkpoint_path,
output_dir=config.attentionui.output_dir,
slide_paths=config.attentionui.slide_paths,
device=config.attentionui.device,
default_slide_mpp=config.attentionui.default_slide_mpp,
)

case _:
raise RuntimeError(
"unreachable: the argparser should only allow valid commands"
Expand DownExpand Up@@ -295,6 +316,9 @@ def main() -> None:
)
commands.add_parser("config", help="Print the loaded configuration")
commands.add_parser("heatmaps", help="Generate heatmaps for a trained model")
commands.add_parser(
"attentionui", help="Provides an interactive UI for exploring attention maps"
)

args = parser.parse_args()

Expand Down
3 changes: 2 additions & 1 deletion src/stamp/config.py
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
from pydantic import BaseModel, ConfigDict

from stamp.encoding.config import PatientEncodingConfig, SlideEncodingConfig
from stamp.heatmaps.config import HeatmapConfig
from stamp.heatmaps.config import HeatmapConfig, AttentionUIConfig
from stamp.modeling.config import (
AdvancedConfig,
CrossvalConfig,
Expand All@@ -24,6 +24,7 @@ class StampConfig(BaseModel):
statistics: StatsConfig | None = None

heatmaps: HeatmapConfig | None = None
attentionui: AttentionUIConfig | None = None

slide_encoding: SlideEncodingConfig | None = None

Expand Down
19 changes: 19 additions & 0 deletions src/stamp/config.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -215,6 +215,25 @@ heatmaps:
#bottomk: 5


attentionui:
output_dir: "/path/to/save/files/to"

# Directory the extracted features are saved in.
feature_dir: "/path/your/extracted/features/are/stored/in"

wsi_dir: "/path/containing/whole/slide/images/to/extract/features/from"

# Path of the model to generate the attention maps with.
checkpoint_path: "/path/to/model.ckpt"

# Slides to generate the attention maps for.
# The slide paths have to be specified relative to `wsi_dir`.
# If not specified, stamp will allow processing for all slides in `wsi_dir`.
#slide_paths:
#- slide1.svs
#- slide2.mrxs


slide_encoding:
# Encoder to use for slide encoding. Possible options are "cobra",
# "eagle", "titan", "gigapath", "chief", "prism", "madeleine".
Expand Down
52 changes: 52 additions & 0 deletions src/stamp/heatmaps/__init__.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Attention UI logic should be in a separate script, as it happens with crossval and train.

Original file line numberDiff line numberDiff line change
Expand Up@@ -314,3 +314,55 @@ def heatmaps_(

fig.savefig(slide_output_dir / f"overview-{h5_path.stem}.png")
plt.close(fig)


def attention_ui_(
*,
feature_dir: Path,
wsi_dir: Path,
checkpoint_path: Path,
output_dir: Path,
slide_paths: Iterable[Path] | None,
device: DeviceLikeType,
default_slide_mpp: SlideMPP | None,
) -> None:
try:
from stamp.heatmaps.attention_ui import show_attention_ui
except ImportError as e:
raise ImportError(
"Attention UI dependencies not installed. "
"Please reinstall stamp using `pip install 'stamp[attentionui]'`"
) from e

with torch.no_grad():
# Collect slides to generate attention maps for
if slide_paths is not None:
wsis_to_process_all = (wsi_dir / slide for slide in slide_paths)
else:
wsis_to_process_all = (
p for ext in supported_extensions for p in wsi_dir.glob(f"**/*{ext}")
)

# Check of a corresponding feature file exists
wsis_to_process = []
for wsi_path in wsis_to_process_all:
h5_path = feature_dir / wsi_path.with_suffix(".h5").name

if not h5_path.exists():
_logger.info(
f"could not find matching h5 file at {h5_path}. Skipping..."
)
continue

wsis_to_process.append(str(wsi_path))

# Launch the UI
show_attention_ui(
feature_dir,
wsis_to_process,
checkpoint_path,
output_dir,
slide_paths,
device,
default_slide_mpp,
)
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add UI for Interactive Attention Exploration by DEschweiler · Pull Request #64 · KatherLab/STAMP · GitHub
Skip to content
Open
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
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -98,6 +98,10 @@ virchow2 = [
"huggingface-hub>=0.27.1",
"torch>=2.0.0",
]
attentionui =[
"napari>=0.6.0",
"pyqt5>=5.15.11",
]
cobra = [
"stamp[flash-attention]",
"causal-conv1d @ git+https://github.com/KatherLab/causal-conv1d.git@52ec902314b9eda800162c73502a89f3572fc522",
Expand Down
24 changes: 24 additions & 0 deletions src/stamp/__main__.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -236,6 +236,27 @@ def _run_cli(args: argparse.Namespace) -> None:
default_slide_mpp=config.heatmaps.default_slide_mpp,
)

case "attentionui":

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

As mentioned in the other comment, this new section could be integrated into heatmaps one adding one more optional field

from stamp.heatmaps import attention_ui_

if config.attentionui is None:
raise ValueError("no attention configuration supplied")

_add_file_handle_(_logger, output_dir=config.attentionui.output_dir)
_logger.info(
"using the following configuration:\n"
f"{yaml.dump(config.attentionui.model_dump(mode='json'))}"
)
attention_ui_(
feature_dir=config.attentionui.feature_dir,
wsi_dir=config.attentionui.wsi_dir,
checkpoint_path=config.attentionui.checkpoint_path,
output_dir=config.attentionui.output_dir,
slide_paths=config.attentionui.slide_paths,
device=config.attentionui.device,
default_slide_mpp=config.attentionui.default_slide_mpp,
)

case _:
raise RuntimeError(
"unreachable: the argparser should only allow valid commands"
Expand DownExpand Up@@ -295,6 +316,9 @@ def main() -> None:
)
commands.add_parser("config", help="Print the loaded configuration")
commands.add_parser("heatmaps", help="Generate heatmaps for a trained model")
commands.add_parser(
"attentionui", help="Provides an interactive UI for exploring attention maps"
)

args = parser.parse_args()

Expand Down
3 changes: 2 additions & 1 deletion src/stamp/config.py
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
from pydantic import BaseModel, ConfigDict

from stamp.encoding.config import PatientEncodingConfig, SlideEncodingConfig
from stamp.heatmaps.config import HeatmapConfig
from stamp.heatmaps.config import HeatmapConfig, AttentionUIConfig
from stamp.modeling.config import (
AdvancedConfig,
CrossvalConfig,
Expand All@@ -24,6 +24,7 @@ class StampConfig(BaseModel):
statistics: StatsConfig | None = None

heatmaps: HeatmapConfig | None = None
attentionui: AttentionUIConfig | None = None

slide_encoding: SlideEncodingConfig | None = None

Expand Down
19 changes: 19 additions & 0 deletions src/stamp/config.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -215,6 +215,25 @@ heatmaps:
#bottomk: 5


attentionui:
output_dir: "/path/to/save/files/to"

# Directory the extracted features are saved in.
feature_dir: "/path/your/extracted/features/are/stored/in"

wsi_dir: "/path/containing/whole/slide/images/to/extract/features/from"

# Path of the model to generate the attention maps with.
checkpoint_path: "/path/to/model.ckpt"

# Slides to generate the attention maps for.
# The slide paths have to be specified relative to `wsi_dir`.
# If not specified, stamp will allow processing for all slides in `wsi_dir`.
#slide_paths:
#- slide1.svs
#- slide2.mrxs


slide_encoding:
# Encoder to use for slide encoding. Possible options are "cobra",
# "eagle", "titan", "gigapath", "chief", "prism", "madeleine".
Expand Down
52 changes: 52 additions & 0 deletions src/stamp/heatmaps/__init__.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Attention UI logic should be in a separate script, as it happens with crossval and train.

Original file line numberDiff line numberDiff line change
Expand Up@@ -314,3 +314,55 @@ def heatmaps_(

fig.savefig(slide_output_dir / f"overview-{h5_path.stem}.png")
plt.close(fig)


def attention_ui_(
*,
feature_dir: Path,
wsi_dir: Path,
checkpoint_path: Path,
output_dir: Path,
slide_paths: Iterable[Path] | None,
device: DeviceLikeType,
default_slide_mpp: SlideMPP | None,
) -> None:
try:
from stamp.heatmaps.attention_ui import show_attention_ui
except ImportError as e:
raise ImportError(
"Attention UI dependencies not installed. "
"Please reinstall stamp using `pip install 'stamp[attentionui]'`"
) from e

with torch.no_grad():
# Collect slides to generate attention maps for
if slide_paths is not None:
wsis_to_process_all = (wsi_dir / slide for slide in slide_paths)
else:
wsis_to_process_all = (
p for ext in supported_extensions for p in wsi_dir.glob(f"**/*{ext}")
)

# Check of a corresponding feature file exists
wsis_to_process = []
for wsi_path in wsis_to_process_all:
h5_path = feature_dir / wsi_path.with_suffix(".h5").name

if not h5_path.exists():
_logger.info(
f"could not find matching h5 file at {h5_path}. Skipping..."
)
continue

wsis_to_process.append(str(wsi_path))

# Launch the UI
show_attention_ui(
feature_dir,
wsis_to_process,
checkpoint_path,
output_dir,
slide_paths,
device,
default_slide_mpp,
)
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Add UI for Interactive Attention Exploration by DEschweiler · Pull Request #64 · KatherLab/STAMP · GitHub
Skip to content
Open
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
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -98,6 +98,10 @@ virchow2 = [
"huggingface-hub>=0.27.1",
"torch>=2.0.0",
]
attentionui =[
"napari>=0.6.0",
"pyqt5>=5.15.11",
]
cobra = [
"stamp[flash-attention]",
"causal-conv1d @ git+https://github.com/KatherLab/causal-conv1d.git@52ec902314b9eda800162c73502a89f3572fc522",
Expand Down
24 changes: 24 additions & 0 deletions src/stamp/__main__.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -236,6 +236,27 @@ def _run_cli(args: argparse.Namespace) -> None:
default_slide_mpp=config.heatmaps.default_slide_mpp,
)

case "attentionui":

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

As mentioned in the other comment, this new section could be integrated into heatmaps one adding one more optional field

from stamp.heatmaps import attention_ui_

if config.attentionui is None:
raise ValueError("no attention configuration supplied")

_add_file_handle_(_logger, output_dir=config.attentionui.output_dir)
_logger.info(
"using the following configuration:\n"
f"{yaml.dump(config.attentionui.model_dump(mode='json'))}"
)
attention_ui_(
feature_dir=config.attentionui.feature_dir,
wsi_dir=config.attentionui.wsi_dir,
checkpoint_path=config.attentionui.checkpoint_path,
output_dir=config.attentionui.output_dir,
slide_paths=config.attentionui.slide_paths,
device=config.attentionui.device,
default_slide_mpp=config.attentionui.default_slide_mpp,
)

case _:
raise RuntimeError(
"unreachable: the argparser should only allow valid commands"
Expand DownExpand Up@@ -295,6 +316,9 @@ def main() -> None:
)
commands.add_parser("config", help="Print the loaded configuration")
commands.add_parser("heatmaps", help="Generate heatmaps for a trained model")
commands.add_parser(
"attentionui", help="Provides an interactive UI for exploring attention maps"
)

args = parser.parse_args()

Expand Down
3 changes: 2 additions & 1 deletion src/stamp/config.py
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
from pydantic import BaseModel, ConfigDict

from stamp.encoding.config import PatientEncodingConfig, SlideEncodingConfig
from stamp.heatmaps.config import HeatmapConfig
from stamp.heatmaps.config import HeatmapConfig, AttentionUIConfig
from stamp.modeling.config import (
AdvancedConfig,
CrossvalConfig,
Expand All@@ -24,6 +24,7 @@ class StampConfig(BaseModel):
statistics: StatsConfig | None = None

heatmaps: HeatmapConfig | None = None
attentionui: AttentionUIConfig | None = None

slide_encoding: SlideEncodingConfig | None = None

Expand Down
19 changes: 19 additions & 0 deletions src/stamp/config.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -215,6 +215,25 @@ heatmaps:
#bottomk: 5


attentionui:
output_dir: "/path/to/save/files/to"

# Directory the extracted features are saved in.
feature_dir: "/path/your/extracted/features/are/stored/in"

wsi_dir: "/path/containing/whole/slide/images/to/extract/features/from"

# Path of the model to generate the attention maps with.
checkpoint_path: "/path/to/model.ckpt"

# Slides to generate the attention maps for.
# The slide paths have to be specified relative to `wsi_dir`.
# If not specified, stamp will allow processing for all slides in `wsi_dir`.
#slide_paths:
#- slide1.svs
#- slide2.mrxs


slide_encoding:
# Encoder to use for slide encoding. Possible options are "cobra",
# "eagle", "titan", "gigapath", "chief", "prism", "madeleine".
Expand Down
52 changes: 52 additions & 0 deletions src/stamp/heatmaps/__init__.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Attention UI logic should be in a separate script, as it happens with crossval and train.

Original file line numberDiff line numberDiff line change
Expand Up@@ -314,3 +314,55 @@ def heatmaps_(

fig.savefig(slide_output_dir / f"overview-{h5_path.stem}.png")
plt.close(fig)


def attention_ui_(
*,
feature_dir: Path,
wsi_dir: Path,
checkpoint_path: Path,
output_dir: Path,
slide_paths: Iterable[Path] | None,
device: DeviceLikeType,
default_slide_mpp: SlideMPP | None,
) -> None:
try:
from stamp.heatmaps.attention_ui import show_attention_ui
except ImportError as e:
raise ImportError(
"Attention UI dependencies not installed. "
"Please reinstall stamp using `pip install 'stamp[attentionui]'`"
) from e

with torch.no_grad():
# Collect slides to generate attention maps for
if slide_paths is not None:
wsis_to_process_all = (wsi_dir / slide for slide in slide_paths)
else:
wsis_to_process_all = (
p for ext in supported_extensions for p in wsi_dir.glob(f"**/*{ext}")
)

# Check of a corresponding feature file exists
wsis_to_process = []
for wsi_path in wsis_to_process_all:
h5_path = feature_dir / wsi_path.with_suffix(".h5").name

if not h5_path.exists():
_logger.info(
f"could not find matching h5 file at {h5_path}. Skipping..."
)
continue

wsis_to_process.append(str(wsi_path))

# Launch the UI
show_attention_ui(
feature_dir,
wsis_to_process,
checkpoint_path,
output_dir,
slide_paths,
device,
default_slide_mpp,
)
Loading