-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
.tiff image format is not compatible with the interface #89
Comments
Issue reported again here : Closing the related issue as duplicated |
@CFIALeronB @rngadam I found a few Tiff javascript handling libraries. I can compile some options. Are there any guidelines on choosing? Does this become an ADR? Tiff js js Tiff decoder based on C LibTIFF Tiff pure JavaScript image decoder Geo Tiff Pure JS image decoder focusing on Tiff Maps UTIF Photopea Tiff decoder (and other advanced formats) from Photopea Should probably check if any of them work with Nachet Tiff files first. |
087.txt |
@ChromaticPanic surprised that since investigation determined Option A is less work and cleaner that we would proceed with Option B? Option A sounds good to me. It will probably mean keeping a mapping in the frontend between input tiff and their png for display. Could you provide sequence diagrams to explain the two options? https://github.blog/2022-02-14-include-diagrams-markdown-files-mermaid/ |
In terms of implementation there appears to be two options: Option A: (Likely Less Work and cleaner) Option B: (More Work) The reason Tiff is not currently displaying is that Image() does not handle it. So image.onload is not triggered when the image is Tiff. So this function would have to be reorganized to process Tiff a different way. const loadToCanvas = useCallback((): void => {
// loads the current image to the canvas and draws the bounding boxes and labels,
// should update whenever a change is made to the image cache or the score threshold and the selected label is changed
const image = new Image();
image.src = imageSrc;
const canvas: HTMLCanvasElement | null = canvasRef.current;
if (canvas === null) {
return;
}
const ctx: CanvasRenderingContext2D | null = canvas.getContext("2d");
if (ctx === null) {
return;
}
image.onload = () => {
canvas.width = image.width;
canvas.height = image.height;
ctx.drawImage(image, 0, 0);
imageCache.forEach((storedImage) => {
// find the current image in the image cache based on current index
if (storedImage.index === imageIndex && storedImage.annotated) {
storedImage.classifications.forEach((prediction, index) => {
// !storedImage.overlapping[index] REMOVE THIS TO SHOW ONLY 1 BB
if (
storedImage.scores[index] >= scoreThreshold / 100 &&
(prediction === selectedLabel || selectedLabel === "all")
) {
ctx.beginPath();
// draw label index
ctx.font = "bold 0.9vw Arial";
ctx.fillStyle = "black";
ctx.textAlign = "center";
Object.keys(labelOccurrences).forEach((key, labelIndex) => {
const scorePercentage = (
storedImage.scores[index] * 100
).toFixed(0);
// check to see if label is cut off by the canvas edge, if so, move it to the bottom of the bounding box
if (storedImage.boxes[index].topY <= 40) {
if (prediction === key) {
if (switchTable) {
ctx.fillText(
`[${labelIndex + 1}] - ${scorePercentage}%`,
((storedImage.boxes[index].bottomX as number) -
(storedImage.boxes[index].topX as number)) /
2 +
(storedImage.boxes[index].topX as number),
(storedImage.boxes[index].bottomY as number) + 23,
);
} else {
ctx.fillText(
`[${index + 1}]`,
((storedImage.boxes[index].bottomX as number) -
(storedImage.boxes[index].topX as number)) /
2 +
(storedImage.boxes[index].topX as number),
(storedImage.boxes[index].bottomY as number) + 23,
);
}
}
} else {
// draw label index and score percentage
if (prediction === key) {
if (switchTable) {
ctx.fillText(
`[${labelIndex + 1}] - ${scorePercentage}%`,
((storedImage.boxes[index].bottomX as number) -
(storedImage.boxes[index].topX as number)) /
2 +
(storedImage.boxes[index].topX as number),
storedImage.boxes[index].topY - 8,
);
} else {
// only draw table if switchTable is false (result component switch button)
ctx.fillText(
`[${index + 1}]`,
((storedImage.boxes[index].bottomX as number) -
(storedImage.boxes[index].topX as number)) /
2 +
(storedImage.boxes[index].topX as number),
storedImage.boxes[index].topY - 8,
);
}
}
}
});
// draw bounding box
ctx.lineWidth = 2;
ctx.strokeStyle = "red";
ctx.rect(
storedImage.boxes[index].topX,
storedImage.boxes[index].topY,
storedImage.boxes[index].bottomX -
storedImage.boxes[index].topX,
storedImage.boxes[index].bottomY -
storedImage.boxes[index].topY,
);
ctx.stroke();
ctx.closePath();
}
});
}
// capture label in bottom left
if (storedImage.index === imageIndex) {
storedImage.imageDims = [image.width, image.height];
ctx.beginPath();
ctx.font = "bold 0.9vw Arial";
ctx.textAlign = "left";
ctx.fillStyle = "#4ee44e";
ctx.fillText(`Capture ${storedImage.index}`, 10, canvas.height - 15);
ctx.stroke();
ctx.closePath();
}
});
};
}, [
imageCache,
imageIndex,
imageSrc,
labelOccurrences,
scoreThreshold,
selectedLabel,
switchTable,
]); From our meeting , I am currently working on Option B |
You actually bring up an important downside Option B would require refactoring that code section but has no additional rendering cost |
Option A.1 with single image useMemo Caching graph TD;
loadToCanvas((loadToCanvas))
Tiff[Tiff]
useMemo[useMemo single PNG cache \n new method]
NonTiff[Non-Tiff]
drawToCanvas[Draw image to canvas \nno refactor]
drawAnnotations[Draw boxes annotations \nno refactor]
None[_]
loadToCanvas -->Tiff
loadToCanvas -->NonTiff
Tiff --> useMemo
useMemo --> drawToCanvas
NonTiff --> None
None --> drawToCanvas
drawToCanvas --> drawAnnotations
Option A.2 using imageCache to cache all images graph TD;
uploadImage((uploadImage))
convertToPNG[convertToPNG\n new method]
imageCache((imageCache\n update add tiff field))
loadToCanvas((loadToCanvas))
useMemo[grab PNG from cache]
Tiff[Tiff]
NonTiff[Non-Tiff]
TiffA[Tiff]
NonTiffA[Non-Tiff]
drawToCanvas[Draw image to canvas \nno refactor]
drawAnnotations[Draw boxes annotations \nno refactor]
None[_]
NoneA[_]
TiffA --> convertToPNG
uploadImage -->TiffA
uploadImage -->NonTiffA
NonTiffA --> NoneA
NoneA --> imageCache
convertToPNG --> imageCache
imageCache --> useMemo
loadToCanvas -->Tiff
loadToCanvas -->NonTiff
Tiff --> useMemo
useMemo --> drawToCanvas
NonTiff --> None
None --> drawToCanvas
drawToCanvas --> drawAnnotations
Option B.1 Refactoring graph TD;
loadToCanvas((loadToCanvas))
Tiff[Tiff]
useMemo[Use Tiff lib]
NonTiff[Non-Tiff]
drawToCanvasA[Draw image to canvasA \n new method]
drawToCanvasB[Draw image to canvasB \n refactor current method]
drawAnnotations[Draw boxes annotations \n refactor current method]
None[Use Image lib]
loadToCanvas -->Tiff
loadToCanvas -->NonTiff
Tiff --> useMemo
useMemo --> drawToCanvasA
NonTiff --> None
None --> drawToCanvasB
drawToCanvasA --> drawAnnotations
drawToCanvasB --> drawAnnotations
Option B.2 No Refactor, Code duplication graph TD;
loadToCanvas((loadToCanvas))
Tiff[Tiff]
useMemo[Use Tiff lib]
NonTiff[Non-Tiff]
drawToCanvasA[Draw image to canvasA \n new method]
drawToCanvasB[Draw image to canvasB \n no change]
drawAnnotations[Draw boxes annotations \n no change]
drawAnnotationsA[Draw boxes annotations \n copy paste current method]
None[Use Image lib]
loadToCanvas -->Tiff
loadToCanvas -->NonTiff
Tiff --> useMemo
useMemo --> drawToCanvasA
NonTiff --> None
None --> drawToCanvasB
drawToCanvasA --> drawAnnotationsA
drawToCanvasB --> drawAnnotations
|
Some success. I actually tried Option A.1 first. From the current implementation it looks like Option B.1 is the cleaner way, since in all the browser side conversion examples for TIFF to PNG all of them rendered it in canvas first before converting to PNG. So converting to PNG first would lead to drawing the image to the canvas twice. |
* Issue #89 tiff file display Solution uses UTIF js from Photopea The advantage over other Tiff libraries is that it is actively maintained. Other libraries have not had any new commits for 5+ years. Photopea is a browser based photo editing software project so using this library may give other benefits in the future if we need to support other image formats. Modification to loadToCanvas function Updated interface to avoid "as Number" type casting Cleanup redundant code in bounding box drawing section created an if else fork to handle Tiff Image canvas drawing extracted common bounding box drawing code outside of image.onload New decodeTiff function uses UTIF js to decodeTiff files Fixed infinite render loop issues
when uploading a pre-generated .tiff image into the interface, the .tiff image cannot show. Interestingly, the user can still go proceed with the "classify" function and it will generate segmentation and classification results.
If transform the .tiff into .PNG file, everything works good.
A .tiff image is now allowed to be attached. Let me know if you need sample .tiff image. Send me the request on teams : liang.zhao@inspection.gc.ca
The text was updated successfully, but these errors were encountered: