Skip to content

Commit 57ab804

Browse files
committed
dev(process): now possible to preview the effect of a separation mask without saving it.
1 parent 3dc6eff commit 57ab804

3 files changed

Lines changed: 81 additions & 9 deletions

File tree

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ pymongo==4.15.1
1515
# pillow==11.3.0 # PIL, in ZooProcess-lib
1616
pyjwt==2.10.1 # For JWT token validation
1717
sqlalchemy==2.0.43 # ORM for database operations
18-
ZooProcess-lib @ https://github.com/ecotaxa/ZooProcess-lib/archive/refs/tags/v0.14.0.tar.gz
18+
ZooProcess-lib @ https://github.com/ecotaxa/ZooProcess-lib/archive/refs/tags/v0.15.0.tar.gz
1919
python-dotenv==1.1.1
2020

2121
# CLI dependencies

src/img_proc/drawing.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,17 @@
88
from providers.ML_multiple_separator import RGB_RED_COLOR
99

1010

11-
def apply_matrix_onto(image: np.ndarray, matrix: np.ndarray) -> np.ndarray:
11+
def apply_matrix_onto(image: np.ndarray, matrix: np.ndarray, keep_grey: bool = False) -> np.ndarray:
1212
"""
1313
Apply a binary matrix onto an image by drawing red points where the matrix has True values.
1414
1515
Args:
1616
image: A numpy array representing the image (RGB format)
1717
matrix: A 2D numpy array containing binary values (True/False or 1/0)
18+
keep_grey: A boolean indicating whether to keep the greyscale image
1819
1920
Returns:
20-
A numpy array representing the image with red points drawn on it
21+
A numpy array representing the image with red/white points drawn from matrix on it
2122
2223
Raises:
2324
ValueError: If the dimensions of the image and matrix don't match
@@ -32,10 +33,10 @@ def apply_matrix_onto(image: np.ndarray, matrix: np.ndarray) -> np.ndarray:
3233
result_image = image.copy()
3334

3435
# If the image is grayscale, convert it to BGR
35-
if len(result_image.shape) == 2:
36+
if (not keep_grey) and len(result_image.shape) == 2:
3637
result_image = cv2.cvtColor(result_image, cv2.COLOR_GRAY2RGB)
3738

38-
# Draw red points at matrix "True" coordinates
39-
result_image[np.where(matrix)] = RGB_RED_COLOR
39+
# Draw red/white points at matrix "True" coordinates
40+
result_image[np.where(matrix)] = RGB_RED_COLOR if not keep_grey else 255
4041

4142
return result_image

src/routers/vignettes.py

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ async def get_vignettes(
134134
# Segmenter
135135
sep_img_path = multiples_to_check_dir / a_vignette
136136
assert sep_img_path.is_file()
137-
_, rois = segment_mask(processor, sep_img_path)
137+
_, rois = segment_mask_file(processor, sep_img_path)
138138
segmenter_output = []
139139
for i in range(len(rois)):
140140
seg_name = (
@@ -211,7 +211,7 @@ async def get_vignette_image(
211211
multiple_name = img_path.rsplit("/", 1)[1]
212212
sep_img_path = multiples_to_check_dir / multiple_name
213213
assert sep_img_path.is_file(), f"Not a file: {sep_img_path}"
214-
sep_img, rois = segment_mask(processor, sep_img_path)
214+
sep_img, rois = segment_mask_file(processor, sep_img_path)
215215
vignette_in_vignette = processor.extractor.extract_image_at_ROI(
216216
sep_img, rois[int(seg_num)], erasing_background=True
217217
)
@@ -352,6 +352,73 @@ async def update_a_vignette_mask(
352352
}
353353

354354

355+
DRAWING_FEATURES = {"object_bx", "object_by", "object_width", "object_height",
356+
"object_x", "object_y", "object_major", "object_minor", "object_angle"}
357+
358+
359+
@router.post("/vignette_mask_maybe/{project_hash}/{sample_hash}/{subsample_hash}/{img_path}")
360+
async def simulate_a_vignette_mask(
361+
project_hash: str,
362+
sample_hash: str,
363+
subsample_hash: str,
364+
img_path: str,
365+
file: UploadFile = File(...),
366+
db: Session = Depends(get_db),
367+
) -> dict:
368+
"""Update _virtually_ a vignette using the drawn mask
369+
370+
Args:
371+
project_hash (str): The ID of the project
372+
sample_hash (str): The hash of the sample
373+
subsample_hash (str): The hash of the subsample
374+
img_path (str): The path to the original image
375+
file (UploadFile): The uploaded file containing the mask
376+
db (Session): Database session
377+
378+
Returns:
379+
dict: Status of the simulation operation
380+
"""
381+
logger.info(
382+
f"simulate_a_vignette_mask: {project_hash}/{sample_hash}/{subsample_hash}/{img_path}"
383+
)
384+
# Validate the project, sample, and subsample hashes
385+
zoo_drive, zoo_project, sample_name, subsample_name = validate_path_components(
386+
db, project_hash, sample_hash, subsample_hash
387+
)
388+
img_path = img_path.replace(API_PATH_SEP, "/")
389+
assert img_path.startswith(
390+
V10_THUMBS_SUBDIR
391+
) # Convention with UI, ref is original image in cut directory
392+
assert img_path.endswith(".png")
393+
_, img_name = img_path.rsplit("/", 1)
394+
processor, thumbs_dir, multiples_to_check_dir, meta_dir = processing_context(
395+
zoo_project, sample_name, subsample_name
396+
)
397+
# Read the content of the uploaded file
398+
content = await file.read()
399+
# Validate that the content is a gzip or zip-encoded matrix
400+
if not is_valid_compressed_matrix(content):
401+
raise_422("Invalid compressed matrix")
402+
assert False
403+
mask = load_matrix_from_compressed(content)
404+
scan_path = thumbs_dir / img_name
405+
scan_img = load_image(scan_path, cv2.IMREAD_GRAYSCALE)
406+
check_mask_sanity(scan_img, mask, subsample_name, img_name[:-4], meta_dir)
407+
masked_img = apply_matrix_onto(scan_img, mask, True)
408+
# Segment the masked image
409+
assert processor.config is not None
410+
rois, _ = processor.segmenter.find_ROIs_in_cropped_image(
411+
masked_img, processor.config.resolution
412+
)
413+
calcs = processor.calculator.ecotaxa_measures_list_from_roi_list(masked_img, processor.config.resolution, rois,
414+
DRAWING_FEATURES)
415+
return {
416+
"status": "success",
417+
"rois": calcs,
418+
"image": str(img_name),
419+
}
420+
421+
355422
def all_pngs_in_dir(a_dir: Path) -> List[str]:
356423
ret = []
357424
if a_dir is None:
@@ -365,10 +432,14 @@ def all_pngs_in_dir(a_dir: Path) -> List[str]:
365432
return ret
366433

367434

368-
def segment_mask(
435+
def segment_mask_file(
369436
processor: Processor, sep_img_path: Path
370437
) -> Tuple[np.ndarray, List[ROI]]:
371438
sep_img = load_image(sep_img_path, cv2.IMREAD_COLOR_BGR)
439+
return segment_mask_image(processor, sep_img)
440+
441+
442+
def segment_mask_image(processor: Processor, sep_img: np.ndarray) -> Tuple[np.ndarray, List[ROI]]:
372443
sep_img2 = cv2.extractChannel(sep_img, 1)
373444
sep_img2[sep_img[:, :, 2] == BGR_RED_COLOR[2]] = 255
374445
assert processor.config is not None

0 commit comments

Comments
 (0)