xp1632 / VPE_IP

0 stars 0 forks source link

First VP ImageJ node example_and_solutions for problems #68

Open xp1632 opened 7 months ago

xp1632 commented 7 months ago

First VP ImageJ node example:


  1. What code should be in the VP import part ?

    • We have

      • Python normal import: to import python libraries
      • Java Jimport: to import java libraries
        HyperSphereShape = jimport('net.imglib2.algorithm.neighborhood.HyperSphereShape')
        radius = HyperSphereShape(2)
      • ImageJ initialization to initialize PyImageJ and ImageJ in the beginning, this part is necessary for all ImageJ functions:
        
        import imagej

      initialize ImageJ

      ij = imagej.init('sc.fiji:fiji:2.14.0') print(f"ImageJ version: {ij.getVersion()}")


Current Solution


Reasons


Small concerns

  1. Orders of VP import:
xp1632 commented 7 months ago
  1. We want to avoid dealing with Knowledge Graph and Display nodes in the first stage, what contents should be in the first VP ImageJ workflow?


  • Images we get from ij.io().open() returns ImageJ2 Dataset java object.
  • Image we get from ij.IJ.openImage() legacy opener returns ImageJ1 ImagePlusjava object

image

xp1632 commented 7 months ago

Here's the draft of our first VP node workflow:

_cgi-bin_mmwebwx-bin_webwxgetmsgimg__ MsgID=2533144353648242033 skey=@crypt_e8cb0f1c_98adc568c6e6ed1bb8fca3fb83d9d6f5 mmweb_appid=wx_webfilehelper

xp1632 commented 7 months ago

Ori_VP_ReadImage_Node

image


{
  "description": "Image read and write functions from torchvision.io",
  "enable": true,
  "nodes": {
    "read_image": {
      "type": "read_image",
      "category": "function",
      "title": "read_image",
      "tooltip": "Reads a JPEG or PNG image into a 3 dimensional RGB or grayscale Tensor. Optionally converts the image to the desired format. The values of the output tensor are uint8 in [0, 255].",
      "externalImports": "from torchvision import io\nfrom torchvision.io import ImageReadMode",
      "codeGenerator": "function code(inputs, outputs, node, generator) {\n    if (inputs[2] === 'ImageReadMode.RGB')\n        return `${outputs[1]} = io.read_image(${inputs[1]}, ${inputs[2]})\\n${outputs[1]} = {\\n  'value': ${outputs[1]},\\n  'dataType': 'torch.tensor',\\n  'metadata': {\\n    'colorChannel': 'rgb',\\n    'channelOrder': 'channelFirst',\\n    'isMiniBatched': False,\\n    'intensityRange': '0-255',\\n    'device': 'cpu'\\n  }\\n}\\n${outputs[0]}`;\n    if (inputs[2] === 'ImageReadMode.GRAY')\n        return `${outputs[1]} = {\\n  'value': ${outputs[1]},\\n  'dataType': 'torch.tensor',\\n  'metadata': {\\n    'colorChannel': 'grayscale',\\n    'channelOrder': 'channelFirst',\\n    'isMiniBatched': False,\\n    'intensityRange': '0-255',\\n    'device': 'cpu'\\n  }\\n}\\n${outputs[0]}`;\n}",
      "inputs": {
        "execIn": {
          "title": "execIn",
          "tooltip": "execIn",
          "dataType": "exec",
          "showWidget": false,
          "showTitle": false
        },
        "path": {
          "title": "path",
          "dataType": "string",
          "tooltip": "path(str) - path of the JPEG or PNG image."
        },
        "mode": {
          "title": "mode",
          "dataType": "imageio.ImageReadMode",
          "defaultValue": "ImageReadMode.RGB",
          "tooltip": "mode(ImageReadMode) - The read mode used for optionally converting the image. Default: ImageReadMode.UNCHANGED."
        }
      },
      "outputs": {
        "execOut": {
          "title": "execOut",
          "tooltip": "execOut",
          "dataType": "exec",
          "showWidget": false,
          "showTitle": false
        },
        "image": {
          "title": "image",
          "dataType": "image",
          "defaultValue": {
            "dataType": "torch.tensor"
          },
          "tooltip": "{dataType: torch.tensor, value, layout: [chw], colorMode: [rgb, grayscale], intensityRange: 0-255' device: cpu}"
        }
      }
    }
  }
}
xp1632 commented 7 months ago

