diff --git a/docs/developers/python/classical/index.mdx b/docs/developers/python/classical/index.mdx index 2411db94..ce03b109 100644 --- a/docs/developers/python/classical/index.mdx +++ b/docs/developers/python/classical/index.mdx @@ -10,7 +10,7 @@ sidebar_label: Classic Style Lets's create an Arkitekt App, as it is the connection layer between your code and the arkitet server. If you don't know what we mean by an App, check out the [App](/docs/design/terminology/app) section of the terminology page. ```python -from arkitekt import easy +from arkitekt_next import easy app = easy("my-app-name") @@ -28,7 +28,7 @@ setup: pointing the app to an arkitekt instance. This is by design. Your code should not be aware of the backend it is connecting to, as this allows easy installation of your code anywhere. Arkitekt uses the fakt service to discover the backend, and will retrieve its configuration from the backend, before connecting to it, on the first call within the context (a useful feature for system admins to configure apps after deployment). Of course you can opt out of this behaviours by passing an explicity url of the arkitekt server or by changing the fakts attributes before entering the context. ```python - from arkitekt import easy + from arkitekt_next import easy from fakts.grants.remote import AnyOtherGrantType app = easy("my-app-name") @@ -51,29 +51,29 @@ setup: 4. The context manager also sets some context specific variables (contextvars). This means we can inject its dependencys within this context automatically sing the governing app context. This allows for more functional code. For example, you can use simply call - `from_xarray` to save an xarray on the mikro instance of the governing app context, without + `from_array_like` to save an xarray on the mikro instance of the governing app context, without explicitly passing the apps mikro server to the function. You can always opt out of this behaviour by specifically passing the desired context to the function. ```python - from arkitekt import easy - from mikro.api.schema import from_xarray + from arkitekt_next import easy + from mikro_next.api.schema import from_array_like import xarray as xr app = easy("my-app-name") with app: - image = from_xarray(xr.DataArray((100,100,1), dims=["x", "y", "z"])) # stores the xarray on the mikro instance of the governing app context + image = from_array_like(xr.DataArray((100,100,1), dims=["x", "y", "z"])) # stores the xarray on the mikro instance of the governing app context # or - image = from_xarray(xr.DataArray((100,100,1), dims=["x", "y", "z"]), rath=app.mikro.rath) # stores the xarray on the specified mikro instance + image = from_array_like(xr.DataArray((100,100,1), dims=["x", "y", "z"]), rath=app.mikro.rath) # stores the xarray on the specified mikro instance ``` :::note This behaviour allows for more functional and concise code but can also lead to unexpected behaviour. For example, if you have multiple app contexts running in parallel, the - `from_xarray` function will use the app context that is currently active. This can lead to + `from_array_like` function will use the app context that is currently active. This can lead to unexpected behaviour if you are not aware of this. ::: @@ -86,10 +86,10 @@ setup: sync code ```python - from arkitekt import Arkitekt + from arkitekt_next import easy import asyncio - app = Arkitekt() + app = easy("my-app-name") async def main(): async with app: @@ -110,16 +110,16 @@ setup: of the context in which your code is running. ```python - from arkitekt import easy - from mikro.api.schema import from_xarray + from arkitekt_next import easy + from mikro_next.api.schema import from_array_like - app = Arkitekt() + app = easy("my-app-name") with app: # do stuff with arkitekt - from_xarray(xr.DataArray((100,100,1), dims=["x", "y", "z"]), name="my-image") + from_array_like(xr.DataArray((100,100,1), dims=["x", "y", "z"]), name="my-image") - from_xarray(xr.DataArray((100,100,1), dims=["x", "y", "z"]), name="my-failed-image") # will fail + from_array_like(xr.DataArray((100,100,1), dims=["x", "y", "z"]), name="my-failed-image") # will fail ``` # Lets get started diff --git a/docs/developers/python/classical/read-write.md b/docs/developers/python/classical/read-write.md index 11fb8806..2f7b805b 100644 --- a/docs/developers/python/classical/read-write.md +++ b/docs/developers/python/classical/read-write.md @@ -30,8 +30,8 @@ Lets start with the simplest example. Imaging you have a numpy array that you wa Lets first look at what code we need to upload the data to Arkitekt. ```python -from arkitekt import easy -from mikro.api.schema import from_xarray +from arkitekt_next import easy +from mikro_next.api.schema import from_array_like import xarray as xr import numpy as np @@ -41,7 +41,7 @@ i = np.random.rand(100,100,1) image_data = xr.DataArray(i, dims=["x", "y", "c"]) with app: - image = from_xarray(image_data, name="Random image") # stores the xarray on the mikro instance + image = from_array_like(image_data, name="Random image") # stores the array on the mikro instance print(image.data) ``` @@ -71,7 +71,7 @@ The python API heavily relies on the concept of context managers. This is a pyth ## About that line of code ```python -image = from_xarray(image_data, name="Random image") +image = from_array_like(image_data, name="Random image") print(image.data) ``` @@ -79,8 +79,8 @@ These two lines of code is a bit more complex than the other lines of code in th ### Functions are your friend -First lets talk about the function `from_xarray`. As you might notice its imported from the `mikro.api.schema` module, which contains a plethora of methods to interact with the Mikro data server (e.g for creating images, marking rois, commenting, subscribe to image events, ...). These functions -will provide ways of briding your local data with the data on the server. The `from_xarray` function is one of these functions. It will take an array-like structure as an argument and create an image on the server, that is then stored in the database and the object store, and return a reference to that back to you. +First lets talk about the function `from_array_like`. As you might notice it's imported from the `mikro_next.api.schema` module, which contains a plethora of methods to interact with the Mikro data server (e.g for creating images, marking rois, commenting, subscribe to image events, ...). These functions +will provide ways of bridging your local data with the data on the server. The `from_array_like` function is one of these functions. It will take any array-like structure (numpy array, xarray DataArray, etc.) as an argument and create an image on the server, stored in the database and the object store, and return a reference back to you. ### But there are steps in between @@ -95,14 +95,14 @@ You might notice that we don't pass a reference to **where** our server is locat While we personally love the flexibility of context managers, some prefer being more explicit about the context. If you are one of those people, you can also pass the underlying `App` mikro api client object to the `from_xarray` function as an argument. This will make the code more explicit, but also more verbose. We will use the context manager in this tutorial, but feel free to use the explicit way if you prefer that. ```python -image = from_xarray(image_data, name="Random image", rath=app.mikro.rath) # this uses the mikro service graphql client (rath) directly +image = from_array_like(image_data, name="Random image", rath=app.mikro.rath) # this uses the mikro service graphql client (rath) directly ``` 2. Configuration, Authentication, Authentication But hey, wasn't Arkitekt a secure and modular platform? How does our app now on which service to store our data? And how do we ensure that not everyone can upload data? This is where the configuration and authentication part comes in. Our app also contains a strategy of how we want to authentication our python script and ourselves against the Arkitekt server. So when we call this function for the first time, the `App` will try to establish a trust relationship with the server. Which strategy we will use greatly depends on how you would like your app to be used, and is highly custimizable. By default and suitable for most usecasses (if you use the `easy`builder ) it will now open a webbrowser for you where you will see the Arkitekt Server, ask you if you trust this app, and if you would like to (temporarilly) grant it access to your data. If you accept, the app will be granted access to the server, and will be send a token that it can use to authenticate itself against the server in the future. Importantly -1. We are calling the `from_xarray` function from the `mikro.api.schema` module. This function is a convenience function that will in turn call the apps mikro services api, with the given parameters. Lets inspect these parameters on their way to the server: +1. We are calling the `from_array_like` function from the `mikro_next.api.schema` module. This function is a convenience function that will in turn call the apps mikro services api, with the given parameters. Lets inspect these parameters on their way to the server: ### Illustration of the upload process diff --git a/docs/developers/python/classical/usage.mdx b/docs/developers/python/classical/usage.mdx index 70c680d1..447bf521 100644 --- a/docs/developers/python/classical/usage.mdx +++ b/docs/developers/python/classical/usage.mdx @@ -18,7 +18,7 @@ authentication, waiting for assignemnts from the server, etc.). This App object adjusted to fit your exact configuration need by using different builders (more on this later). ```python -from arkitekt import easy +from arkitekt_next import easy app = easy("your_app_name", version="your_app_version") @@ -53,20 +53,20 @@ A few words about this setup: by specifically passing the desired context to the function. ```python - from arkitekt import Arkitekt - from mikro import from_xarray + from arkitekt_next import easy + from mikro_next.api.schema import from_array_like import xarray as xr app = easy() with app: - image = from_xarray(xr.DataArray((100,100,1), dims=["x", "y", "z"]), name="image name") # stores the xarray on the mikro instance of the governing app context + image = from_array_like(xr.DataArray((100,100,1), dims=["x", "y", "z"]), name="image name") # stores the array on the mikro instance of the governing app context ``` :::note This behaviour allows for more functional and concise code but can also lead to unexpected behaviour. For example, if you have multiple app contexts running in parallel, the - `from_xarray` function will use the app context that is currently active. You can always opt out of this behaviour + `from_array_like` function will use the app context that is currently active. You can always opt out of this behaviour by specifically passing the desired context to the function. ::: @@ -79,7 +79,7 @@ A few words about this setup: sync code. ```python - from arkitekt import Arkitekt + from arkitekt_next import easy import asyncio app = easy() diff --git a/docs/developers/python/graphql.mdx b/docs/developers/python/graphql.mdx index 734380f0..3e48a35b 100644 --- a/docs/developers/python/graphql.mdx +++ b/docs/developers/python/graphql.mdx @@ -50,7 +50,7 @@ To get started, you will need to create a new project using the Arkitekt CLI. Yo ```bash -arkitekt init +arkitekt-next init ``` @@ -61,7 +61,7 @@ for the GraphQL API: We can now generate a configuration file for our code generation for our project by running the following command: ```bash -arkitekt gen init mikro +arkitekt-next gen init mikro ``` It will ask you where you want to generate the module for your generated code. Leave it as the default and press enter. @@ -98,7 +98,7 @@ This is a simple query that retrieves all datasets from the server. After saving ```bash -arkitekt gen compile mikro +arkitekt-next gen compile mikro ``` @@ -109,7 +109,7 @@ This should now create a directory named `api` in the same directory, that conta To use the generated code, you will need to import the generated query and use it to interact with the GraphQL API. You can do this by running the following code: ```python -from arkitekt import easy +from arkitekt_next import easy from api.mikro import first_query # Create a new app diff --git a/docs/developers/python/installation.mdx b/docs/developers/python/installation.mdx index 33051bd9..40cb39d4 100644 --- a/docs/developers/python/installation.mdx +++ b/docs/developers/python/installation.mdx @@ -10,27 +10,27 @@ import TabItem from "@theme/TabItem"; # Getting Started! -The easiest way to install the library is to use poetry or pip: +The easiest way to install the library is to use pip: -```python -pip install "arkitekt[all]" +```bash +pip install "arkitekt-next[all]" ``` This will install the library with all its dependencies, including the Arkitekt CLI as well as all supporting libraries for each service. -This is the recommend way to install arkitekt, especially if you are not planning -to use arkitekt as a dependency in another project. +This is the recommended way to install arkitekt-next, especially if you are not planning +to use it as a dependency in another project. :::info Modular Arkitekt If you are planning to only -use arkitekt as a dependency in another project, you can install it without the -CLI and add it additional dependencies as needed. +use arkitekt-next as a dependency in another project, you can install it without the +CLI and add additional dependencies as needed. -```python -pip install arkitekt -# pip install mikro (for microscopy support) -# pip install rekuest (for registering nodes) +```bash +pip install arkitekt-next +pip install mikro-next # for microscopy image support +pip install rekuest-next # for registering nodes ``` ::: @@ -50,20 +50,18 @@ The Arkitekt API here sits in the background just like any other python library, are in full control of the execution of your code. ```python -from arkitekt import easy -from mikro.api.schema import from_xarray +from arkitekt_next import easy +from mikro_next.api.schema import from_array_like import numpy as np # whatever python code -with easy("my_little_app", url="http://localhost:8000"): +with easy("my_little_app", url="http://localhost:8000"): # do stuff with the arkitekt api - # new image img = np.random.rand(100, 100) - image = from_xarray(img, name="my_image") # uploads image to the mikro service + image = from_array_like(img, name="my_image") # uploads image to the mikro service # Whatever python code - ``` This is a very simple example of how you can use the Arkitekt API to interact with the @@ -76,18 +74,19 @@ To get started with the classical style, you can follow the [Classic Tutorial](/ ## Plugin -App that follow the `integrated style` are more integrated into the Arkitekt ecosystem. -The Integrated style is your best choice if you want to **provide new functionality** (nodes) -and develop your own Arkitekt App. In the integrated style the Arkitekt CLI takes more control over your development process, ands streamline -the process of developing and your app publishing apps so that it can be used by other users. +Apps that follow the `plugin style` are more integrated into the Arkitekt ecosystem. +The Plugin style is your best choice if you want to **provide new functionality** (nodes) +and develop your own Arkitekt App. In the plugin style the Arkitekt CLI takes more control over your development process, and streamlines +the process of developing and publishing apps so that they can be used by other users. ```python -from arkitekt import register -from mikro.api.schema import from_xarray, RepresentationFragment +from arkitekt_next import register +from mikro_next.api.schema import from_array_like, Image +import numpy as np @register -def create_random_image(x_size: int, y_size: int) -> RepresentationFragment: +def create_random_image(x_size: int, y_size: int) -> Image: """ Random Image Creates a random image with the given size. @@ -101,42 +100,39 @@ def create_random_image(x_size: int, y_size: int) -> RepresentationFragment: Returns ------- - RepresentationFragment + Image The created image. """ - - img = np.random.rand(100, 100) - - return from_xarray(image, "Random Image") + img = np.random.rand(x_size, y_size) + return from_array_like(img, name="Random Image") if __name__ == "__main__": # here you can run other code, that # will not be executed when the app is - # run by the arkitekt cli + # run by the arkitekt-next cli pass - ``` Here we are creating an app that can be run by the Arkitekt CLI and that -provides a Node (Add two Numbers) that can be used by other apps. The -`@register` decorator is used to mark a function as a node. +provides a Node that can be used by other apps. The +`@register` decorator is used to mark a function as a node. -While you can run this script like any other python script ( executing the -`if __name__ == "__main__"` block ) in the integrated style you are using -the Arkitekt CLI to run yout app via: +While you can run this script like any other python script (executing the +`if __name__ == "__main__"` block), in the plugin style you use +the Arkitekt CLI to run your app via: ```bash -arkitekt run dev +arkitekt-next run dev ``` The CLI will then create the App context for your app and register the functions -that are marked with the `@register` decorator as nodes +that are marked with the `@register` decorator as nodes. :::info Why do we use this? This pattern might seem strange at first, but it has a few advantages, such as nice developer experience features like hot-reloading, - but most importantly and easy portability of your app to other Arkitekt instances. +but most importantly an easy portability of your app to other Arkitekt instances. ::: -To get started with the integrated style, you can follow the [Integrated Tutorial](//docs/developers/python/plugin) +To get started with the plugin style, you can follow the [Plugin Tutorial](/docs/developers/python/plugin) diff --git a/docs/developers/python/plugin/build.mdx b/docs/developers/python/plugin/build.mdx index a9cf125f..766b6554 100644 --- a/docs/developers/python/plugin/build.mdx +++ b/docs/developers/python/plugin/build.mdx @@ -28,7 +28,7 @@ all the dependencies and the code. In a default configuration, Arkitekt uses Doc To start building your plugin, you will have to initialize a `Flavour` for your project. You can do this by running the following command: ```bash -arkitekt port init +arkitekt-next kabinet init ``` This will ask you a few questions about your project and will create a new flavour in your project ".arkitekt" directory. The content of @@ -113,7 +113,7 @@ We are still working on the documentation for the selectors, so for now, you can To build the container and your plugins, you can run the following command: ```bash -arkitekt port build +arkitekt-next kabinet build ``` This will build the container with your local docker daemon and add a new build in your local directory. @@ -124,7 +124,7 @@ Importantly this build is not yet published and uploaded and can only be used on Staging a Build allows you to test out your just build plugin against your local Arkitekt instance. To stage a build, you can run the following command: ```bash -arkitekt port stage +arkitekt-next kabinet stage ``` This will run the container locally and will instruct it to connect to your local Arkitekt instance. @@ -136,7 +136,7 @@ This is a great way to test your plugin before you publish it. To publish a build, you can run the following command: ```bash -arkitekt port publish +arkitekt-next kabinet publish ``` This is the final step in the process of building your plugin. This will upload your container to a registry, and will create diff --git a/docs/developers/python/plugin/index.mdx b/docs/developers/python/plugin/index.mdx index ef08eb4d..8627061b 100644 --- a/docs/developers/python/plugin/index.mdx +++ b/docs/developers/python/plugin/index.mdx @@ -20,7 +20,7 @@ The integrated Style revolves around the concepts of Apps. So lets create one. O want to create your new app and run : ```bash -arkitekt init +arkitekt-next init ``` This should start an installation wizard, which will guide you through the process of creating a new app and will ask you a few questions @@ -63,7 +63,7 @@ This is the main file of your app. And the entrypoint that the CLI will call whe It is a python file, that contains all the [Nodes](/docs/design/terminology/node) of your app. Lets open it up ```python -from arkitekt import register +from arkitekt_next import register @register def hello_world(name: str) -> str: @@ -83,13 +83,13 @@ def hello_world(name: str) -> str: return greeting ``` -This is the default code that is generated for you. It contains a single function that defines and implement the -"Hello World" Node [Node](/docs/design/terminology/node). Its pretty straigtforward but lets break it down: +This is the default code that is generated for you. It contains a single function that defines and implements the +"Hello World" Node [Node](/docs/design/terminology/node). It's pretty straightforward but let's break it down: ```python -from arkitekt import register +from arkitekt_next import register ``` -This is defining import that make this python code an Arkitekt. `register` is a decorator that can mark any function in your app, as a Node. +This import is what makes this Python code an Arkitekt app. `register` is a decorator that can mark any function in your app as a Node. You can have as many Nodes as you want in your app, and once you run your app, they will become available on the Arkitekt Server and can be used by others. @@ -105,7 +105,7 @@ an Arkitekt Server and have it running. For strategies of how to do that, check Open up your terminal and run: ```bash -arkitekt run dev --url localhost:8000 +arkitekt-next run dev --url localhost:8000 ``` This will start your app in development mode and connect it to your local Arkitekt Server. You should see something like this: diff --git a/docs/developers/python/plugin/more.mdx b/docs/developers/python/plugin/more.mdx index a65f24b3..4d7cc845 100644 --- a/docs/developers/python/plugin/more.mdx +++ b/docs/developers/python/plugin/more.mdx @@ -15,7 +15,7 @@ Lets adapt our app to do something useful. We will create a simple app that will choice. So how do we do that ? Lets look at the code we created, before and deduce what we need to change. ```python -from arkitekt import register +from arkitekt_next import register @register def hello_world(name: str) -> str: @@ -35,46 +35,122 @@ def hello_world(name: str) -> str: return greeting ``` -So what do we need to change ? First of all we need to change the name of the function. We will call it `max_project`. Then we need to -change the documentation. We will call it `Max Project` and we will change the description to `Max Project a microscopy image`. Documentation -your functions is very important. It will help you and others to understand what your function does. And it will help you to find your function +So what do we need to change? First of all we need to change the name of the function. We will call it `max_project`. Then we need to +change the documentation. We will call it `Max Project` and change the description to `Max Project a microscopy image`. Documenting +your functions is very important — it will help you and others to understand what your function does, and it will help users find your function in the UI. -But how do we tell Arkitekt that we are no longer working with strings but need "Images"? This is done through updating the type hint of our function. We will change it from `str` to `Representation`, -an import for the `mikro` python package. When register a function the SDK will automatically detect the type hint and will create the correct input and output [Port](/docs/design/terminology/port), for you. -This means you have to (almost) always use type hints to indicate the type of your input and output. But hey you should do that anyway. +But how do we tell Arkitekt that we are no longer working with strings but need "Images"? This is done through updating the type hint of our function. We will change it from `str` to `Image`, +imported from the `mikro_next` Python package. When you register a function, the SDK will automatically detect the type hint and will create the correct input and output [Port](/docs/design/terminology/port) for you. +This means you have to (almost) always use type hints to indicate the type of your input and output. ```python -from arkitekt import register -from mikro import Representation +from arkitekt_next import register +from mikro_next.api.schema import Image, from_array_like @register -def max_project(image: Representation) -> Representation: +def max_project(image: Image) -> Image: """Max Project a microscopy image - This is a simple node that will max project a microscopy image. + This is a simple node that will max project a microscopy image along the z-axis. Args: - image (Representation): The input image + image (Image): The input image Returns: - str: The output image + Image: The max-projected output image """ - print(f"Max Projecting {image}") - ... - return image + projected = image.data.max(dim="z") + return from_array_like(projected, name=f"Max Projection of {image.name}", origins=[image]) ``` And that is it. :::note -The `Representation` type from the `mikro` is our first [Structure](/docs/design/terminology/structure). Structures represent more complex data types, then +The `Image` type from `mikro_next` is our first [Structure](/docs/design/terminology/structure). Structures represent more complex data types than simple types like `String` or `Int`. While most of the time you will find yourself working with sharable global structures (that live on a connected -service), you can also create your own structures (even non serializable), that are only available to your app. We will cover this in a later tutorial. +service), you can also create your own structures (even non-serializable ones) that are only available to your app. We will cover this in a later tutorial. ::: +## Advanced: App Lifecycle Hooks + +`arkitekt-next` provides several decorators beyond `@register` to help you manage your app's lifecycle and share state between nodes. + +### `@startup` — Run code on agent start + +Use `@startup` to run initialization logic once when your app connects to the server (e.g. loading a model into memory). + +```python +from arkitekt_next import register, startup + +@startup +async def load_model(): + # called once when the agent starts up + print("Model loaded!") + +@register +def run_inference(image: Image) -> Image: + ... +``` + +### `@background` — Run persistent background tasks + +Use `@background` to run an async function as a long-running background task while your app is connected. + +```python +from arkitekt_next import register, background + +@background +async def poll_hardware(): + import asyncio + while True: + # check hardware status, emit logs, etc. + await asyncio.sleep(5) +``` + +### `@state` — Share mutable state between nodes + +Use `@state` to define a dataclass that holds shared state across all your nodes. The state is initialized once and injected into any node that declares it as a parameter. + +```python +from arkitekt_next import register, startup, state +from dataclasses import dataclass, field + +@state +@dataclass +class MyState: + model: object = field(default=None) + +@startup +async def load(my_state: MyState): + my_state.model = load_my_model() + +@register +def run_inference(image: Image, my_state: MyState) -> Image: + result = my_state.model.predict(image.data) + return from_array_like(result, name="inference result") +``` + +### `@context` — Inject app-level context into nodes + +Use `@context` to define a context class that provides access to connected services (e.g. database clients) within your nodes. + +```python +from arkitekt_next import register, context + +@context +class MyContext: + ... # populated automatically with service clients + +@register +def my_node(image: Image, context: MyContext) -> Image: + ... +``` + +These hooks give you full control over your app's lifecycle without any extra boilerplate. + diff --git a/docs/developers/python/plugin/usage.mdx b/docs/developers/python/plugin/usage.mdx index 7e278351..32b925e5 100644 --- a/docs/developers/python/plugin/usage.mdx +++ b/docs/developers/python/plugin/usage.mdx @@ -37,7 +37,7 @@ cd demo Inside the folder, we will run the following command to create a new arkitekt app. ```bash -arkitekt init +arkitekt-next init ``` This will launch the arkitekt CLI and ask you a few questions about your app. Don't worry, you can always change @@ -98,12 +98,12 @@ instance, enabling them as nodes. You can imaging the app.py file as the "main" Lets take a look at the app.py file that was created for us, if you chose the `filter` template: ```python -from arkitekt import register -from mikro.api.schema import RepresentationFragment, from_xarray +from arkitekt_next import register +from mikro_next.api.schema import Image, from_array_like @register -def max_intensity_projection(image: RepresentationFragment) -> RepresentationFragment: +def max_intensity_projection(image: Image) -> Image: """Z-Project the Maximum Intensity This function projects the maximum intensity of the input image @@ -111,36 +111,36 @@ def max_intensity_projection(image: RepresentationFragment) -> RepresentationFra Parameters ---------- - image : RepresentationFragment + image : Image The input image Returns ------- - RepresentationFragment + Image The projected image """ - image = image.data.max(dim="z") - return from_xarray( - image, name="Max Intensity Projection" + image.name, origins=[image] + projected = image.data.max(dim="z") + return from_array_like( + projected, name="Max Intensity Projection " + image.name, origins=[image] ) ``` This is the `hello world` of arkitekt apps. A simple function that takes an image as input and returns a new image -as output. Lets break it down. +as output. Let's break it down. ```python -from arkitekt import register -from mikro.api.schema import RepresentationFragment, from_xarray +from arkitekt_next import register +from mikro_next.api.schema import Image, from_array_like ``` -First we import the `register` decorator from the arkitekt package. This is the decorator that we will use to register -our functions with the arkitekt instance. We also import the `RepresentationFragment` and `from_xarray` functions from -the mikro package (which will help us interact with the Mikro service on the connected instance). Calling `from_xarray` will create a new representation/image from any xarray or numpy object. +First we import the `register` decorator from the `arkitekt_next` package. This is the decorator that we will use to register +our functions with the arkitekt instance. We also import the `Image` type and `from_array_like` function from +the `mikro_next` package (which will help us interact with the Mikro service on the connected instance). Calling `from_array_like` will create a new image from any xarray or numpy array-like object. ```python @register -def max_intensity_projection(image: RepresentationFragment) -> RepresentationFragment: +def max_intensity_projection(image: Image) -> Image: """Z-Project the Maximum Intensity This function projects the maximum intensity of the input image @@ -148,12 +148,12 @@ def max_intensity_projection(image: RepresentationFragment) -> RepresentationFra Parameters ---------- - image : RepresentationFragment + image : Image The input image Returns ------- - RepresentationFragment + Image The projected image """ @@ -162,32 +162,28 @@ def max_intensity_projection(image: RepresentationFragment) -> RepresentationFra And this is our function. It takes a single argument, which is an image, and returns a new image. The function is decorated with the `@register` decorator, which will register the function with the arkitekt instance when the app is run. A lot of -the information that is needed to register the function is extracted from the function signature. For example, the name of +the information needed to register the function is extracted from the function signature. For example, the name of the function is extracted from the function name, the description is extracted from the docstring, and the input and output -types are extracted from the function signature. The `@register` decorator also takes a few optional arguments, which can -be used to customize the registration process. You can read more about the `@register` decorator in the decorators section. +types are extracted from the type annotations. The `@register` decorator also takes a few optional arguments, which can +be used to customize the registration process. ```python -image = image.data.max(dim="z") -return from_xarray( - image, name="Max Intensity Projection" + image.name, origins=[image] +projected = image.data.max(dim="z") +return from_array_like( + projected, name="Max Intensity Projection " + image.name, origins=[image] ) ``` -And this is the body of our function. When calling our function, the arkitekt instance will pass in a `RepresentationFragment` +And this is the body of our function. When calling our function, the arkitekt instance will pass in an `Image` object, which contains the input data. We can access the data by calling `image.data`. The data is stored as an xarray object, which we can use to perform our calculations. In this case, we are calculating the maximum intensity projection of the image -along the z-axis. We then create a new Image on the platform from the calculated data and return it, through the `from_xarray` +along the z-axis. We then create a new Image on the platform from the calculated data and return it through the `from_array_like` function. (Note that we are also adding a prefix to the name of the image, to make it easier to distinguish between the input and output images) -:::info Why Representation Fragment? +:::info About the Image type -If you are wondering why our image is called a `RepresentationFragment` and not just an image, please read the [mikro](https://mikro.dev) -and graphql sections of the docs. But in short, a `RepresentationFragment` is a representation of an image that is stored -on a mikro instance. It contains information about the image, such as its name, description, and data. Besides the data, -it also contains a "fragment" of metadata from all of the metadata that is associated with the image. The nomenclature is -a bit confusing, and we _will definetly_ change it in the future, but for now, just think of it as an image. +`Image` (from `mikro_next`) represents an image stored on a Mikro-Next instance. It contains information about the image such as its name, description, and data. You can access the underlying xarray data via `image.data`, which gives you full access to the image array for computation. :::