labsyspharm / mcmicro

Multiple-choice microscopy pipeline
https://mcmicro.org/
MIT License
104 stars 58 forks source link

unstiched image #526

Open Elena983 opened 11 months ago

Elena983 commented 11 months ago

Hi Artem

I would like to use the BaSiC module, but our device only provides the already stitched and aligned image. So, I've done the code to divide into tiles with overlapping, I need to name the tiles, so they then correctly may be assembled in ashlar.

Code the next from PIL import Image

# Load the TIFF image
image = Image.open("/Users/me/Desktop/CRC.ome.tif")

# Define the tile size (e.g., 100x100 pixels)
tile_size = (600, 600)

# Define the overlap size (e.g., 20 pixels)
overlap = 30

# Calculate the effective tile size considering overlap
effective_tile_width = tile_size[0] - overlap
effective_tile_height = tile_size[1] - overlap

# Calculate the number of rows and columns
num_rows = (image.height + overlap) // effective_tile_height
num_columns = (image.width + overlap) // effective_tile_width

# Iterate over the image and crop into overlapping tiles
for i in range(num_rows):
    for j in range(num_columns):
        x1 = j * effective_tile_width
        y1 = i * effective_tile_height
        x2 = x1 + tile_size[0]
        y2 = y1 + tile_size[1]

        if x2 > image.width:
            x1 -= (x2 - image.width)
            x2 = image.width
        if y2 > image.height:
            y1 -= (y2 - image.height)
            y2 = image.height

        tile = image.crop((x1, y1, x2, y2))
        tile.save(f"/Users/me/Desktop/tiles/tile_{i}_{j}.tif")

# Close the original image
image.close()
ArtemSokolov commented 11 months ago

Hi Elena,

Since you're already in Python, it might be a lot easier to just run the Python version of BaSiC (https://basicpy.readthedocs.io/en/latest/) on tiles without overlap, then assemble everything back into the original grid.

Modify your for loop to store tiles in a list:

tiles = []
for i in range(num_rows):
    for j in range(num_columns):

    # cut out tiles without overlap

    tile = image.crop((x1, y1, x2, y2))
    tiles.append(tile)

Then you can pass the whole list to BaSiC with:

from basicpy import BaSiC
import numpy as np

basic = BaSiC(get_darkfield=True)
basic.fit(np.stack(tiles))
corrected_tiles = basic.transform(np.stack(tiles))

# After this, reassemble corrected_tiles based on the original grid
Elena983 commented 11 months ago

Thank you for answering quickly Could you have a look at my code? It says, "Reweighting did not converge."

from PIL import Image
from basicpy import BaSiC
import numpy as np

# Load the TIFF image
image = Image.open("/Users/me/Desktop/CRC.ome.tif")

# Define the tile size (e.g., 600x600 pixels)
tile_size = (600, 600)

# The effective tile size
effective_tile_width = tile_size[0]
effective_tile_height = tile_size[1]

# The number of rows and columns
num_rows = image.height // effective_tile_height
num_columns = image.width // effective_tile_width

# An empty list to store tiles
tiles = []

# Iterate over the image 
for i in range(num_rows):
    for j in range(num_columns):
        x1 = j * effective_tile_width
        y1 = i * effective_tile_height
        x2 = x1 + tile_size[0]
        y2 = y1 + tile_size[1]

        if x2 > image.width:
            x1 -= (x2 - image.width)
            x2 = image.width
        if y2 > image.height:
            y1 -= (y2 - image.height)
            y2 = image.height

        tile = image.crop((x1, y1, x2, y2))

        tiles.append(tile)

basic = BaSiC(get_darkfield=True)
basic.fit(np.stack(tiles))
corrected_tiles = basic.transform(np.stack(tiles))

# Iterate over the corrected tiles and reassemble the image
tile_index = 0
for i in range(num_rows):
    for j in range(num_columns):
        x1 = j * effective_tile_width
        y1 = i * effective_tile_height
        x2 = x1 + effective_tile_width
        y2 = y1 + effective_tile_height

        # The last tiles in each row and column don't go beyond the image dimensions
        x2 = min(x2, image.width)
        y2 = min(y2, image.height)

        corrected_tile = Image.fromarray(corrected_tiles[tile_index])
        output_image.paste(corrected_tile, (x1, y1))
        tile_index += 1

# The corrected image
output_image.save("/Users/me/Desktop/corrected_image.tif")
ArtemSokolov commented 11 months ago

Hi @Elena983,

How did you decide on 600x600 for your tile size? BaSiC works best when the tiles share the same illumination profile, and the error message suggests that your tiles don't.

