Skip to content

Commit

Permalink
Merge pull request #164 from DigitalSlideArchive/ui-redaction
Browse files Browse the repository at this point in the history
UI redaction
  • Loading branch information
marySalvi authored Dec 8, 2023
2 parents dc5de66 + fae0172 commit c1cc550
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 22 deletions.
17 changes: 16 additions & 1 deletion imagedephi/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import urllib.parse

from PIL import Image, UnidentifiedImageError
from fastapi import BackgroundTasks, FastAPI, Form, HTTPException, Request
from fastapi import BackgroundTasks, FastAPI, Form, HTTPException, Request, WebSocket
from fastapi.responses import HTMLResponse, PlainTextResponse, StreamingResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
Expand All @@ -20,6 +20,9 @@
import tifftools

from imagedephi.redact import iter_image_files, redact_images
from imagedephi.utils.progress_log import get_next_progress_message

# from imagedephi.redact.redact import output_file_counter
from imagedephi.utils.tiff import get_associated_image_svs, get_ifd_for_thumbnail, get_is_svs

if TYPE_CHECKING:
Expand Down Expand Up @@ -67,6 +70,7 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
shutdown_event = asyncio.Event()

app.mount("/assets", StaticFiles(directory="imagedephi/assets"), name="assets")
app.mount("/js", StaticFiles(directory="imagedephi/js"), name="js")


class DirectoryData:
Expand Down Expand Up @@ -268,3 +272,14 @@ def redact(
"redacted": True,
},
)


