Commit 38b9513f authored by Andrey Filippov's avatar Andrey Filippov
Browse files

Merge branch 'master' of git@git.elphel.com:Elphel/python3-imagej-tiff.git

parents cdd98c34 091eaecb
Loading
Loading
Loading
Loading
+92 −86
Original line number Original line Diff line number Diff line
@@ -2,7 +2,7 @@


'''
'''
/**
/**
 * @file imagej_tiff_saver.py
 * @file imagej_tiffwriter.py
 * @brief save tiffs for imagej (1.52d+) - with stacks and hyperstacks
 * @brief save tiffs for imagej (1.52d+) - with stacks and hyperstacks
 * @par <b>License</b>:
 * @par <b>License</b>:
 *  This program is free software: you can redistribute it and/or modify
 *  This program is free software: you can redistribute it and/or modify
@@ -29,47 +29,88 @@ Usage example:
  import imagej_tiffwriter
  import imagej_tiffwriter
  import numpy as np
  import numpy as np


  # have a few images in the form of numpy arrays
  Have a few images in the form of numpy arrays np.float32:
  # make sure to stack them as:
  - (h,w)
  #   - (t,z,h,w,c)
  - (n,h,w)
  #   - (z,h,w,c)
  #   - (h,w,c)
  #   - (h,w)


  imagej_tiffwriter.save(path,images)
  Labels can be provided as a list: ['label1','label2', etc.]
  No list length check against number of images

  imagej_tiffwriter.save(path,images,labels)


'''
'''


from PIL import Image, TiffImagePlugin
import numpy as np
import numpy as np
import struct
import tifffile
import math
import math


# DO NOT USE?
# from here: https://stackoverflow.com/questions/50258287/how-to-specify-colormap-when-saving-tiff-stack
# thing is the old ImageJs <1.52d poorly handle tags directories or something like that
def imagej_metadata_tags(metadata, byteorder):
def __get_IJ_IFD(t,z,c):
    """Return IJMetadata and IJMetadataByteCounts tags from metadata dict.


  ifd = TiffImagePlugin.ImageFileDirectory_v2()
    The tags can be passed to the TiffWriter.save function as extratags.


  ijheader = [
    """
    'ImageJ=',
    header = [{'>': b'IJIJ', '<': b'JIJI'}[byteorder]]
    'hyperstack=true',
    bytecounts = [0]
    'images='+str(t*z*c),
    body = []
    'channels='+str(c),

    'slices='+str(z),
    def writestring(data, byteorder):
    'frames='+str(t),
        return data.encode('utf-16' + {'>': 'be', '<': 'le'}[byteorder])
    'loop=false'

  ]
    def writedoubles(data, byteorder):

        return struct.pack(byteorder+('d' * len(data)), *data)
  ifd[270] = ("\n".join(ijheader)+"\n")

    def writebytes(data, byteorder):
        return data.tobytes()

    metadata_types = (
        ('Info', b'info', 1, writestring),
        ('Labels', b'labl', None, writestring),
        ('Ranges', b'rang', 1, writedoubles),
        ('LUTs', b'luts', None, writebytes),
        ('Plot', b'plot', 1, writebytes),
        ('ROI', b'roi ', 1, writebytes),
        ('Overlays', b'over', None, writebytes))

    for key, mtype, count, func in metadata_types:
        if key not in metadata:
            continue
        if byteorder == '<':
            mtype = mtype[::-1]
        values = metadata[key]
        if count is None:
            count = len(values)
        else:
            values = [values]
        header.append(mtype + struct.pack(byteorder+'I', count))
        for value in values:
            data = func(value, byteorder)
            body.append(data)
            bytecounts.append(len(data))

    body = b''.join(body)
    header = b''.join(header)
    data = header + body
    bytecounts[0] = len(header)
    bytecounts = struct.pack(byteorder+('I' * len(bytecounts)), *bytecounts)
    return ((50839, 'B', len(data), data, True),
            (50838, 'I', len(bytecounts)//4, bytecounts, True))


  #is_hyperstack = 'true' if len(shape)>1 else 'false'
  #if (len(shape)>0):


  return ifd
#def save(path,images,force_stack=False,force_hyperstack=False):
def save(path,images,labels=None,label_prefix="Label "):


  '''
    labels a list or None
  '''


#def save(path,images,force_stack=False,force_hyperstack=False):
  '''
def save(path,images):
    Expecting:
    (h,w),
    (n,h,w) - just create a simple stack
  '''


  # Got images, analyze shape:
  # Got images, analyze shape:
  #   - possible formats (c == depth):
  #   - possible formats (c == depth):
@@ -78,46 +119,30 @@ def save(path,images):
  #     -- (h,w,c)
  #     -- (h,w,c)
  #     -- (h,w)
  #     -- (h,w)



  # 0 or 1 images.shapes are not handled
  # 0 or 1 images.shapes are not handled
  #
  #
  # (h,w)
  # (h,w)
  if len(images.shape)==2:
  if len(images.shape)==2:
    images = images[np.newaxis,...]


    image = Image.fromarray(images)
  # now the shape length is 3
    image.save(path)

  elif len(images.shape)>2:

    h,w,c  = images.shape[-3:]

  if len(images.shape)==3:
  if len(images.shape)==3:
      images = np.reshape(images,(1,h,w,c))
    # tifffile treats shape[0] as channel, need to expand to get labels displayed

    #images = images[images.shape[0],np.newaxis,images.shape[1],images.shape[2]]
    z = images.shape[-4]
    images = np.reshape(images,(images.shape[0],1,images.shape[1],images.shape[2]))


    if len(images.shape)==4:
    labels_list = []
      images = np.reshape(images,(1,z,h,w,c))
    if labels is None:

      for i in range(images.shape[0]):
    t  = images.shape[-5]
        labels_list.append(label_prefix+str(i+1))

    c_axis = -1

    if c==1:
      split_channels = images
    else:
    else:
      channels = np.array(np.split(images,c,axis=c_axis))
      labels_list = labels
      split_channels = np.concatenate(channels,axis=-3)

    images_flat = np.reshape(split_channels,(-1,h,w))


    imlist = []
    ijtags = imagej_metadata_tags({'Labels':labels_list}, '<')
    for i in range(images_flat.shape[0]):
      imlist.append(Image.fromarray(images_flat[i]))


    imlist[0].save(path,save_all=True,append_images=imlist[1:])
    with tifffile.TiffWriter(path, bigtiff=False,imagej=True) as tif:
    # thing is the old ImageJs <1.52d poorly handle tags directories or something like that
      for i in range(images.shape[0]):
    #imlist[0].save(path,save_all=True,append_images=imlist[1:],tiffinfo=__get_IJ_IFD(t,z,c))
        tif.save(images[i], metadata={'version':'1.11a','loop': False}, extratags=ijtags)


# Testing
# Testing
if __name__ == "__main__":
if __name__ == "__main__":
@@ -129,34 +154,15 @@ if __name__ == "__main__":
  hw = hamming_window
  hw = hamming_window


  NT = 5
  NT = 5
  NC = 2
  NZ = 3
  NX = 512
  NX = 512
  NY = 512
  NY = 512


  images = np.empty((NT,NZ,NY,NX,NC))
  images = np.empty((NT,NY,NX),np.float32)


  import time
  import time
  print(str(time.time())+": Generating test images")
  print(str(time.time())+": Generating test images")
  for t in range(NT):
  for t in range(NT):
    for z in range(NZ):
    images[t,:,:] = np.array([[(255-t*25)*hw(i,512)*hw(j,512) for i in range(NX)] for j in range(NY)],np.float32)
      for c in range(NC):
        images[t,z,:,:,c] = np.array([[(255-t*25)*hw(i,512)*hw(j,512) for i in range(NX)] for j in range(NY)],np.float32)
  print(str(time.time())+": Test images generated")
  print(str(time.time())+": Test images generated")
  print("Images shape: "+str(images.shape))
  print("Images shape: "+str(images.shape))

  v = save("tiffwriter_test.tiff",images)
  print("5D run")
  v = save("result_5D.tiff",images)
  print("4D run")
  v = save("result_4D.tiff",images[0])
  print("3D run")
  v = save("result_3D.tiff",images[0,0])
  print("3D run, 1 channel")
  tmp_images = images[0,0,:,:,0]
  v = save("result_3D1C.tiff",tmp_images[:,:,np.newaxis])





imagej_tiffwriter_test.py

deleted100644 → 0
+0 −240
Original line number Original line Diff line number Diff line
#!/usr/bin/env python3

'''
/**
 * @file imagej_tiff_saver.py
 * @brief save tiffs for imagej (1.52d+) - with stacks and hyperstacks
 * @par <b>License</b>:
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
'''

__copyright__ = "Copyright 2018, Elphel, Inc."
__license__   = "GPL-3.0+"
__email__     = "oleg@elphel.com"

'''
Usage example:
  import imagej_tiffwriter
  import numpy as np

  # have a few images in the form of numpy arrays
  # make sure to stack them as:
  #   - (t,z,h,w,c)
  #   - (z,h,w,c)
  #   - (h,w,c)
  #   - (h,w)

  imagej_tiffwriter.save(path,images)

'''

from PIL import Image, TiffImagePlugin
import numpy as np
import math

# DO NOT USE?
# thing is the old ImageJs <1.52d poorly handle tags directories or something like that
def __get_IJ_IFD(t,z,c):

  #ifd = TiffImagePlugin.ImageFileDirectory_v2(ifh=b"MM*\x00\x00\x00\x00\x00",prefix= b"MM")
  ifd = TiffImagePlugin.ImageFileDirectory_v2(prefix= b"MM")
  #ifd = TiffImagePlugin.ImageFileDirectory_v2()
  #ifd = TiffImagePlugin.ImageFileDirectory(prefix='MM')

  ijheader = [
    'ImageJ=',
    'hyperstack=true',
    'images='+str(t*z*c),
    'channels='+str(c),
    'slices='+str(z),
    'frames='+str(t),
    'loop=false'
  ]

  ifd[270] = ("\n".join(ijheader)+"\n")

  ijlabl = [
    b'IJIJinfo\x00\x00\x00\x01labl\x00\x00\x00\x05',
    '<info></info>'.encode('UTF-16-LE'),
    'img1'.encode('UTF-16-LE'),
    'img2'.encode('UTF-16-LE'),
    'img3'.encode('UTF-16-LE'),
    'img4'.encode('UTF-16-LE'),
    'img5'.encode('UTF-16-LE')
  ]

  #for i in range()

  #ifd[50838] = (20,10)
  #ifd[50839] = (20,10)
  ifd_50838_list = []
  ifd_50839 = b"".join(ijlabl)

  for label in ijlabl:
    ifd_50838_list.append(len(label))

  ifd_50838 = tuple(ifd_50838_list)
  #print(ifd_50838)
  #print(ifd_50839)

  #is_hyperstack = 'true' if len(shape)>1 else 'false'
  #if (len(shape)>0):

  ifd[50838] = ifd_50838
  ifd[50839] = ifd_50839

  # override
  #tif = Image.open("test.tiff")
  #tag_50838 = tif.tag[50838]
  #tag_50839 = tif.tag[50839]
  #tag_270 = tif.tag[270]

  #print(tag_50839)

  #ifd[270] = tag_270
  #ifd[50838] = tag_50838
  #ifd[50839] = tag_50839

  return ifd


#def save(path,images,force_stack=False,force_hyperstack=False):
def save(path,images):

  # Got images, analyze shape:
  #   - possible formats (c == depth):
  #     -- (t,z,h,w,c)
  #     -- (t,h,w,c), t or z does not matter
  #     -- (h,w,c)
  #     -- (h,w)


  # 0 or 1 images.shapes are not handled
  #
  # (h,w)
  if len(images.shape)==2:

    image = Image.fromarray(images)
    image.save(path)

  elif len(images.shape)>2:

    h,w,c  = images.shape[-3:]

    if len(images.shape)==3:
      images = np.reshape(images,(1,h,w,c))

    z = images.shape[-4]

    if len(images.shape)==4:
      images = np.reshape(images,(1,z,h,w,c))

    t  = images.shape[-5]

    c_axis = -1

    if c==1:
      split_channels = images
    else:
      channels = np.array(np.split(images,c,axis=c_axis))
      split_channels = np.concatenate(channels,axis=-3)

    images_flat = np.reshape(split_channels,(-1,h,w))

    imlist = []
    for i in range(images_flat.shape[0]):
      imlist.append(Image.fromarray(images_flat[i]))

    #imlist[0].save(path,save_all=True,append_images=imlist[1:])
    # thing is the old ImageJs <1.52d poorly handle tags directories or something like that
    imlist[0].save(path,save_all=True,append_images=imlist[1:],tiffinfo=__get_IJ_IFD(t,z,c))

# Testing
if __name__ == "__main__":

  def hamming_window(x,N):
    y = 0.54 - 0.46*math.cos(2*math.pi*x/(N-1))
    return y

  hw = hamming_window

  NT = 5
  NC = 1
  NZ = 1
  NX = 2916
  NY = 2178

  images = np.empty((NT,NZ,NY,NX,NC))

  import time
  print(str(time.time())+": Generating test images")
  for t in range(NT):
    for z in range(NZ):
      for c in range(NC):
        images[t,z,:,:,c] = np.array([[(255-t*25)*hw(i,512)*hw(j,512) for i in range(NX)] for j in range(NY)],np.float32)
  print(str(time.time())+": Test images generated")
  print("Images shape: "+str(images.shape))

  print("1D run")
  imgs = images[:,0,:,:,0]
  print(imgs.shape)

  imlist = []
  for i in range(imgs.shape[0]):
    tmp = Image.fromarray(imgs[i])
    #tmp.mode = "I;32BS"
    imlist.append(tmp)

  TiffImagePlugin.DEBUG = True
  TiffImagePlugin.WRITE_LIBTIFF = False

  #import sys
  #sys.byteorder = "big"
  #imlist[0].mode = "I;16B"
  #print("encoderconfig")
  #print(imlist[0].encoderconfig)

  imlist[0].save("result_1D.tiff",save_all=True,append_images=imlist[1:],tiffinfo=__get_IJ_IFD(NT,NZ,NC))
  #v = save("result_1D.tiff",imgs)

  #tif = Image.open("result_1D.tiff")
  #tag_50838 = tif.tag[50838]
  #tag_50839 = tif.tag[50839]
  #tag_270 = tif.tag[270]

  #print(tag_50839)

  #tif = Image.open("test.tiff")

  #tif.save("test_saved.tiff")

  #print("5D run")
  #v = save("result_5D.tiff",images)
  #print("4D run")
  #v = save("result_4D.tiff",images[0])
  #print("3D run")
  #v = save("result_3D.tiff",images[0,0])

  #print("3D run, 1 channel")
  #tmp_images = images[0,0,:,:,0]
  #v = save("result_3D1C.tiff",tmp_images[:,:,np.newaxis])

  # open test