Skip to content

Commit cb94cd5

Browse files
Merge pull request #5 from Imaging-Server-Kit/hidableui
Hidableui
2 parents 7132691 + bc9ef09 commit cb94cd5

8 files changed

Lines changed: 87 additions & 8 deletions

File tree

‎src/imaging_server_kit/core/algorithm.py‎

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@ def __init__(
241241
project_url: str = "https://github.com/Imaging-Server-Kit/imaging-server-kit",
242242
metadata_file: str = "metadata.yaml",
243243
samples: Optional[List[Dict[str, Any]]] = None,
244+
tileable: bool = True,
244245
):
245246
# Initialize mutables
246247
if tags is None:
@@ -261,6 +262,9 @@ def __init__(
261262

262263
# Samples
263264
self.samples = samples
265+
266+
# Tileability
267+
self.tileable = tileable
264268

265269
# Resolve the Pydantic parameters model
266270
self.parameters_model = _parse_pydantic_params_schema(
@@ -277,7 +281,7 @@ def __init__(
277281
@property
278282
def algorithms(self) -> Iterable[str]:
279283
return self._algorithms
280-
284+
281285
@algorithms.setter
282286
def algorithms(self, algorithms: Iterable[str]):
283287
self._algorithms = algorithms
@@ -340,14 +344,17 @@ def get_sample(self, algorithm=None, idx: int = 0) -> Optional[Results]:
340344
sample_results = Results()
341345
for param_name, param_value in resolved_params.items():
342346
kind = algo_params_defs.get(param_name).get("param_type")
343-
if (kind == "image") & (not isinstance(param_value, np.ndarray)):
347+
if (kind in ["image", "mask"]) & (not isinstance(param_value, np.ndarray)):
344348
param_value = skimage.io.imread(param_value)
345349
sample_results.create(kind, param_value, param_name)
346350
return sample_results
347351

348352
def get_n_samples(self, algorithm=None):
349353
return len(self.samples)
350354

355+
def is_tileable(self, algorithm=None):
356+
return self.tileable
357+
351358
@validate_algorithm
352359
def get_signature_params(self, algorithm=None) -> List[str]:
353360
"""List parameter names of the algo run function."""
@@ -413,7 +420,8 @@ def algorithm(
413420
project_url: str = "https://github.com/Imaging-Server-Kit/imaging-server-kit",
414421
metadata_file: str = "metadata.yaml",
415422
samples: Optional[List[Dict[str, Any]]] = None,
416-
):
423+
tileable: bool = True,
424+
) -> Algorithm:
417425
"""Convert a Python function into an algorithm instance (sk.Algorithm).
418426
419427
Parameters
@@ -426,7 +434,8 @@ def algorithm(
426434
project_url: A link to a related, or the original project (gets displayed on the algo doc page).
427435
metadata_file: A path to a metadata.yaml file with algorithm metadata.
428436
samples: A list of sample parameters for the algorithm, each represented as a dictionary mapping parameter_name to example_value. Sample images can be a Numpy array, a URL, or a local path to a file readable by `skimag.io.imread`.
429-
437+
tileable: Whether to allow running the algorithm tile-by-tile. Can be set to False to explicitely disable that functionality.
438+
430439
Returns
431440
-------
432441
An algorithm instance (sk.Algorithm).
@@ -442,6 +451,7 @@ def _decorate(run_aglorithm_func: Callable):
442451
project_url=project_url,
443452
metadata_file=metadata_file,
444453
samples=samples,
454+
tileable=tileable,
445455
)
446456

447457
if func is not None and callable(func):

‎src/imaging_server_kit/core/app.py‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,17 @@ def get_n_samples(algorithm_name: str):
252252
algorithm = find_algorithm(algorithm_name, self.algorithms_dict)
253253
n_samples = algorithm.get_n_samples(algorithm=algorithm_name)
254254
return {"n_samples": n_samples}
255+
256+
@self.app.get(
257+
"/{algorithm_name}/tileable",
258+
response_model=dict,
259+
summary="Whether the algorithm is tileable.",
260+
tags=["algorithm"],
261+
)
262+
def is_tileable(algorithm_name: str):
263+
algorithm = find_algorithm(algorithm_name, self.algorithms_dict)
264+
tileable = algorithm.is_tileable(algorithm=algorithm_name)
265+
return {"tileable": tileable}
255266

256267
@self.app.get("/{algorithm_name}/signature", tags=["algorithm"])
257268
def get_signature(algorithm_name: str):

‎src/imaging_server_kit/core/client.py‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,13 @@ def get_n_samples(self, algorithm=None) -> int:
103103
json_response = self._access_algo_get_endpoint(endpoint)
104104
n_samples = json_response.get("n_samples")
105105
return n_samples
106+
107+
@validate_algorithm
108+
def is_tileable(self, algorithm=None) -> bool:
109+
endpoint = f"{self.server_url}/{algorithm}/tileable"
110+
json_response = self._access_algo_get_endpoint(endpoint)
111+
is_tileable = json_response.get("tileable")
112+
return is_tileable
106113

107114
@validate_algorithm
108115
def get_signature_params(self, algorithm: str) -> List[str]:

‎src/imaging_server_kit/core/errors.py‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import importlib.util
2+
from typing import Optional
23

34
class AlgorithmNotFoundError(Exception):
45
"""Exception raised when a specified algorithm is not found."""
@@ -83,10 +84,12 @@ class AlgorithmRuntimeError(Exception):
8384
def __init__(
8485
self,
8586
algorithm: str,
86-
error: Exception,
87+
error: Optional[Exception] = None,
8788
message="Algorithm did not run successfully. ",
8889
):
89-
self.message = message + f"{algorithm=}, Error: {error}"
90+
self.message = message + f"{algorithm=}"
91+
if error is not None:
92+
self.message = self.message + ", Error: {error}"
9093
super().__init__(self.message)
9194

9295

‎src/imaging_server_kit/core/multialgo.py‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def algorithms_dict(self) -> Dict[str, Algorithm]:
2323
@property
2424
def algorithms(self) -> List[str]:
2525
return list(self.algorithms_dict.keys())
26-
26+
2727
@validate_algorithm
2828
def info(self, algorithm: str):
2929
return self.algorithms_dict[algorithm].info(algorithm)
@@ -39,6 +39,10 @@ def get_sample(self, algorithm: str, idx: int = 0) -> Optional[Results]:
3939
@validate_algorithm
4040
def get_n_samples(self, algorithm: str) -> int:
4141
return self.algorithms_dict[algorithm].get_n_samples(algorithm)
42+
43+
@validate_algorithm
44+
def is_tileable(self, algorithm: str) -> bool:
45+
return self.algorithms_dict[algorithm].is_tileable(algorithm)
4246

4347
@validate_algorithm
4448
def get_signature_params(self, algorithm: str) -> List[str]:

‎src/imaging_server_kit/core/runner.py‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ def get_sample(self, algorithm: str, idx: int = 0) -> LayerStackBase: ...
8383

8484
@abstractmethod
8585
def get_n_samples(self, algorithm: str) -> int: ...
86+
87+
@abstractmethod
88+
def is_tileable(self, algorithm: str) -> bool: ...
8689

8790
@abstractmethod
8891
def get_signature_params(self, algorithm: str) -> List[str]: ...
@@ -175,6 +178,11 @@ def run(
175178
stream = self._is_stream(algorithm)
176179

177180
if tiled:
181+
if self.tileable is False:
182+
raise AlgorithmRuntimeError(
183+
algorithm,
184+
message="Algorithm cannot be run in tiled mode.",
185+
)
178186
if stream:
179187
raise AlgorithmStreamError(
180188
algorithm,

‎src/imaging_server_kit/demo/examples.py‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,3 +517,39 @@ def project(image, method, axis: str):
517517
name=f"Projection",
518518
meta={"contrast_limits": [proj.min(), proj.max()], "colormap": "viridis"},
519519
)
520+
521+
522+
## Conway's game of life (inspired by: https://www.geeksforgeeks.org/dsa/conways-game-life-python-implementation/)
523+
@sk.algorithm(
524+
name="Game of Life",
525+
description="Conway's game of life.",
526+
project_url="https://www.geeksforgeeks.org/dsa/conways-game-life-python-implementation/",
527+
)
528+
def conway_algo(max_iter=200, delay=0.1):
529+
min_val = 0
530+
max_val = 255
531+
532+
N=100 # Size of the grid
533+
534+
def update(grid: np.ndarray, N: int) -> np.ndarray:
535+
newGrid = grid.copy()
536+
for i in range(N):
537+
for j in range(N):
538+
total = int((grid[i, (j-1)%N] + grid[i, (j+1)%N] +
539+
grid[(i-1)%N, j] + grid[(i+1)%N, j] +
540+
grid[(i-1)%N, (j-1)%N] + grid[(i-1)%N, (j+1)%N] +
541+
grid[(i+1)%N, (j-1)%N] + grid[(i+1)%N, (j+1)%N]))
542+
total = total / max_val
543+
if grid[i, j] == max_val:
544+
if (total < 2) or (total > 3):
545+
newGrid[i, j] = min_val
546+
else:
547+
if total == 3:
548+
newGrid[i, j] = max_val
549+
return newGrid
550+
551+
grid = np.random.choice([min_val, max_val], N*N, p=[0.5, 0.5]).reshape(N, N)
552+
for k in range(max_iter):
553+
grid = update(grid, N)
554+
time.sleep(delay)
555+
yield sk.Mask(grid), f"Iterations: {k} / {max_iter}"

‎src/imaging_server_kit/types/_mask.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ def features2instance_mask_3d(features, image_shape):
150150
_, ry, rx = image_shape
151151
for feature in features:
152152
feature_xy_coordinates = np.array(feature["geometry"]["coordinates"])
153-
feature_coordinates = feature_coordinates[0, :, :] # Remove an extra dimension
153+
feature_xy_coordinates = feature_xy_coordinates[0, :, :] # Remove an extra dimension
154154
feature_xy_coordinates = feature_xy_coordinates[:, ::-1] # Invert XY
155155
feature_mask = polygon2mask((ry, rx), feature_xy_coordinates)
156156
feature_z_idx = feature["properties"]["z_idx"]

0 commit comments

Comments
 (0)