Skip to content

Commit

Permalink
Optimize pagination
Browse files Browse the repository at this point in the history
Add update flag for show_redaction_plan allowing the use of an existing plan instead of always rebuilding
  • Loading branch information
marySalvi committed Apr 4, 2024
1 parent f3f1873 commit 9689629
Show file tree
Hide file tree
Showing 9 changed files with 53 additions and 39 deletions.
3 changes: 2 additions & 1 deletion client/src/api/rest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ export async function getRedactionPlan(
path: string,
limit?: number,
offset?: number,
update?: boolean,
) {
const response = await fetch(
`${basePath}/redaction_plan?input_directory=${path}&limit=${limit}&offset=${offset}`,
`${basePath}/redaction_plan?input_directory=${path}&limit=${limit}&offset=${offset}&update=${update}`,
{
method: "GET",
mode: "cors",
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/FileBrowser.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const updateDirectories = async (currentDirectory?: string) => {
updateDirectories();
const updateImageData = async (directory: string) => {
imageRedactionPlan.value = await getRedactionPlan(directory, 10, 0);
imageRedactionPlan.value = await getRedactionPlan(directory, 10, 0, true);
};
const closeModal = () => {
Expand Down
3 changes: 3 additions & 0 deletions client/src/components/ImageList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const updateImageList = async () => {
selectedDirectories.value.inputDirectory,
rows.value,
offset,
false,
);
};
</script>
Expand Down Expand Up @@ -62,6 +63,7 @@ const updateImageList = async () => {
<div v-if="totalPages && totalPages > 1" class="join flex justify-center">
<button
class="join-item btn btn-base-100 btn-xs"
:disabled="page === 1"
@click="page--, updateImageList()"
>
<i class="ri-arrow-left-s-line"></i>
Expand All @@ -75,6 +77,7 @@ const updateImageList = async () => {
{{ totalPages }}
<button
class="join-item btn btn-base-100 btn-xs"
:disabled="page === totalPages"
@click="page++, updateImageList()"
>
<i class="ri-arrow-right-s-line"></i>
Expand Down
3 changes: 2 additions & 1 deletion imagedephi/gui/api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,13 @@ def get_redaction_plan(
input_directory: str = ("/"), # noqa: B008
limit: int = 10,
offset: int = 0,
update: bool = True,
):
input_path = Path(input_directory)
if not input_path.is_dir():
raise HTTPException(status_code=404, detail="Input directory not found")

return show_redaction_plan(input_path, limit=limit, offset=offset)._asdict()
return show_redaction_plan(input_path, limit=limit, offset=offset, update=update)._asdict()


@router.post("/redact/")
Expand Down
12 changes: 7 additions & 5 deletions imagedephi/redact/dicom.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,9 @@ def determine_redaction_operation(
return rule.action
return "delete"

def report_plan(self) -> dict[str, dict[str, str]]:
def report_plan(self) -> dict[str, dict[str | int, str | int]]:
logger.info("DICOM Metadata Redaction Plan\n")
report = {}
report: dict[str, dict[str | int, str | int]] = {}
report[self.image_path.name] = {}
for element, _ in DicomRedactionPlan._iter_dicom_elements(self.dicom_data):
rule = self.metadata_redaction_steps.get(element.tag, None)
Expand Down Expand Up @@ -175,16 +175,18 @@ def execute_plan(self) -> None:
def is_comprehensive(self) -> bool:
return not self.no_match_tags

def report_missing_rules(self, report) -> None:
def report_missing_rules(self, report=None) -> None:
if self.is_comprehensive():
logger.info("The redaction plan is comprehensive.")
else:
logger.error("The following tags could not be redacted given the current set of rules.")
report[self.image_path.name]["missing_tags"] = []
if report is not None:
report[self.image_path.name]["missing_tags"] = []

for tag in self.no_match_tags:
logger.error(f"Missing tag (dicom): {tag} - {keyword_for_tag(tag)}")
report[self.image_path.name]["missing_tags"].append({tag: keyword_for_tag(tag)})
if report is not None:
report[self.image_path.name]["missing_tags"].append({tag: keyword_for_tag(tag)})

def save(self, output_path: Path, overwrite: bool) -> None:
if output_path.exists():
Expand Down
45 changes: 26 additions & 19 deletions imagedephi/redact/redact.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,30 +135,37 @@ def show_redaction_plan(
recursive=False,
limit: int | None = None,
offset: int | None = None,
update: bool = True,
) -> NamedTuple:
image_paths = iter_image_files(input_path, recursive) if input_path.is_dir() else [input_path]
base_rules = get_base_rules()
report = {}
for image_path in image_paths:
try:
redaction_plan = build_redaction_plan(image_path, base_rules, override_rules)
except tifftools.TifftoolsError:
logger.error(f"Could not open {image_path.name} as a tiff.")
continue
except MalformedAperioFileError:
logger.error(f"{image_path.name} could not be processed as a valid Aperio file.")
continue
except UnsupportedFileTypeError as e:
logger.error(f"{image_path.name} could not be processed. {e.args[0]}")
continue
logger.info(f"Redaction plan for {image_path.name}")
report.update(redaction_plan.report_plan())
total = len(report)

if update:
global redaction_plan_report
redaction_plan_report = {} # type: ignore
for image_path in image_paths:
try:
redaction_plan = build_redaction_plan(image_path, base_rules, override_rules)
except tifftools.TifftoolsError:
logger.error(f"Could not open {image_path.name} as a tiff.")
continue
except MalformedAperioFileError:
logger.error(f"{image_path.name} could not be processed as a valid Aperio file.")
continue
except UnsupportedFileTypeError as e:
logger.error(f"{image_path.name} could not be processed. {e.args[0]}")
continue
logger.info(f"Redaction plan for {image_path.name}")
redaction_plan_report.update(redaction_plan.report_plan()) # type: ignore
total = len(redaction_plan_report) # type: ignore
sorted_dict = OrderedDict(
sorted(report.items(), key=lambda item: "missing_tags" not in item[1])
sorted(
redaction_plan_report.items(), # type: ignore
key=lambda item: "missing_tags" not in item[1],
)
)
if limit is not None and offset is not None:
sorted_dict = dict(list(sorted_dict.items())[offset : limit + offset])
images_plan = namedtuple("RedactionPlan", ["data", "total"])
sorted_dict = OrderedDict(list(sorted_dict.items())[offset : limit + offset])
images_plan = namedtuple("images_plan", ["data", "total"])

return images_plan(sorted_dict, total)
4 changes: 2 additions & 2 deletions imagedephi/redact/redaction_plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class RedactionPlan:
file_format: FileFormat

@abc.abstractmethod
def report_plan(self) -> dict[str, dict[str, str]]: ...
def report_plan(self) -> dict[str, dict[str | int, str | int]]: ...

@abc.abstractmethod
def execute_plan(self) -> None: ...
Expand All @@ -19,7 +19,7 @@ def is_comprehensive(self) -> bool:
...

@abc.abstractmethod
def report_missing_rules(self) -> None: ...
def report_missing_rules(self, report=None) -> None: ...

@abc.abstractmethod
def save(self, output_path: Path, overwrite: bool) -> None: ...
16 changes: 8 additions & 8 deletions imagedephi/redact/svs.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ def apply(self, rule: ConcreteMetadataRule, data: SvsDescription | IFD) -> None:
def is_comprehensive(self) -> bool:
return super().is_comprehensive() and not self.no_match_description_keys

def report_missing_rules(self, report) -> None:
def report_missing_rules(self, report=None) -> None:
if self.is_comprehensive():
logger.info("The redaction plan is comprehensive.")
else:
Expand All @@ -180,13 +180,14 @@ def report_missing_rules(self, report) -> None:
)
for key in self.no_match_description_keys:
logger.error(f"Missing key (Aperio ImageDescription): {key}")
report[self.image_path.name]["missing_keys"].append(key)
if report is not None:
report[self.image_path.name]["missing_keys"].append(key)

def report_plan(self) -> dict[str, dict[str, str]]:
def report_plan(self) -> dict[str, dict[str | int, str | int]]:
logger.info("Aperio (.svs) Metadata Redaction Plan\n")
offset = -1
ifd_count = 0
report = {}
report: dict[str, dict[str | int, str | int]] = {}
report[self.image_path.name] = {}
for tag, ifd in self._iter_tiff_tag_entries(self.tiff_info["ifds"]):
if ifd["offset"] != offset:
Expand Down Expand Up @@ -218,10 +219,9 @@ def report_plan(self) -> dict[str, dict[str, str]]:
f"{match_counts[key]} image(s) match rule:"
f" {key} - {self.rules.associated_images[key].action}"
)
report[self.image_path.name] = (
f"{match_counts[key]} image(s) match rule:"
f" {key} - {self.rules.associated_images[key].action}"
)
report[self.image_path.name][match_counts[key]] = self.rules.associated_images[
key
].action

return report

Expand Down
4 changes: 2 additions & 2 deletions imagedephi/redact/tiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,11 +210,11 @@ def report_missing_rules(self, report=None) -> None:
if report is not None:
report[self.image_path.name]["missing_tags"].append({tag.value: tag.name})

def report_plan(self) -> dict[str, dict[str, str]]:
def report_plan(self) -> dict[str, dict[str | int, str | int]]:
logger.info("Tiff Metadata Redaction Plan\n")
offset = -1
ifd_count = 0
report = {}
report: dict[str, dict[str | int, str | int]] = {}
report[self.image_path.name] = {}

for tag, ifd in self._iter_tiff_tag_entries(self.tiff_info["ifds"]):
Expand Down

0 comments on commit 9689629

Please sign in to comment.