VP_IJ_read_image_node is working:

image IJ_read_image6.json

xp1632 commented 7 months ago

Problems with Intermediate Image Display on Node:



we wrote :

n_1_image = {
  'value': n_1_image,
  'dataType': 'torch.tensor',
  'metadata': {
    'colorChannel': 'rgb',
    'channelOrder': 'channelFirst',
    'isMiniBatched': False,
    'intensityRange': '0-255',
    'device': 'cpu'
  }
}
n_1_image = {
  'value': n_1_image,
  'dataType': '**numpy.ndarray**',
  'metadata': {
    'colorChannel': 'rgb',
    'channelOrder': '**channelLast**',
    'isMiniBatched': False,
    'intensityRange': '0-255',
    'device': 'cpu'
  }
}
xp1632 commented 7 months ago

CaptureImageCode structure:


And find out the problems should happen in two parts:

1. The conversion part: image


2. The PIL image part:

xp1632 commented 7 months ago

Solution for the different required numpy shape:


image https://matplotlib.org/stable/gallery/color/colormap_reference.html image


The picture without color mapping should look like: image

xp1632 commented 6 months ago

CaptureImageCode inside problems caused by non- unique dom id

are in Issue https://github.com/Max-ChenFei/VPE_IP/issues/70


image



image image image

xp1632 commented 6 months ago

The PIL conversion looks wrong, we test some cases to see which part went wrong:

  1. We'll test jpg image read by ori read_image_io's case's PIL image image image


  1. So it means, while this image and our image are both converted from array to PIL image, it's dtype or shape or range could be different

image


2.1 Our Java object tif image we get from PyImage, then converted to numpy array has different type and range:

image

PIL images expect pixel values to be in the range of 0-255 for 8-bit images. If the numpy image has a different data type or the pixel values are not in the expected range, the resulting PIL image may not look correct.


- Solution


Conclusion for CaptureImaegCode functions:

  1. we should only keep the desired node panel as Watched and unwatch all the rest node in other cells, notebooks, in the whole jupyter environment to keep its unique dom_id

    • otherwise, the image would be pass to the first VP cell with first watch node
  2. we should normalize the image array into range [0,225] and type uint8 with code like
    np_img_1707918575145['value'] = ((np_img_1707918575145['value'] - np_img_1707918575145['value'].min()) / (np_img_1707918575145['value'].max() - np_img_1707918575145['value'].min()) * 255).astype('uint8') so it could be correctly displayed

    • And I was wondering, should we keep this normalization part in every node?
    • Or in CaptureImageFunction before its PIL conversion Image.fromArray
    • Now I don't want to change CaptureImageFunction itself so I'll just normalize the output numpy array of our IJ_VP_Node
xp1632 commented 6 months ago

- Working node:

IJ_read_image32.json image

xp1632 commented 6 months ago

Proceed to Op filters

image image image


-I think for segmention, this image is a better example: https://imagej.net/plugins/auto-threshold#available-methods image


- We could also print some short introductory messages before each image display

xp1632 commented 6 months ago

Modification of IJ_Read Node before moving on to Op

