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
137 changes: 136 additions & 1 deletion tiatoolbox/data/visualization/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,59 @@
left: 6pt;
top: 116pt;
}
.patient-id {
position: absolute;
top: 10px;
left: 50%;
transform: translateX(-50%);
z-index: 2000;
background: #fff;
padding: 10px 24px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.10);
font-weight: bold;
font-size: 1.2em;
color: #333;
border: 1px solid #eee;
}
.icon-btn {
background: #fff;
border: 1px solid #eee;
border-radius: 50%;
width: 44px;
height: 44px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5em;
color: #333;
box-shadow: 0 2px 8px rgba(0,0,0,0.10);
cursor: pointer;
transition: background 0.2s, color 0.2s;
outline: none;
position: relative;
}
.icon-btn:hover {
background: #f0f0f0;
color: #007bff;
}
.icon-btn:active {
background: #e0e0e0;
}
</style>
<title>{{ title }}</title>
</head>

<body>
{% if patient_id %}
<div class="patient-id">Patient ID: {{ patient_id }}</div>
{% endif %}
<div id="map" class="map" data-layers="{{ layers }}"></div>
<div id="toolbar" style="position:absolute;top:60px;left:50%;transform:translateX(-50%);z-index:2100;display:flex;gap:18px;align-items:center;">
<button id="measure-btn" class="icon-btn" title="Measure Distance"><i class="fas fa-ruler-horizontal"></i></button>
<button id="freehand-btn" class="icon-btn" title="Freehand Annotation"><i class="fas fa-pen"></i></button>
<button id="clear-btn" class="icon-btn" title="Clear Annotations"><i class="fas fa-trash"></i></button>
</div>
<script type="text/javascript">
layersData = JSON.parse(document.getElementById('map').dataset.layers);
var layers = layersData.map(function (layer) {
Expand All @@ -119,7 +166,6 @@
return new ol.layer.Tile({
title: layer.name,
source: source

});
});
var resolutions = layers[0].getSource().getTileGrid().getResolutions();
Expand Down Expand Up @@ -301,6 +347,95 @@
},
});
map.addControl(screenSpaceGraticuleToggle);
// --- Drawing Tools ---
var measureBtn = document.getElementById('measure-btn');
var freehandBtn = document.getElementById('freehand-btn');
var clearBtn = document.getElementById('clear-btn');
var drawLayer = new ol.layer.Vector({
source: new ol.source.Vector(),
style: function(feature) {
var styles = [
new ol.style.Style({
stroke: new ol.style.Stroke({
color: '#ff0000', width: 2
}),
image: new ol.style.Circle({
radius: 5,
fill: new ol.style.Fill({color: '#ff0000'})
}),
fill: new ol.style.Fill({color: 'rgba(255,0,0,0.1)'})
})
];
var geom = feature.getGeometry();
if (geom.getType() === 'LineString') {
var coords = geom.getCoordinates();
if (coords.length === 2) {
var dx = coords[1][0] - coords[0][0];
var dy = coords[1][1] - coords[0][1];
var pxDist = Math.sqrt(dx*dx + dy*dy);
var mpp = layersData[0].mpp; // microns per pixel
var mmDist = pxDist * mpp / 1000.0;
var label = mmDist.toFixed(2) + ' mm';
styles.push(new ol.style.Style({
text: new ol.style.Text({
text: label,
font: 'bold 16px Calibri,sans-serif',
fill: new ol.style.Fill({color: '#ff0000'}),
stroke: new ol.style.Stroke({color: '#fff', width: 3}),
offsetY: -10,
placement: 'line',
})
}));
}
}
return styles;
}
});
map.addLayer(drawLayer);
var measureDraw = null;
var freehandDraw = null;
function activateMeasure() {
if (measureDraw) map.removeInteraction(measureDraw);
measureDraw = new ol.interaction.Draw({
source: drawLayer.getSource(),
type: 'LineString',
maxPoints: 2
});
map.addInteraction(measureDraw);
measureDraw.on('drawend', function(evt) {
// allow multiple lines
setTimeout(function() {
map.removeInteraction(measureDraw);
measureDraw = null;
}, 100);
});
}
function activateFreehand() {
if (freehandDraw) map.removeInteraction(freehandDraw);
freehandDraw = new ol.interaction.Draw({
source: drawLayer.getSource(),
type: 'Polygon',
freehand: true
});
map.addInteraction(freehandDraw);
freehandDraw.on('drawend', function(evt) {
// allow multiple polygons
setTimeout(function() {
map.removeInteraction(freehandDraw);
freehandDraw = null;
}, 100);
});
}
measureBtn.addEventListener('click', function() {
activateMeasure();
});
freehandBtn.addEventListener('click', function() {
activateFreehand();
});
clearBtn.addEventListener('click', function() {
drawLayer.getSource().clear();
});
// --- End Drawing Tools ---
map.getView().fit(extent);
</script>
</body>
Expand Down
20 changes: 16 additions & 4 deletions tiatoolbox/visualization/bokeh_app/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,19 @@
.hidden {
display: none;
}

.patient-id {
position: absolute;
top: 10px;
left: 10px;
z-index: 1000;
background: #fff;
padding: 8px 16px;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
font-weight: bold;
font-size: 1.1em;
color: #333;
}
.popup {
position: absolute;
top: 30%;
Expand All @@ -45,7 +57,6 @@
justify-content: center;
align-items: center;
}

.popup-content {
background-color: white;
padding: 10px;
Expand All @@ -59,20 +70,21 @@
height: 75%;
overflow: auto;
}

.popup-header {
cursor: move;
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
}

</style>
{% endblock %}

<!-- goes in body -->
{% block contents %}
{% if patient_id %}
<div class="patient-id">Patient ID: {{ patient_id }}</div>
{% endif %}
<div id="popup" class="popup">
<div class="popup-content hidden">
<div class="popup-header">
Expand Down
3 changes: 3 additions & 0 deletions tiatoolbox/visualization/tileserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ def __init__( # noqa: PLR0915
title: str,
layers: dict[str, WSIReader | str] | list[WSIReader | str],
renderer: AnnotationRenderer | None = None,
patient_id: str = None, # <-- add patient_id argument
) -> None:
"""Initialize :class:`TileServer`."""
super().__init__(
Expand All @@ -87,6 +88,7 @@ def __init__( # noqa: PLR0915
),
)
self.title = title
self.patient_id = patient_id # <-- store patient_id
self.layers = {}
self.pyramids = {}
self.renderer = renderer
Expand Down Expand Up @@ -370,6 +372,7 @@ def index(self: TileServer) -> str:
"index.html",
title=self.title,
layers=json.dumps(layers),
patient_id=self.patient_id, # <-- pass patient_id to template
)

def change_prop(self: TileServer) -> str:
Expand Down
Loading