Typically, the tiles would be darker along the edges, which results in a dark grid in the fully stitched image (see Figure 1a in https://www.nature.com/articles/ncomms14836). Does your choice of 600x600 line up with the dark grid in your image?

-Artem

Elena983 commented 11 months ago

Hi Artem

I have no lack, as I received again the same error.


from PIL import Image
from basicpy import BaSiC
import numpy as np

# Load the TIFF image
image = Image.open("/Users/melnike/Desktop/CRC.ome.tif")
plt.imshow(image)

# Define the tile size (e.g., 600x600 pixels)
tile_size = (647, 606)

# The effective tile size
effective_tile_width = tile_size[0]
effective_tile_height = tile_size[1]

# The number of rows and columns
num_rows = image.height // effective_tile_height
num_columns = image.width // effective_tile_width

# Initialize an empty list to store tiles
tiles = []

# Iterate over the image
for i in range(num_rows):
    for j in range(num_columns):
        x1 = j * effective_tile_width
        y1 = i * effective_tile_height
        x2 = x1 + tile_size[0]
        y2 = y1 + tile_size[1]

        # Adjust for the dark grid
        x1 += 5  # Adjust as needed to align with the dark grid
        y1 += 5  # Adjust as needed to align with the dark grid
        x2 -= 5  # Adjust as needed to align with the dark grid
        y2 -= 5  # Adjust as needed to align with the dark grid

        # Ensure the adjusted coordinates are within the image boundaries
        x1 = max(0, x1)
        y1 = max(0, y1)
        x2 = min(image.width, x2)
        y2 = min(image.height, y2)

        tile = image.crop((x1, y1, x2, y2))
        tiles.append(tile)

# Initialize BaSiC with darkfield correction
basic = BaSiC(get_darkfield=True)

# Fit BaSiC with the tiles
basic.fit(np.stack(tiles))

# Correct the tiles
corrected_tiles = basic.transform(np.stack(tiles))

# Create an output image to paste the corrected tiles
output_image = Image.new("RGB", image.size)

# Iterate over the corrected tiles and reassemble the image
tile_index = 0
for i in range(num_rows):
    for j in range(num_columns):
        x1 = j * effective_tile_width
        y1 = i * effective_tile_height
        x2 = x1 + effective_tile_width
        y2 = y1 + effective_tile_height

        # The last tiles in each row and column don't go beyond the image dimensions
        x2 = min(x2, image.width)
        y2 = min(y2, image.height)

        corrected_tile = Image.fromarray(corrected_tiles[tile_index])
        output_image.paste(corrected_tile, (x1, y1))
        tile_index += 1

# The corrected image
output_image.save("/Users/melnike/Desktop/corrected_image.tif")
plt.imshow(output_image)

iScreen Shoter - 20231108171503526

What other options exist for the BaSiC (not py)?

clarenceyapp commented 11 months ago

Hi @Elena983 can you confirm how you are selecting the tile sizes? I see you went from 600x600 to something else. How are you picking these?

jmuhlich commented 11 months ago

Can you tell us what microscope (manufacturer and model, and ideally the manufacturer and model of the camera device) you used to acquire the original image? That could help determine the correct dimensions for untiling.

I am not sure the BaSiC algorithm will necessarily work properly on tiles that have been split out from a previously stitched image -- the method used for blending the tile overlaps will probably affect how BaSiC's statistical model of illumination errors works.

Elena983 commented 11 months ago

Hi @Elena983 can you confirm how you are selecting the tile sizes? I see you went from 600x600 to something else. How are you picking these?

Hi I just divided the height and width by 10 and then received tile_size

Elena983 commented 11 months ago

Can you tell us what microscope (manufacturer and model, and ideally the manufacturer and model of the camera device) you used to acquire the original image? That could help determine the correct dimensions for untiling.

I am not sure the BaSiC algorithm will necessarily work properly on tiles that have been split out from a previously stitched image -- the method used for blending the tile overlaps will probably affect how BaSiC's statistical model of illumination errors works.

Hi We have a Comet from Lunaphore with a wide-field microscope. I need to check which model they use if this helps. I asked the company whether I could have tiles after a run, but no, they are deleted after stitching

clarenceyapp commented 11 months ago

Hi @Elena983 , BaSiC cannot run on tiles that have been cut using some arbitrary number. The tiles that go into BaSiC need to have been illuminated by the same underlying flat field pattern. If you take a stack of tiles that have been cut by some random size, then the pattern will be different for each tile and then BaSiC cannot estimate what the common pattern is across these tiles (please see @ArtemSokolov 's earlier message that references the BaSiC paper). As @jmuhlich mentioned, it's much better (maybe even necessary) to use raw unstitched tiles. Can you check with your Lunaphore representative if they can pause the processing steps before stitching? We demo'd a Lunaphore Comet machine before and they were able to stop just before stitching for us as a one-off test. We gave them feedback that the MCMICRO modules (including BaSiC) worked well if they did this.

Elena983 commented 11 months ago

Hi @Elena983 , BaSiC cannot run on tiles that have been cut using some arbitrary number. The tiles that go into BaSiC need to have been illuminated by the same underlying flat field pattern. If you take a stack of tiles that have been cut by some random size, then the pattern will be different for each tile and then BaSiC cannot estimate what the common pattern is across these tiles (please see @ArtemSokolov 's earlier message that references the BaSiC paper). As @jmuhlich mentioned, it's much better (maybe even necessary) to use raw unstitched tiles.

Can you check with your Lunaphore representative if they can pause the processing steps before stitching? We demo'd a Lunaphore Comet machine before and they were able to stop just before stitching for us as a one-off test. We gave them feedback that the MCMICRO modules (including BaSiC) worked well if they did this.

Hi Clarence I will try to deploy them, thank you Do you have any example of how to organize pipeline input with the tiles then?

clarenceyapp commented 11 months ago

Hi @Elena983 , each cycle was in its own folder called 'Cycle_00*'. Inside each cycle folder, there is a tiff file for each channel and each tile. So if you have 4 channels and 100 tiles, you will have 400 files. The filename convention was like this: Ch_1_Slice_01_Row_01_Col_01.tiff for the first tile and channel.

Sorry but I now remember that I had to run BaSiC and ASHLAR manually as standalone tools to get this to work. Because the tiff files do not have a master index file, I had to run BaSiC through ImageJ, not MCMICRO. For ASHLAR, I used the fileseries loader to load the cycle subfolders and flat field profiles from BaSiC. The resulting stitched images looked fine after that.