We want the current IJ_read_image node to


  1. show grayscale image without matplotlib's default color mapping

    • only set cmap=gray for grayscale image with 2D shape(x,y)
  2. show color tif image and RGB jpg image as it is(with shape x,y,channel)

    • in current version of IJ_read_image, when reading jpg image, we get numpy array with shape (channel, x, y), channel is in the first order of dimension
    • however in current knowledge graph, numpy array are ChannelLast
    • we need to transpose the array, for example, from (3, x, y) to (x,y,3)
  3. show helping messages for time-series with shape (x, y, channel, time)

    • only 2d images can be displayed on jupyter, so we'll recommend the user to slice the time series image and then display it
  4. show introductory messages before every image display in jupyter:

    • for example : Original Image:, After Mean Filter, After Segmentation
    • If we want to show more information of current image, we can also display the dump info after reading the image
      
      def dump_info(image):
      """A handy function to print details of an image object."""
      name = image.name if hasattr(image, 'name') else None # xarray
      if name is None and hasattr(image, 'getName'): name = image.getName() # Dataset
      if name is None and hasattr(image, 'getTitle'): name = image.getTitle() # ImagePlus
      print(f" name: {name or 'N/A'}")
      print(f" type: {type(image)}")
      print(f"dtype: {image.dtype if hasattr(image, 'dtype') else 'N/A'}")
      print(f"shape: {image.shape}")
      print(f" dims: {image.dims if hasattr(image, 'dims') else 'N/A'}")

dump_info(dataset)