@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
while True:
message = get_next_progress_message()
if message is not None:
await websocket.send_text(str({message[0]}))
else:
await asyncio.sleep(0.001)
12 changes: 12 additions & 0 deletions imagedephi/js/redaction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
window.onload = (event) => {

const redactBanner = document.getElementById("redacting");
const redactButton = document.getElementById("dephi");
const redactForm = document.getElementById('redact')
redactForm.addEventListener("submit", () => {
redactButton.setAttribute("disabled", "true")
redactBanner.classList.remove("hidden")
location.reload()
})

}
2 changes: 1 addition & 1 deletion imagedephi/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,9 @@ def imagedephi(
@click.pass_obj
def run(obj: ImagedephiContext, input_path: Path, output_dir: Path, verbose, quiet, log_file):
"""Perform the redaction of images."""
redact_images(input_path, output_dir, obj.override_rule_set)
if verbose or quiet or log_file:
set_logging_config(verbose, quiet, log_file)
redact_images(input_path, output_dir, obj.override_rule_set)


@imagedephi.command
Expand Down
47 changes: 29 additions & 18 deletions imagedephi/redact/redact.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@
from collections.abc import Generator
import datetime
import importlib.resources
from io import StringIO
from pathlib import Path

import click
import tifftools
import tifftools.constants
import yaml

from imagedephi.rules import Ruleset
from imagedephi.utils.logger import logger
from imagedephi.utils.progress_log import push_progress

from .build_redaction_plan import FILE_EXTENSION_MAP, build_redaction_plan
from .svs import MalformedAperioFileError
Expand Down Expand Up @@ -78,24 +81,32 @@ def redact_images(
output_file_max = len(images_to_redact)
redact_dir = create_redact_dir(output_dir)
show_redaction_plan(input_path)
for image_file in images_to_redact:
logger.info(f"Redacting {image_file.name}...")
if image_file.suffix in FILE_EXTENSION_MAP:
redaction_plan = build_redaction_plan(image_file, base_rules, override_rules)
if not redaction_plan.is_comprehensive():
logger.info(f"Redaction could not be performed for {image_file.name}.")
redaction_plan.report_missing_rules()
else:
redaction_plan.execute_plan()
output_path = _get_output_path(
image_file,
redact_dir,
output_file_name_base,
output_file_counter,
output_file_max,
)
redaction_plan.save(output_path, overwrite)
output_file_counter += 1

file = StringIO()
with click.progressbar(
images_to_redact, label="Redacting Images", show_pos=True, file=file, show_percent=True
) as bar:
for image_file in bar:
push_progress(image_file.name, output_file_counter, output_file_max)

if image_file.suffix in FILE_EXTENSION_MAP:
redaction_plan = build_redaction_plan(image_file, base_rules, override_rules)
if not redaction_plan.is_comprehensive():
logger.info(f"Redaction could not be performed for {image_file.name}.")
redaction_plan.report_missing_rules()
else:
redaction_plan.execute_plan()
output_path = _get_output_path(
image_file,
redact_dir,
output_file_name_base,
output_file_counter,
output_file_max,
)
redaction_plan.save(output_path, overwrite)
if output_file_counter == output_file_max:
click.echo("Redactions completed")
output_file_counter += 1


def show_redaction_plan(input_path: Path, override_rules: Ruleset | None = None) -> None:
Expand Down
26 changes: 24 additions & 2 deletions imagedephi/templates/HomePage.html.j2
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,12 @@
},
}
</script>
<script type="text/javascript" src="js/redaction.js"></script>
</head>
<body class="bg-base-300 min-h-screen">
<div class="flex">
<!-- djlint:off-->
<form method="post" action="{{ url_for('redact') }}"><!-- djlint:on -->
<form method="post" action="{{ url_for('redact') }}" id="redact"><!-- djlint:on -->
<input type="hidden"
value="{{ input_directory_data.directory }}"
name="input_directory" />
Expand All @@ -96,7 +97,7 @@
{% include "InputStep.html.j2" %}
{% include "OutputStep.html.j2" %}
{% include "RulesetStep.html.j2" %}
<button type="submit" class="btn btn-wide bg-accent m-auto" {{ 'disabled' if redacted}}>De-phi
<button type="submit" class="btn btn-wide bg-accent m-auto" {{ 'disabled' if redacted}} id="dephi">De-phi
images
</button>
</div>
Expand All @@ -108,6 +109,27 @@
{% include "OutputSelectorWidget.html.j2" %}
</form>
{# TODO make into a separate component #}
<div class="px-8 py-4 hidden" id="redacting">
<div class="hero bg-base-200 m-auto">
<div class="hero-content text-center">
<div>
<h1 class="text-5xl font-bold">Redacting Images</h1>
</div>
<div>
Redacting image <span id='progress'></span> out of {{ input_directory_data.child_images|length }}
<script>
var ws = new WebSocket("{{ url_for('websocket_endpoint') }}");
var progress = document.getElementById('progress');
ws.onmessage = function (event) {
progress.textContent = event.data.slice(1, event.data.length - 1)
};
</script>
</div>
</div>
</div>
</div>
{% if redacted %}
<div class="px-8 py-4">
<div class="hero bg-base-200">
Expand Down
25 changes: 25 additions & 0 deletions imagedephi/utils/progress_log.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# import logging
# import logging.handlers
import queue

# _progress_logger = logging.getLogger('progress')
# _progress_queue: queue.Queue[logging.LogRecord] = queue.Queue(-1)
_progress_queue: queue.Queue[tuple] = queue.Queue(-1)

# _queue_handler = logging.handlers.QueueHandler(_progress_queue)
# _progress_logger.addHandler(_queue_handler)


def push_progress(file_name: str, count: int, max: int) -> None:
# _progress_logger.info("Redacting %s. Image %d of %d", file_name, count, max)
_progress_queue.put_nowait((count, max))


def get_next_progress_message() -> tuple | None:
try:
record = _progress_queue.get_nowait()
except queue.Empty:
return None
else:
# return record.message
return record
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ dependencies = [
"hypercorn",
"pyyaml",
"Pillow",
"websockets",
]
dynamic = ["version"]

Expand Down

0 comments on commit c1cc550

Please sign in to comment.