**
![image](https://github.com/Max-ChenFei/VPE_IP/assets/8528052/cfd18293-5a8d-413f-aa30-3c47899eb4fc)
**
---

**We would like our `IJ_read_image` to work as follows**

![image](https://github.com/Max-ChenFei/VPE_IP/assets/8528052/e18fb088-3b82-4b7c-9841-2a7d2f549915)
xp1632 commented 6 months ago

Final Code Snippet for IJ_read_image:

import imagej
ij = imagej.init('sc.fiji:fiji:2.14.0')
import numpy as np
tif_image = ij.py.initialize_numpy_image(ij.io().open('retina.jpg'))
n_1_image = ij.py.rai_to_numpy(ij.io().open('retina.jpg'),tif_image)
number_of_dims = np.ndim(n_1_image)
def image_info(image):
    # A handy function to print details of an image object.
    print("--- Image Information ---")
    name = image.name if hasattr(image, 'name') else None # xarray
    if name is None and hasattr(image, 'getName'): name = image.getName() # Dataset
    if name is None and hasattr(image, 'getTitle'): name = image.getTitle() # ImagePlus
    print(f" type: {type(image)}")
    print(f"dtype: {image.dtype if hasattr(image, 'dtype') else 'N/A'}")
    print(f"shape: {image.shape}")
    print(f" dims: {image.dims if hasattr(image, 'dims') else 'N/A'}")
    print ("-------------------------")
image_info(n_1_image);
if number_of_dims == 2:
    #Grayscale image
    print("Grayscale image:")
    ij.py.show(n_1_image,cmap='gray')
if number_of_dims == 3:
    #RGB image 
    print("Input Image:")
    #transpose (channel,x,y) -> (x,y,channel)
    n_1_image = np.transpose(n_1_image, (1, 2, 0))
    print(n_1_image.shape)
    ij.py.show(n_1_image)
if number_of_dims == 4:
    #time series image
    print("Currently we focus on 2d images, please slice the 3d image for displaying in jupyter")
#Normalization to range (0,255), uint8 
n_1_image = ((n_1_image - n_1_image.min()) / (n_1_image.max() - n_1_image.min())* 255).astype('uint8')
color_channel = 'grayscale' if number_of_dims == 2 else 'rgb'
n_1_image = {
    'value': n_1_image,
    'dataType': 'numpy.ndarray',
    'metadata': {
    'colorChannel': color_channel,
    'channelOrder': 'channelLast',
    'isMiniBatched': False,
    'intensityRange': '0-255',
    'device': 'cpu'
    }
}

  1. Display Color tif: image

  2. Display Grayscale tif without color mapping:

image

  1. Display 4d timeseries tif
xp1632 commented 6 months ago

Final IJ_Read_Image Code --> Node


format_code() in json_node_generator

def format_code(code, input1, output1):
    # Replace the input and output names in the code
    code = code.replace("'{}'".format(input1), "${inputs[1]}")
    code = code.replace(output1, "${outputs[1]}")

    # Add a visible '\n' at the end of each line
    code = '\\n'.join(code.strip().splitlines()) + '\\n'
    code += '${outputs[0]}'
    return code

# Test the function
code = """
tif_image = ij.py.initialize_numpy_image(ij.io().open('retina.jpg'))
n_1_image = ij.py.rai_to_numpy(ij.io().open('retina.jpg'),tif_image)
n_1_image = ((n_1_image - n_1_image.min()) / (n_1_image.max() - n_1_image.min())* 255).astype('uint8')
ij.py.show(n_1_image)
n_1_image = {
    'value': n_1_image,
    'dataType': 'numpy.ndarray',
    'metadata': {
        'colorChannel': 'grayscale',
        'channelOrder': 'channelLast',
        'isMiniBatched': False,
        'intensityRange': '0-255',
        'device': 'cpu'
    }
}
"""
formatted_code = format_code(code, "retina.jpg", "n_1_image")
print(formatted_code)

tif_image = ij.py.initialize_numpy_image(ij.io().open(${inputs[1]}))\n${outputs[1]} = ij.py.rai_to_numpy(ij.io().open(${inputs[1]}),tif_image)\n${outputs[1]} = ((${outputs[1]} - ${outputs[1]}.min()) / (${outputs[1]}.max() - ${outputs[1]}.min())* 255).astype('uint8')\nij.py.show(${outputs[1]})\n${outputs[1]} = {\n    'value': ${outputs[1]},\n    'dataType': 'numpy.ndarray',\n    'metadata': {\n        'colorChannel': 'grayscale',\n        'channelOrder': 'channelLast',\n        'isMiniBatched': False,\n        'intensityRange': '0-255',\n        'device': 'cpu'\n    }\n}\n${outputs[0]}

Thoughts for IJ_Op_VP_node template and auto-generation only for this part

xp1632 commented 6 months ago

Auto-generated part of json node pass:

- Enhancement of Auto_Json_generation is in Issue https://github.com/Max-ChenFei/VPE_IP/issues/72

image


Copied textual Code

import imagej
ij = imagej.init('sc.fiji:fiji:2.14.0')
import numpy as np
tif_image = ij.py.initialize_numpy_image(ij.io().open('retina.jpg'))
n_1_image = ij.py.rai_to_numpy(ij.io().open('retina.jpg'),tif_image)
number_of_dims = np.ndim(n_1_image)
def image_info(image):
    # A handy function to print details of an image object.
    print("--- Image Information ---")
    name = image.name if hasattr(image, 'name') else None # xarray
    if name is None and hasattr(image, 'getName'): name = image.getName() # Dataset
    if name is None and hasattr(image, 'getTitle'): name = image.getTitle() # ImagePlus
    print(f" type: {type(image)}")
    print(f"dtype: {image.dtype if hasattr(image, 'dtype') else 'N/A'}")
    print(f"shape: {image.shape}")
    print(f" dims: {image.dims if hasattr(image, 'dims') else 'N/A'}")
    print ("-------------------------")
image_info(n_1_image);
if number_of_dims == 2:
    #Grayscale image
    print("Grayscale image:")
    ij.py.show(n_1_image,cmap='gray')
if number_of_dims == 3:
    #RGB image 
    print("Input Image:")
    #transpose (channel,x,y) -> (x,y,channel)
    n_1_image = np.transpose(n_1_image, (1, 2, 0))
    print(n_1_image.shape)
    ij.py.show(n_1_image)
if number_of_dims == 4:
    #time series image
    print("Currently we focus on 2d images, please slice the 3d image for displaying in jupyter")
#Normalization to range (0,255), uint8 
n_1_image = ((n_1_image - n_1_image.min()) / (n_1_image.max() - n_1_image.min())* 255).astype('uint8')
color_channel = 'grayscale' if number_of_dims == 2 else 'rgb'
n_1_image = {
    'value': n_1_image,
    'dataType': 'numpy.ndarray',
    'metadata': {
    'colorChannel': color_channel,
    'channelOrder': 'channelLast',
    'isMiniBatched': False,
    'intensityRange': '0-255',
    'device': 'cpu'
    }
}

Partial_Json_generator:

def format_code(code, input1, output1):
    # Replace the input and output names in the code
    code = code.replace("'{}'".format(input1), "${inputs[1]}")
    code = code.replace(output1, "${outputs[1]}")

    # Add a visible '\n' at the end of each line
    code = '\\n'.join(code.strip().splitlines()) + '\\n'
    code += '${outputs[0]}'

    # Add backticks at the beginning and end of the code
    code = '`' + code + '`'
    return code
# Test real code
code = """
tif_image = ij.py.initialize_numpy_image(ij.io().open('test_grayscale_image.tif'))
n_1_image = ij.py.rai_to_numpy(ij.io().open('test_grayscale_image.tif'),tif_image)
number_of_dims = np.ndim(n_1_image)
def image_info(image):
    # A handy function to print details of an image object.
    print("--- Image Information ---")
    name = image.name if hasattr(image, 'name') else None # xarray
    if name is None and hasattr(image, 'getName'): name = image.getName() # Dataset
    if name is None and hasattr(image, 'getTitle'): name = image.getTitle() # ImagePlus
    print(f" type: {type(image)}")
    print(f"dtype: {image.dtype if hasattr(image, 'dtype') else 'N/A'}")
    print(f"shape: {image.shape}")
    print(f" dims: {image.dims if hasattr(image, 'dims') else 'N/A'}")
    print ("-------------------------")
image_info(n_1_image);
if number_of_dims == 2:
    #Grayscale image
    print("Grayscale image:")
    ij.py.show(n_1_image,cmap='gray')
if number_of_dims == 3:
    #RGB image 
    print("Input Image:")
    #transpose (channel,x,y) -> (x,y,channel)
    n_1_image = np.transpose(n_1_image, (1, 2, 0))
    print(n_1_image.shape)
    ij.py.show(n_1_image)
if number_of_dims == 4:
    #time series image
    print("Currently we focus on 2d images, please slice the 3d image for displaying in jupyter")
#Normalization to range (0,255), uint8 
n_1_image = ((n_1_image - n_1_image.min()) / (n_1_image.max() - n_1_image.min())* 255).astype('uint8')
color_channel = 'grayscale' if number_of_dims == 2 else 'rgb'
n_1_image = {
    'value': n_1_image,
    'dataType': 'numpy.ndarray',
    'metadata': {
    'colorChannel': color_channel,
    'channelOrder': 'channelLast',
    'isMiniBatched': False,
    'intensityRange': '0-255',
    'device': 'cpu'
    }
}
"""
formatted_code = format_code(code, "test_grayscale_image.tif", "n_1_image")
print(formatted_code)

json_generator_output:

`tif_image = ij.py.initialize_numpy_image(ij.io().open(${inputs[1]}))\n${outputs[1]} = ij.py.rai_to_numpy(ij.io().open(${inputs[1]}),tif_image)\nnumber_of_dims = np.ndim(${outputs[1]})\ndef image_info(image):\n    # A handy function to print details of an image object.\n    print("--- Image Information ---")\n    name = image.name if hasattr(image, 'name') else None # xarray\n    if name is None and hasattr(image, 'getName'): name = image.getName() # Dataset\n    if name is None and hasattr(image, 'getTitle'): name = image.getTitle() # ImagePlus\n    print(f" type: {type(image)}")\n    print(f"dtype: {image.dtype if hasattr(image, 'dtype') else 'N/A'}")\n    print(f"shape: {image.shape}")\n    print(f" dims: {image.dims if hasattr(image, 'dims') else 'N/A'}")\n    print ("-------------------------")\nimage_info(${outputs[1]});\nif number_of_dims == 2:\n    #Grayscale image\n    print("Grayscale image:")\n    ij.py.show(${outputs[1]},cmap='gray')\nif number_of_dims == 3:\n    #RGB image \n    print("Input Image:")\n    #transpose (channel,x,y) -> (x,y,channel)\n    ${outputs[1]} = np.transpose(${outputs[1]}, (1, 2, 0))\n    print(${outputs[1]}.shape)\n    ij.py.show(${outputs[1]})\nif number_of_dims == 4:\n    #time series image\n    print("Currently we focus on 2d images, please slice the 3d image for displaying in jupyter")\n#Normalization to range (0,255), uint8 \n${outputs[1]} = ((${outputs[1]} - ${outputs[1]}.min()) / (${outputs[1]}.max() - ${outputs[1]}.min())* 255).astype('uint8')\ncolor_channel = 'grayscale' if number_of_dims == 2 else 'rgb'\n${outputs[1]} = {\n    'value': ${outputs[1]},\n    'dataType': 'numpy.ndarray',\n    'metadata': {\n    'colorChannel': color_channel,\n    'channelOrder': 'channelLast',\n    'isMiniBatched': False,\n    'intensityRange': '0-255',\n    'device': 'cpu'\n    }\n}\n${outputs[0]}`
xp1632 commented 6 months ago

Requirement for code that we want to autoformat:

  1. no extra space or line break between lines ? - 2. Value of node name and node type in json object should be the same, title doesn't matter for Node but is important for user to read
    1. We support '#' for commenting, we don't support """
xp1632 commented 6 months ago

calling Ops Informations are in https://github.com/Max-ChenFei/VPE_IP/issues/62#issuecomment-1960070119

xp1632 commented 6 months ago

Op Segmentation workflow:

  1. Cell Segmentation based on existing ops (https://py.imagej.net/en/latest/Classic-Segmentation.html#segmentation-workflow-with-imagej2)

1.2 Particles Analysis: (https://py.imagej.net/en/latest/Puncta-Segmentation.html)

1.4 Frangi vesselness from a MRA brain image: (https://github.com/imagej/tutorials/blob/master/notebooks/1-Using-ImageJ/2-ImageJ-Ops.ipynb) image image


1.5 Thresholding, tons of thresholding op: https://github.com/imagej/tutorials/blob/master/notebooks/1-Using-ImageJ/2-ImageJ-Ops.ipynb image we should use test image here: https://imagej.net/plugins/auto-threshold#available-methods image

1.6 Morphology https://github.com/imagej/tutorials/blob/master/notebooks/1-Using-ImageJ/2-ImageJ-Ops.ipynb image


(-)2. More advanced cellpose segmentation for future case: (https://py.imagej.net/en/latest/Cellpose-StarDist-Segmentation.html)

(x)3. More more advanced texture analysis and add red rectangle on image: https://py.imagej.net/en/latest/GLCM.html#using-pyimagej-and-imagej-ops-for-glcm-texture-analysis

xp1632 commented 6 months ago

Cell Segmentation Workflow

  1. 3d_image_slicing: cell_segmentation_slicing6.json image

  2. edge_preserve_smoothing cell_segmentation_slicing6_edge7.json image image

  3. otsu_segmentation cell_segmentation_s6_e7_otsu1.json image

4.1 morphology filter_dilation

cell_segmentation_s6_e7_o1_morph3.json image

4.2 morphology filter_fillHoles cell_segmentation_s6_e7_o1_m3_fillHole1.json

image

  1. watershed labeling
xp1632 commented 6 months ago

Watershed Problems