コード例 #1
0
ファイル: test.py プロジェクト: nimne/readlif
    def test_get_plane_on_xz_img(self):
        ref = Image.open("./tests/tiff/xz_c0_t0.tif")
        obj = LifFile("./tests/testdata_2channel_xz.lif").get_image(0)
        test = obj.get_plane(c=0, requested_dims={4: 0})
        self.assertEqual(test.tobytes(), ref.tobytes())

        ref2 = Image.open("./tests/tiff/xz_c1_t8.tif")
        # 3: z
        # 4: t
        test2 = obj.get_plane(c=1, requested_dims={4: 8})
        self.assertEqual(test2.tobytes(), ref2.tobytes())
コード例 #2
0
ファイル: test.py プロジェクト: jojoelfe/readlif
    def test_image_loading(self):
        # order = c, z, t
        test_array = [[0, 0, 0], [0, 2, 0], [0, 2, 2], [1, 0, 0]]
        for i in test_array:
            c = str(i[0])
            z = str(i[1])
            t = str(i[2])
            ref = Image.open("./tests/tiff/c" + c + "z" + z + "t" + t + ".tif")

            obj = LifFile("./tests/xyzt_test.lif").get_image(0)
            test = obj.get_frame(z=z, t=t, c=c)
            self.assertEqual(test.tobytes(), ref.tobytes())
コード例 #3
0
ファイル: test.py プロジェクト: nimne/readlif
    def test_get_plane_on_normal_img(self):
        # order = c, z, t
        test_array = [[0, 0, 0], [0, 2, 0], [0, 2, 2], [1, 0, 0]]
        for i in test_array:
            c = str(i[0])
            z = str(i[1])
            t = str(i[2])
            ref = Image.open("./tests/tiff/c" + c + "z" + z + "t" + t + ".tif")

            obj = LifFile("./tests/xyzt_test.lif").get_image(0)
            # 3: z
            # 4: t
            test = obj.get_plane(c=c, requested_dims={3: z, 4: t})
            self.assertEqual(test.tobytes(), ref.tobytes())
コード例 #4
0
 def test_not_implemented_mosaic(self):
     import os
     # Can't test this in CI, don't have permission to publish this
     if os.path.exists("./tests/private/tile_002.lif"):
         with self.assertRaises(NotImplementedError):
             LifFile("./tests/private/tile_002.lif").get_image(0)
     pass
コード例 #5
0
    def _read_immediate(self) -> np.ndarray:
        # Get image dims indicies
        lif = LifFile(filename=self._file)
        image_dim_indices = LifReader._dims_shape(lif=lif)

        # Catch inconsistent scene dimension sizes
        if len(image_dim_indices) > 1:
            # Choose the provided scene
            log.info(
                f"File contains variable dimensions per scene, "
                f"selected scene: {self.specific_s_index} for data retrieval."
            )
            data, _ = LifReader._get_array_from_offset(
                self._file,
                self._chunk_offsets,
                self._chunk_lengths,
                self.metadata,
                {Dimensions.Scene: self.specific_s_index},
            )

        else:
            # If the list is length one that means that all the scenes in the image
            # have the same dimensions
            # Read all data in the image
            data, _ = LifReader._get_array_from_offset(
                self._file, self._chunk_offsets, self._chunk_lengths, self.metadata,
            )

        return data
コード例 #6
0
    def test_not_that_many_images(self):
        obj = LifFile("./tests/xyzt_test.lif")
        with self.assertRaises(ValueError):
            obj.get_image(10)

        image = obj.get_image(0)
        with self.assertRaises(ValueError):
            image.get_frame(z=10, t=0, c=0)

        with self.assertRaises(ValueError):
            image.get_frame(z=0, t=10, c=0)

        with self.assertRaises(ValueError):
            image.get_frame(z=0, t=0, c=10)

        with self.assertRaises(ValueError):
            image._get_item(100)
コード例 #7
0
ファイル: test.py プロジェクト: jojoelfe/readlif
    def test_private_images_16bit(self):
        # These tests are for images that are not public.
        # These images will be pulled from a protected web address
        # during CI testing.
        if os.environ.get('READLIF_TEST_DL_PASSWD') is not None \
                and os.environ.get('READLIF_TEST_DL_PASSWD') != "":
            downloadPrivateFile("16bit.lif")
            downloadPrivateFile("i1c0z2_16b.tif")
            # Note - readlif produces little endian files,
            # ImageJ makes big endian files for 16bit by default
            obj = LifFile("./tests/private/16bit.lif").get_image(1)

            self.assertEqual(obj.bit_depth[0], 12)

            ref = Image.open("./tests/private/i1c0z2_16b.tif")
            test = obj.get_frame(z=2, c=0)

            self.assertEqual(test.tobytes(), ref.tobytes())
        else:
            print("\nSkipped private test for 16-bit images\n")
コード例 #8
0
ファイル: test.py プロジェクト: jojoelfe/readlif
    def test_private_images_mosaic(self):
        # These tests are for images that are not public.
        # These images will be pulled from a protected web address
        # during CI testing.
        if os.environ.get('READLIF_TEST_DL_PASSWD') is not None\
                and os.environ.get('READLIF_TEST_DL_PASSWD') != "":
            downloadPrivateFile("tile_002.lif")
            downloadPrivateFile("i0c1m2z0.tif")

            obj = LifFile("./tests/private/tile_002.lif").get_image(0)
            self.assertEqual(obj.dims.m, 165)

            m_list = [i for i in obj.get_iter_m()]
            self.assertEqual(len(m_list), 165)

            ref = Image.open("./tests/private/i0c1m2z0.tif")
            test = obj.get_frame(c=1, m=2)

            self.assertEqual(test.tobytes(), ref.tobytes())

        else:
            print("\nSkipped private test for mosaic images\n")
コード例 #9
0
def update_image():

    global CENTERS_NO

    try:
        session['stack'] = int(request.args.get('stack'))
        session['zframe'] = int(request.args.get('zframe'))
        session['channel'] = int(request.args.get('channel'))
        session['bg_thresh'] = int(request.args.get('bg_thresh'))
        session['adaptive_thresh'] = int(request.args.get('adaptive_thresh'))
        session['erosion'] = int(request.args.get('erosion'))
        session['dilation'] = int(request.args.get('dilation'))
        session['min_dist'] = int(request.args.get('min_dist'))
        session['gamma'] = float(request.args.get('gamma'))
        session['gain'] = float(request.args.get('gain'))

        # img = cv2.imread('./test.jpg', cv2.IMREAD_GRAYSCALE)

        lif_file = LifFile(session['file_path'])
        img_list = [i for i in lif_file.get_iter_image()]
        img, CENTERS_NO = generate_image(img_list, session['stack'], session['zframe'], session['channel'],
                                         session['bg_thresh'], session['adaptive_thresh'],
                                         session['erosion'], session['dilation'],
                                         session['min_dist'], session['gamma'], session['gain'],
                                         connectivity=CONNECTIVITY, circle_radius=CIRCLE_RADIUS)


        (flag, encodedImage) = cv2.imencode(".jpg", img)

        response = base64.b64encode(encodedImage)

        return response

    except Exception as e:
        logger.error(e)
        resp = {'message': 'Failed'}
        return make_response(jsonify(resp), 400)
コード例 #10
0
    def __init__(self, lif_file):
        """
            just specify path to lif-file in constructor
        """
        self.lif_path = Path(lif_file)

        self.filename = self.lif_path.stem
        self.filename_full = self.lif_path.name
        self.outdir = self.lif_path.parent / self.filename
        self.outdir.mkdir(parents=True, exist_ok=True)

        self._release_logger()
        logging.basicConfig(filename=self.outdir /
                            (self.filename + '_extractlog.log'),
                            filemode='w',
                            level=logging.DEBUG,
                            format='%(message)s')

        print("#########################")
        print("reading file", self.filename_full)

        self.lifhandler = LifFile(self.lif_path)

        # categories under which images "series" will get sorted later
        self.export_entries = ["xy", "xyc", "xyz", "xyt"
                               ]  # currently supported entrytypes for export
        self.nonexport_entries = [
            "xyct", "xycz", "xyzt", "xyczt", "envgraph", "MAF", "other"
        ]
        self.grouped_img = {key: [] for key in self.export_entries}
        self.grouped_img.update({key: [] for key in self.nonexport_entries})

        self._get_overview()
        self._log_overview()
        self.print_overview()
        self._write_xml()
コード例 #11
0
def define_lif_pipeline(input_def):
    # defines a processing pipeline starting with a .lif file
    fpath_in_lif = glob("{}/*lif".format(input_def["root"]))[0] # use only one lif file prer folder!!!
    print("Analysing",fpath_in_lif)
    # load lif file
    lifobj = LifFile(fpath_in_lif)
    if input_def["split_z"]:
        input_def["input_type"] = ".tif" # further processing of the lif file is then based on the tiff folder!!!!
        new_folder = os.path.join(input_def["root"],"tiff")
        if not os.path.exists(new_folder):
            os.makedirs(new_folder)
            print("Creating folder..." + new_folder)
        for ex_ch in input_def["export_multiple_ch"]:
            split_lif_z_to_tiff(lifobj,input_def["root"],input_def["z_step"],ex_ch,input_def["rigth_size"],input_def["mydtype"])
    return lifobj,input_def
コード例 #12
0
    def _dims_shape(lif: LifFile):
        """
        Get the dimensions for the opened file from the binary data (not the metadata)

        Parameters
        ----------
        lif: LifFile

        Returns
        -------
        list[dict]
            A list of dictionaries containing Dimension / depth. If the shape is
            consistent across Scenes then the list will have only one Dictionary. If
            the shape is inconsistent the the list will have a dictionary for each
            Scene. A consistently shaped file with 3 scenes, 7 time-points
            and 4 Z slices containing images of (h,w) = (325, 475) would return
            [
             {'S': (0, 3), 'T': (0,7), 'X': (0, 475), 'Y': (0, 325), 'Z': (0, 4)}
            ].
            The result for a similarly shaped file but with different number of time
            points per scene would yield
            [
             {'S': (0, 1), 'T': (0,8), 'X': (0, 475), 'Y': (0, 325), 'Z': (0, 4)},
             {'S': (1, 2), 'T': (0,6), 'X': (0, 475), 'Y': (0, 325), 'Z': (0, 4)},
             {'S': (2, 3), 'T': (0,7), 'X': (0, 475), 'Y': (0, 325), 'Z': (0, 4)}
            ]

        """
        shape_list = [
            {
                Dimensions.Time: (0, img.nt),
                Dimensions.Channel: (0, img.channels),
                Dimensions.SpatialZ: (0, img.nz),
                Dimensions.SpatialY: (0, img.dims[1]),
                Dimensions.SpatialX: (0, img.dims[0]),
            }
            for idx, img in enumerate(lif.get_iter_image())
        ]
        consistent = all(elem == shape_list[0] for elem in shape_list)
        if consistent:
            shape_list[0][Dimensions.Scene] = (0, len(shape_list))
            shape_list = [shape_list[0]]
        else:
            for idx, lst in enumerate(shape_list):
                lst[Dimensions.Scene] = (idx, idx + 1)
        return shape_list
コード例 #13
0
    def test_iterators(self):
        images = [i for i in LifFile("./tests/xyzt_test.lif").get_iter_image()]
        self.assertEqual(len(images), 1)

        obj = LifFile("./tests/xyzt_test.lif").get_image(0)

        c_list = [i for i in obj.get_iter_c()]
        self.assertEqual(len(c_list), 2)

        t_list = [i for i in obj.get_iter_t()]
        self.assertEqual(len(t_list), 3)

        z_list = [i for i in obj.get_iter_z()]
        self.assertEqual(len(z_list), 3)
コード例 #14
0
def upload_file():

    uploaded_file = request.files['imagefile']
    if uploaded_file and allowed_file(uploaded_file.filename):
        original_name = uploaded_file.filename
        file_name = session['uid'] + "." + original_name.rsplit('.', 1)[1].lower()
        session['file_path'] = os.path.join(app.config['UPLOAD_FOLDER'], file_name)
        session['export_dir'] = os.path.join(app.config['EXPORT_FOLDER'], session['uid'])

        if os.path.exists(session['file_path']):
            os.remove(session['file_path'])

        uploaded_file.save(session['file_path'])

        try:
            lif_file = LifFile(session['file_path'])
            img_list = lif_file.image_list

            stack_dict_list = []
            stack_list = []

            for img in img_list:
                img_name = ''.join(e for e in img['name'] if (e.isalnum() or e == ' '))
                stack_list.append(img_name)
                c_list = [i for i in range(img['channels'])]
                z_list = [i for i in range(img['dims'].z)]
                stack_dict_list.append({'Z_LIST': z_list, 'C_LIST': c_list})

            session['stack_list'] = stack_list
            session['stack_dict_list'] = stack_dict_list

            # session['stack_list'] = [1,2]
            # session['stack_dict_list'] = [{'Z_LIST':[1,2,3,4], 'C_LIST':[1,2,3,4]}, {'Z_LIST':[1,2], 'C_LIST':[1,2]}]

            return render_template('index.html', stack_list=session['stack_list'],
                                   stack_dict_list=session['stack_dict_list'])

        except Exception as e:
            logger.error(e)
            return render_template('index.html', invalid_feedback="Invalid file - Please check if the image file is valid", stack_list=[], stack_dict_list = [])
    else:
        return render_template('index.html', invalid_feedback="Invalid file - Please check if file exists and is in correct format", stack_list = [], stack_dict_list = [])
コード例 #15
0
    def __init__(
        self,
        data: types.FileLike,
        chunk_by_dims: List[str] = [
            Dimensions.SpatialZ,
            Dimensions.SpatialY,
            Dimensions.SpatialX,
        ],
        S: int = 0,
        **kwargs,
    ):
        # Run super init to check filepath provided
        super().__init__(data, **kwargs)

        # Store parameters needed for _daread
        self.chunk_by_dims = chunk_by_dims
        self.specific_s_index = S
        lif = LifFile(filename=self._file)
        #  _chunk_offsets is a list of ndarrays
        # (only way I could deal with inconsistent scene shape)
        self._chunk_offsets, self._chunk_lengths = LifReader._compute_offsets(lif=lif)
コード例 #16
0
ファイル: test.py プロジェクト: jojoelfe/readlif
    def test_iterators(self):
        images = [i for i in LifFile("./tests/xyzt_test.lif").get_iter_image()]
        self.assertEqual(len(images), 1)

        obj = LifFile("./tests/xyzt_test.lif").get_image(0)
        self.assertEqual(repr(obj), "'LifImage object with "
                                    "dimensions: "
                                    "Dims(x=1024, y=1024, z=3, t=3, m=1)'")

        c_list = [i for i in obj.get_iter_c()]
        self.assertEqual(len(c_list), 2)

        t_list = [i for i in obj.get_iter_t()]
        self.assertEqual(len(t_list), 3)

        z_list = [i for i in obj.get_iter_z()]
        self.assertEqual(len(z_list), 3)
コード例 #17
0
ファイル: track_image.py プロジェクト: nimne/acit
def track_lif(lif_path: str, out_path: str, model: keras.models.Model) -> None:
    """
    Applies ML model (model object) to everything in the lif file.

    This will write a trackmate xml file via the method tm_xml.write_xml(),
    and save output tiff image stacks from the lif file.

    Args:
        lif_path (str): Path to the lif file
        out_path (str): Path to output directory
        model (str): A trained keras.models.Model object

    Returns: None
    """
    print("loading LIF")
    lif_data = LifFile(lif_path)
    print("Iterating over lif")
    for image in lif_data.get_iter_image():
        folder_path = "/".join(str(image.path).strip("/").split('/')[1:])
        path = folder_path + "/" + str(image.name)
        name = image.name

        if os.path.exists(os.path.join(out_path, path + '.tif.xml')) \
           or os.path.exists(os.path.join(out_path, path + '.tif.trackmate.xml')):
            print(str(path) + '.xml' + ' exists, skipping')
            continue

        make_dirs = os.path.join(out_path, folder_path)
        if not os.path.exists(make_dirs):
            os.makedirs(make_dirs)

        print("Processing " + str(path))
        start = time.time()
        # initialize XML creation for this file
        tm_xml = trackmateXML()
        i = 1
        image_out = image.get_frame()  # Initialize the output image
        images_to_append = []
        for frame in image.get_iter_t():
            images_to_append.append(frame)
            np_image = np.asarray(frame.convert('RGB'))
            image_array = np_image[:, :, ::-1].copy()

            tm_xml.filename = name + '.tif'
            tm_xml.imagepath = os.path.join(out_path, folder_path)
            if tm_xml.nframes < i:  # set nframes to the maximum i
                tm_xml.nframes = i
            tm_xml.frame = i
            # preprocess image for network
            image_array = preprocess_image(image_array)
            image_array, scale = resize_image(image_array)

            # process image
            boxes, scores, labels = model.predict_on_batch(
                np.expand_dims(image_array, axis=0))

            # correct for image scale
            boxes /= scale

            # filter the detection boxes
            pre_passed_boxes = []
            pre_passed_scores = []
            for box, score, label in zip(boxes[0], scores[0], labels[0]):
                if score >= 0.2:
                    pre_passed_boxes.append(box.tolist())
                    pre_passed_scores.append(score.tolist())

            passed_boxes, passed_scores = filter_boxes(
                in_boxes=pre_passed_boxes,
                in_scores=pre_passed_scores,
                _passed_boxes=[],
                _passed_scores=[])  # These are necessary

            print("found " + str(len(passed_boxes)) + " cells in " +
                  str(path) + " frame " + str(i))

            # tell the trackmate writer to add the passed_boxes to the final output xml
            tm_xml.add_frame_spots(passed_boxes, passed_scores)
            i += 1
        # write the image to trackmate, prepare for next image
        print("processing time: ", time.time() - start)
        tm_xml.write_xml()
        image_out.save(os.path.join(out_path, path + '.tif'),
                       format="tiff",
                       append_images=images_to_append[1:],
                       save_all=True,
                       compression='tiff_lzw')
コード例 #18
0
class lif_summary:
    """
        class which contains all functions to extract images from lif file
    """
    def __init__(self, lif_file):
        """
            just specify path to lif-file in constructor
        """
        self.lif_path = Path(lif_file)

        self.filename = self.lif_path.stem
        self.filename_full = self.lif_path.name
        self.outdir = self.lif_path.parent / self.filename
        self.outdir.mkdir(parents=True, exist_ok=True)

        self._release_logger()
        logging.basicConfig(filename=self.outdir /
                            (self.filename + '_extractlog.log'),
                            filemode='w',
                            level=logging.DEBUG,
                            format='%(message)s')

        print("#########################")
        print("reading file", self.filename_full)

        self.lifhandler = LifFile(self.lif_path)

        # categories under which images "series" will get sorted later
        self.export_entries = ["xy", "xyc", "xyz", "xyt"
                               ]  # currently supported entrytypes for export
        self.nonexport_entries = [
            "xyct", "xycz", "xyzt", "xyczt", "envgraph", "MAF", "other"
        ]
        self.grouped_img = {key: [] for key in self.export_entries}
        self.grouped_img.update({key: [] for key in self.nonexport_entries})

        self._get_overview()
        self._log_overview()
        self.print_overview()
        self._write_xml()

    def _release_logger(self):
        """ 
            releases old logger instance, called upon init of new lif
            might be useful in e.g. jupyter where object not automatically released after export
        """

        logging.shutdown()  #clear old logger
        logging.getLogger().handlers.clear()

    def _build_query(self, imgentry, query=""):
        """
            constructs cmd to query specified element, takes also care if element is in subfolder
            imgentry = entry from img_list (=dict)
        """
        # procedure works, however a bit cumbersome... better to directly extract more param in readlif?

        path = imgentry[
            "path"]  # subfolders are nested here: projectname/subf1/subf2/
        sfolders = path.split("/")[
            1:-1]  # split by / and take all except first and last
        name = imgentry["name"]

        elquery = 'Element/Children'  # main entrypoint, all images are always children of main element
        for sfolder in sfolders:
            elquery = elquery + f'/Element[@Name="{sfolder}"]/Children'  # build query of all subfolders

        elquery = elquery + f'/Element[@Name="{name}"]'  # attach query for element with specified name
        query = elquery + query
        return query

    def _query_hist(self, imgentry):
        """
            reads out BlackValue and WhiteValue for specific imgentry
            take care, multichannel not implemented yet, will return values of first found entry
        """
        query = self._build_query(imgentry,
                                  "/Data/Image/Attachment/ChannelScalingInfo")

        rootel = self.lifhandler.xml_root
        blackval = float(rootel.find(query).attrib["BlackValue"])
        whiteval = float(rootel.find(query).attrib["WhiteValue"])

        return [blackval, whiteval]

    def _query_chan(self, imgentry):
        """
            reads out used contrast method + filter cube for specific imgentry
            returns as list where each item corresponds to channel
        """

        cquery = self._build_query(
            imgentry, "Data/Image/Attachment/"
            "ATLCameraSettingDefinition/WideFieldChannelConfigurator/WideFieldChannelInfo"
        )
        rootel = self.lifhandler.xml_root
        chan_els = rootel.findall(cquery)
        chaninfo = [
            chan_el.attrib["ContrastingMethodName"] + "_" +
            chan_el.attrib["FluoCubeName"] for chan_el in chan_els
        ]

        return chaninfo

    def _query_timestamp(self, imgentry):
        """ returns acquision date (= first timestamp) of selected entry """
        tsquery = self._build_query(imgentry, "Data/Image/TimeStampList")
        rootel = self.lifhandler.xml_root
        # try:
        ts = rootel.find(tsquery).text
        ts = ts.split(" ")[0]
        # except (AttributeError, TypeError):
        # return None

        # conversion adapted from bioformats
        stampLowStart = max(0, len(ts) - 8)
        stampHighEnd = max(0, stampLowStart)

        stampHigh = ts[0:stampHighEnd]
        stampLow = ts[stampLowStart:]

        low = int(stampLow, 16)
        high = int(stampHigh, 16)

        ticks = (high << 32) | low
        ticks = ticks / 10000

        COBOL_EPOCH = 11644473600000
        ts_unix = int(ticks - COBOL_EPOCH)  # in ms

        return ts_unix

    def _log_img(self, imgentry):
        """
            logs currently exported imagentry to logfile
        """
        logging.warning(
            f"########## exporting entry {imgentry['idx']} ##########")
        for entry in [
                "name", "path", "bit_depth", "dims", "scale", "channels",
                "chaninfo", "Blackval", "Whiteval", "AcqTS"
        ]:
            logging.warning(f'{entry}: %s', imgentry[entry])

    def _log_overview(self):
        """ logs info of entries added by _get_overview to logfile """

        logging.warning(f"########## entries found in file ##########")
        logging.warning(f"- entries which will be exported:")

        for imgtype in self.export_entries:
            imglist = self.grouped_img[imgtype]

            logging.warning(f'{imgtype}: {len(imglist)}')

        logging.warning(f"- entries whose export is not supported yet:")

        N_nonexported = 0
        for imgtype in self.nonexport_entries:
            imglist = self.grouped_img[imgtype]

            if len(imglist) > 0:
                N_nonexported += len(imglist)

            logging.warning(f'{imgtype}: {len(imglist)}')

        if N_nonexported > 0:
            logging.warning(
                f"### {N_nonexported} entries won't be exported ###")

    def print_overview(self):
        """ prints overview of found entries, shows which ones will be exported """

        print("following entries found in file: ")
        print("- entries which will be exported:")

        for imgtype in self.export_entries:
            imglist = self.grouped_img[imgtype]

            col = '\033[0m'
            if len(imglist) > 0:
                col = '\033[92m'

            print(f'{col} {imgtype}: {len(imglist)}' + '\033[0m')

        print("- entries whose export is not supported yet:")

        N_nonexported = 0
        for imgtype in self.nonexport_entries:
            imglist = self.grouped_img[imgtype]
            col = '\033[0m'
            if len(imglist) > 0:
                N_nonexported += len(imglist)
                col = '\033[93m'

            print(f'{col} {imgtype}: {len(imglist)}' + '\033[0m')

        if N_nonexported > 0:
            print(
                f"\033[91m {N_nonexported} entries won't be exported \033[0m")

    def _get_overview(self):
        """
            extracts information of stored images from metadata
            fills dict self.grouped_img with dict for each imgentry
        """

        for idx, img in enumerate(self.lifhandler.image_list):

            # print(img)
            img["idx"] = idx  # add index which is used to request frame
            img["chaninfo"] = self._query_chan(img)
            img["AcqTS"] = self._query_timestamp(img)
            img["Blackval"] = None
            img["Whiteval"] = None

            # check for special cases first
            if ('EnvironmentalGraph') in img["name"]:
                self.grouped_img["envgraph"].append(img)
                continue

            if ('Mark_and_Find') in img["path"]:
                self.grouped_img["MAF"].append(img)
                continue

            # check various dimensions to sort entries accordingly
            dimtuple = img["dims"]
            Nx, Ny, Nz, NT = dimtuple[0], dimtuple[1], dimtuple[2], dimtuple[3]
            # dimension tuple must be indexed with int, 0:x, 1:y, 2:z, 3:t, 4:m mosaic tile
            NC = img["channels"]

            # xy (simple image)
            if (Nz == 1 and NT == 1 and NC == 1):
                # print("entry is xy")
                self.grouped_img["xy"].append(img)

            # xyc (multichannel image)
            elif (Nz == 1 and NT == 1 and NC > 1):
                # print("entry is xyc")
                self.grouped_img["xyc"].append(img)

            # xyz (singlechannel zstack)
            elif (Nz > 1 and NT == 1 and NC == 1):
                # print("entry is xyz")
                self.grouped_img["xyz"].append(img)

            # xyt singlechannel video/ timelapse
            elif (Nz == 1 and NT > 1 and NC == 1):
                # print("entry is xyt")

                img["fps"] = img["scale"][3]
                self.grouped_img["xyt"].append(img)

            # xyct (multichannel video/ timelapse)
            elif (Nz == 1 and NT > 1 and NC > 1):

                img["fps"] = img["scale"][3]
                self.grouped_img["xyct"].append(img)

            # xycz
            elif (Nz > 1 and NT == 1 and NC > 1):
                self.grouped_img["xycz"].append(img)

            # xyzt
            elif (Nz > 1 and NT > 1 and NC == 1):
                img["fps"] = img["scale"][3]
                self.grouped_img["xyzt"].append(img)

            # xyczt
            elif (Nz > 1 and NT > 1 and NC > 1):
                img["fps"] = img["scale"][3]
                self.grouped_img["xyczt"].append(img)

            # add to category other if no previously checked category applies
            else:
                self.grouped_img["other"].append(img)

        # find blackval/whiteval (or even other param if desired) for xy and xyt-images
        for cat in ["xy", "xyt"]:
            imglist = self.grouped_img[cat]

            for img in imglist:
                black_val, white_val = self._query_hist(img)
                img["Blackval"] = black_val
                img["Whiteval"] = white_val

    def _write_xml(self):
        """
            writes metadata of lif-file in pretty xml
        """

        xmlstr = minidom.parseString(ET.tostring(
            self.lifhandler.xml_root)).toprettyxml(indent="   ")

        fname = self.outdir / (self.filename + "_meta.xml")
        with open(fname, "w") as f:
            f.write(xmlstr)
        #https://stackoverflow.com/questions/56682486/xml-etree-elementtree-element-object-has-no-attribute-write

    def export_xy(self, min_rangespan=0.2, max_clipped=0.2):
        """
            exports all xy image entries:
            - raw export: tif 
            - compressed export (jpg, scaled to blackval/whiteval which was set during acquisition 
                with burned in title + scale bar)
        """

        # check if entries to export
        if len(self.grouped_img["xy"]) == 0:
            return

        #### raw export folder
        rawfolder = self.outdir / "Images_xy" / "raw"
        rawfolder.mkdir(parents=True, exist_ok=True)
        #### compressed jpg export folder
        compfolder = self.outdir / "Images_xy" / "compressed"
        compfolder.mkdir(parents=True, exist_ok=True)

        # iterate all images
        for imgentry in self.grouped_img["xy"]:

            self._log_img(imgentry)
            img_idx = imgentry["idx"]
            img_name = imgentry["name"]
            print(f"exporting image {img_name}")  # with meta: \n {imgentry}")
            """
            # option to concatenate subfolders into filename
            path = imgentry["path"] # subfolders are nested here: projectname/subf1/subf2/
            sfolders = path.split("/")[1:-1] # split by / and take all except first and last
            sfolders.append(img_name)
            img_name = ("_".join(sfolders))
            """

            imghandler = self.lifhandler.get_image(img_idx)
            img = imghandler.get_frame(z=0, t=0, c=0)
            img_np = np.array(img)

            resolution_mpp = 1.0 / imgentry["scale_n"][
                1]  # unit should be pix per micron of scale_n

            imgpath = rawfolder / (img_name + ".tif")
            self.save_single_tif(img_np, imgpath, resolution_mpp)

            # compressed export:
            # scale images to 8bit, add scalebar + title, save as jpg in orig resolution
            bit_resolution = imgentry["bit_depth"][0]
            img_scale = 2**bit_resolution - 1

            # image_adj_contrast = self.adj_contrast(img_np, imgentry["Blackval"]*img_scale, imgentry["Whiteval"]*img_scale)
            vmin, vmax = self.check_contrast(img_np,
                                             imgentry["Blackval"] * img_scale,
                                             imgentry["Whiteval"] * img_scale,
                                             min_rangespan=min_rangespan,
                                             max_clipped=max_clipped)
            image_adj_contrast = exposure.rescale_intensity(
                img_np, in_range=(vmin, vmax))  # stretch min/max
            img_8 = cv2.convertScaleAbs(image_adj_contrast,
                                        alpha=(255.0 / img_scale))

            labeled_image = self.plot_scalebar(img_8, resolution_mpp, img_name)

            imgpath_jpg = compfolder / (img_name + ".jpg")
            skimage.io.imsave(imgpath_jpg, labeled_image)

    def export_xyz(self):
        """
            exports all xyz image entries (=zstacks)
            - raw export: tif 
            - compressed export: none
        """

        # check if entries to export
        if len(self.grouped_img["xyz"]) == 0:
            return

        #### raw export folder
        rawfolder = self.outdir / "Images_xyz"
        rawfolder.mkdir(parents=True, exist_ok=True)

        # iterate all images
        for imgentry in self.grouped_img["xyz"]:

            self._log_img(imgentry)
            resolution_mpp = 1.0 / imgentry["scale_n"][
                1]  # unit should be pix per micron of scale_n
            img_idx = imgentry["idx"]
            img_name = imgentry["name"]
            print(f"exporting zstack {img_name}")  # with meta: \n {imgentry}")
            imghandler = self.lifhandler.get_image(img_idx)

            # get correct z-spacing dz
            # take care! might need to be adjusted if reader.py changes
            z_spacing = imgentry["scale_n"][3]  # planes per micron
            Nz = imgentry["dims"][2]
            total_z = Nz / z_spacing
            dz = total_z / (Nz - 1)

            dzstring = f"-dz_{dz:.2f}_um".replace(
                ".", "_"
            )  # write plane spacing into foldername such that it's easily accessible
            stackfolder = rawfolder / (img_name + dzstring
                                       )  # create overall folder for zstack
            stackfolder.mkdir(parents=True, exist_ok=True)

            Nplanes = imgentry["dims"][2]
            for plane in tqdm.tqdm(np.arange(Nplanes),
                                   desc="Plane",
                                   file=sys.stdout,
                                   position=0,
                                   leave=True):

                img = imghandler.get_frame(z=plane, t=0, c=0)
                img_np = np.array(img)

                planepath = stackfolder / f"{img_name}-{plane:04d}.tif"  # series_name+"-{:04d}.tif".format(plane))
                self.save_single_tif(img_np, planepath, resolution_mpp)

    def export_xyc(self):
        """
            exports all xyc image entries (=multichannel images)
            - raw export: tif 
            - compressed export: none            
        """

        # check if entries to export
        if len(self.grouped_img["xyc"]) == 0:
            return

        #### raw export folder
        rawfolder = self.outdir / "Images_xyc"
        rawfolder.mkdir(parents=True, exist_ok=True)

        # iterate all images
        for imgentry in self.grouped_img["xyc"]:

            self._log_img(imgentry)
            resolution_mpp = 1.0 / imgentry["scale_n"][
                1]  # unit should be pix per micron of scale_n
            img_idx = imgentry["idx"]
            img_name = imgentry["name"]
            imgpath = rawfolder / (img_name + ".tif")
            print(f"exporting multichannel {img_name}"
                  )  # with meta: \n {imgentry}")

            imghandler = self.lifhandler.get_image(img_idx)
            channel_list = [
                np.array(img) for img in imghandler.get_iter_c(t=0, z=0)
            ]

            img_xyc = np.array(channel_list)
            self.save_single_tif(img_xyc,
                                 imgpath,
                                 resolution_mpp,
                                 photometric='minisblack')

    def export_xyt(self, min_rangespan=0.2, max_clipped=0.2):
        """
            exports all xyt image entries (=video/ timelapse entries)
            directly pipes frames to ffmpeg
            - large export: .mp4 in full resolution, low compression
            - small export: .mp4, longest side scaled to 1024, include scalebar          
        """
        # check if entries to export
        if len(self.grouped_img["xyt"]) == 0:
            return

        #### hq export folder
        lgfolder = self.outdir / "Videos" / "lg"
        lgfolder.mkdir(parents=True, exist_ok=True)
        #### compressed jpg export folder
        smfolder = self.outdir / "Videos" / "sm"
        smfolder.mkdir(parents=True, exist_ok=True)

        # iterate all entries
        for imgentry in self.grouped_img["xyt"]:

            self._log_img(imgentry)
            resolution_mpp = 1.0 / imgentry["scale_n"][1]
            img_idx = imgentry["idx"]
            img_name = imgentry["name"]
            fps = imgentry['fps']
            Nx, Ny, NT = imgentry["dims"][0], imgentry["dims"][1], imgentry[
                "dims"][3]
            Nmax_sm = 1024  # longest side of small video
            codec_lg, codec_sm = 'libx264', 'libx264'
            crf_lg, crf_sm = 17, 23

            #rescale smaller video such that longest side = 1024
            # prevent upscaling
            Nlong = max(Nx, Ny)
            scalingfactor = float(Nmax_sm) / Nlong
            if scalingfactor > 1.0:  # don't allow upscaling
                scalingfactor = 1.0
            #print("scaling", scalingfactor)
            Nx_sm, Ny_sm = int(Nx * scalingfactor), int(Ny * scalingfactor)

            scalebar = self.create_scalebar(
                Nx_sm,
                resolution_mpp / scalingfactor)  #create scalebar for small vid
            scale_width_px = scalebar.shape[0]
            scale_height_px = scalebar.shape[1]

            print(f"exporting video {img_name}")  # with meta: \n {imgentry}")
            #print("resolution of small video:", Nx_sm, Ny_sm)
            imghandler = self.lifhandler.get_image(img_idx)

            # export of both vids simultaneously, get infos to start ffmpeg subprocess

            resinfo_lg = f'resolution_xy={resolution_mpp:.6f}_mpp'.replace(
                ".", "_")
            resinfo_sm = f'resolution_xy={resolution_mpp/scalingfactor:.6f}_mpp'.replace(
                ".", "_")  # correct by scalingfactor
            # stores resolution info in metadata (in category comment) for quick access from videofile
            path_lg = str(lgfolder /
                          (img_name +
                           "_lg.mp4"))  # string needed for input to ffmpeg cmd
            path_sm = str(smfolder /
                          (img_name +
                           "_sm.mp4"))  # string needed for input to ffmpeg cmd
            sizestring_lg = f'{Nx}x{Ny}'  # e.g. 1024x1024, xsize x ysize, todo: check if order correct
            sizestring_sm = f'{Nx_sm}x{Ny_sm}'

            startt = time.time()  #for quick check of exporttimes

            # write video via pipe to ffmpeg-stream, start process here
            # solution from https://stackoverflow.com/questions/61260182/how-to-output-x265-compressed-video-with-cv2-videowriter
            process_lg = sp.Popen(shlex.split(
                f'"{FFMPEG_BINARY}" -y -s {sizestring_lg} '
                f'-pixel_format gray8 -f rawvideo -r {fps} -i pipe: -vcodec {codec_lg} '
                f'-pix_fmt yuv420p -crf {crf_lg} -metadata comment="{resinfo_lg}" "{path_lg}"'
            ),
                                  stdin=sp.PIPE,
                                  stderr=sp.DEVNULL
                                  )  # supress ffmpeg output to console

            # directly create process for export of smaller vid -> img has to be pulled only once
            process_sm = sp.Popen(shlex.split(
                f'"{FFMPEG_BINARY}" -y -s {sizestring_sm} '
                f'-pixel_format gray8 -f rawvideo -r {fps} -i pipe: -vcodec {codec_sm} '
                f'-pix_fmt yuv420p -crf {crf_sm} -metadata comment="{resinfo_sm}" "{path_sm}"'
            ),
                                  stdin=sp.PIPE,
                                  stderr=sp.DEVNULL)  #

            # check correct exposure scaling on one frame (image at half videolength)
            # save frame also as tif in full res for later access
            Nmean = int(imgentry["dims"][3] /
                        2) - 1  # idx of mean frame ~ at half of Nframes
            mimg = imghandler.get_frame(z=0, t=Nmean, c=0)
            img_np = np.array(mimg)

            bit_resolution = imgentry["bit_depth"][0]
            img_scale = 2**bit_resolution - 1
            vmin, vmax = self.check_contrast(img_np,
                                             imgentry["Blackval"] * img_scale,
                                             imgentry["Whiteval"] * img_scale,
                                             min_rangespan=min_rangespan,
                                             max_clipped=max_clipped)
            vmin8, vmax8 = vmin * (255.0 / img_scale), vmax * (
                255.0 / img_scale)  # adjust to 8bit
            # print(imgentry["Blackval"], imgentry["Whiteval"])
            # print(vmin8, vmax8)
            # logging.warning(f'set vmin8, vmax8 to {vmin8}, {vmax8}')

            # save single tiff in full size
            stillpath = self.outdir / "Videos" / "tifstills"
            stillpath.mkdir(parents=True, exist_ok=True)
            self.save_single_tif(img_np, stillpath / (img_name + ".tif"),
                                 resolution_mpp)

            # kwarg info:
            # -y: overwrite wo. asking
            # -s: size
            # -pixel_format: bgr24 was set... use gray8 for 8bit grayscale
            # -f: "Force input or output file format. -> here set to raw stream"
            # -r: framerate, can be used for input and output stream, here only input specified -> output will be same
            # -i: pipe
            for frame in tqdm.tqdm(imghandler.get_iter_t(c=0, z=0),
                                   desc="Frame",
                                   file=sys.stdout,
                                   position=0,
                                   leave=True,
                                   total=NT):
                img_np = np.array(frame)
                img8 = cv2.convertScaleAbs(
                    img_np, alpha=(255.0 / img_scale))  # scale to 8bit range
                img_scaled = exposure.rescale_intensity(
                    img8, in_range=(vmin8, vmax8))  # stretch min/max

                img_sm = cv2.resize(img_scaled,
                                    (Nx_sm, Ny_sm))  # scale down small vid
                img_sm[-1 - scale_width_px:-1,
                       -1 - scale_height_px:-1] = scalebar  # add scalebar

                process_lg.stdin.write(img_scaled.tobytes())
                process_sm.stdin.write(img_sm.tobytes())

            for process in [process_lg, process_sm]:
                process.stdin.close()
                process.wait()
                process.terminate()

            print("video export finished in", time.time() - startt, "s")

    def export_all(self):
        """ 
            exports xy, xyc, xyz, xyt entries (all currently supported export options)
            by calling individual exportfunctions
        """

        self.export_xy(min_rangespan=0.2, max_clipped=0.6)
        self.export_xyz()
        self.export_xyc()
        self.export_xyt(min_rangespan=0.2, max_clipped=0.6)

    def save_single_tif(self,
                        image,
                        path,
                        resolution_mpp,
                        photometric=None,
                        compress=None):
        """
            saves single imagej-tif into specified folder
            uses resolution_mpp to indicate resolution in x and y dimensions in microns per pixel
        """

        resolution_ppm = 1 / resolution_mpp  # convert micron per pixel to pixel per micron
        metadata = {'unit': 'um'}

        if photometric == None:
            tifffile.imsave(
                path,
                image,
                imagej=True,
                resolution=(resolution_ppm, resolution_ppm),
                metadata=metadata,
                compress=compress)  #what if ppm is different in x and y?
        else:
            tifffile.imsave(path,
                            image,
                            imagej=True,
                            resolution=(resolution_ppm, resolution_ppm),
                            metadata=metadata,
                            photometric=photometric,
                            compress=compress)

    def check_contrast(self,
                       image,
                       vmin=None,
                       vmax=None,
                       min_rangespan=0.2,
                       max_clipped=0.2):
        """
            checks if desired scaling range between vmin and vmax yields to a reasonable
            intensity range (< max_clipped (20 % default) of image over/underexposed, 
            image spans > min_rangespan (20 % default) of range)
            adjusts vmin and vmax to 0.2 - 99.8 percentile if not
        """

        # check first if contrast is somehow alright

        # check spanwidth of image vs. spanwidth of defined interval
        # if rangespan small -> low contrast
        # rangespan can also be alright but values shifted -> over/ underxposure
        # -> check both
        imglimits = np.percentile(image, [5, 95])
        rangespan = (imglimits[1] - imglimits[0]) / (vmax - vmin)

        # check fraction of px outside defined interval (max. 1 = all)
        # outside px are over/underexposed
        px_clipped = ((image < vmin) | (image > vmax)).sum() / image.size
        if ((px_clipped > max_clipped) or (rangespan < min_rangespan)):
            print(
                '\033[96m' +
                "extracted histogram scaling (blackval/ whiteval) would "
                "correspond to an over/underexposure of > 20 % of the image "
                "or the image would span < 20 % of chosen range"
                "-> switching to automatic rescaling to range from 0.2 - 99.8 percentile"
                + '\033[0m')
            logging.warning(f'adjusting contrast range to {vmin}, {vmax}')
            #print(f"vmin {vmin}, vmax {vmax}, immin {imglimits[0]}, immax {imglimits[1]}")
            vmin, vmax = None, None

        if None in (vmin, vmax):
            vmin = np.percentile(image, 0.2)
            vmax = np.percentile(image, 99.8)

        return vmin, vmax

    def create_scalebar(self, dimX_px, microns_per_pixel):
        """
            creates scalebar as np array which then can be transferred to image
        """

        scale_values = [
            1, 2, 5, 10, 15, 20, 30, 40, 50, 70, 100, 150, 200, 300, 400, 500,
            700, 1000
        ]
        initial_scale_length = dimX_px * 0.2 * microns_per_pixel

        text_height_px = int(round(dimX_px * 0.05))
        drawfont = ImageFont.truetype("arial.ttf", text_height_px)

        scale_length_microns = min(
            scale_values,
            key=lambda x: abs(x - initial_scale_length))  # pick nearest value
        scale_caption = str(scale_length_microns) + " µm"
        scale_length_px = scale_length_microns / microns_per_pixel
        scale_height_px = dimX_px * 0.01

        bg_square_spacer_px = scale_length_px * 0.07

        bg_square_length_px = int(
            round(scale_length_px + 2 * bg_square_spacer_px))
        bg_square_height_px = int(
            round(text_height_px + scale_height_px + 2 * bg_square_spacer_px))

        scalebar = Image.new("L", (bg_square_length_px, bg_square_height_px),
                             "white")
        draw = ImageDraw.Draw(scalebar)

        w_caption, h_caption = draw.textsize(scale_caption, font=drawfont)

        draw.rectangle(((0, 0), (bg_square_length_px, bg_square_height_px)),
                       fill="black")
        draw.rectangle(
            ((bg_square_spacer_px,
              bg_square_height_px - bg_square_spacer_px - scale_height_px),
             (bg_square_length_px - bg_square_spacer_px,
              bg_square_height_px - bg_square_spacer_px)),
            fill="white")
        draw.text(
            (bg_square_spacer_px + bg_square_length_px / 2 - w_caption / 2,
             bg_square_spacer_px / 2),
            scale_caption,
            font=drawfont,
            fill="white")

        output_scalebar = np.array(scalebar)
        return output_scalebar

    #todo: reorganize such that cmds are not repeated in create_scalebar...
    def plot_scalebar(self, input_image, microns_per_pixel, image_name=None):
        """
            plots scalebar + title onto image if desired
            scalebar is only plotted if image width > 800 px
            image input: np array
        """

        image_scalebar = Image.fromarray(input_image)  # Image is PIL.Image
        #np.uint8(input_image*255)

        dimX_px = input_image.shape[1]
        dimY_px = input_image.shape[0]
        initial_scale_length = dimX_px * 0.2 * microns_per_pixel

        text_height_px = int(round(dimY_px * 0.05))

        scale_values = [
            1, 2, 5, 10, 15, 20, 30, 40, 50, 70, 100, 150, 200, 300, 400, 500,
            700, 1000
        ]
        drawfont = ImageFont.truetype("arial.ttf", text_height_px)

        scale_length_microns = min(
            scale_values,
            key=lambda x: abs(x - initial_scale_length))  # pick nearest value
        scale_caption = str(scale_length_microns) + " µm"

        draw = ImageDraw.Draw(image_scalebar)
        w_caption, h_caption = draw.textsize(scale_caption, font=drawfont)

        scale_length_px = scale_length_microns / microns_per_pixel
        scale_height_px = dimY_px * 0.01

        bg_square_spacer_px = scale_length_px * 0.07

        bg_square_length_px = scale_length_px + 2 * bg_square_spacer_px
        bg_square_height_px = text_height_px + scale_height_px + 2 * bg_square_spacer_px

        if dimX_px > 800:
            #print(dimX_px - bg_square_length_px, dimX_px - bg_square_height_px)
            draw.rectangle(
                ((dimX_px - bg_square_length_px,
                  dimY_px - bg_square_height_px), (dimX_px, dimY_px)),
                fill="black")
            draw.rectangle(
                ((dimX_px - bg_square_length_px + bg_square_spacer_px,
                  dimY_px - bg_square_spacer_px - scale_height_px),
                 (dimX_px - bg_square_spacer_px,
                  dimY_px - bg_square_spacer_px)),
                fill="white")
            draw.text(
                (dimX_px - bg_square_length_px + bg_square_spacer_px +
                 bg_square_length_px / 2 - w_caption / 2,
                 dimY_px - bg_square_height_px + bg_square_spacer_px / 2),
                scale_caption,
                font=drawfont,
                fill="white")  # scale_caption.decode('utf8')

            # burn title if provided
            if image_name != None:
                title_height_px = int(round(dimY_px * 0.05))
                drawfont = ImageFont.truetype("arial.ttf", title_height_px)
                draw.rectangle(((0, 0), (dimX_px, title_height_px * 1.2)),
                               fill="black")
                draw.text((0, 0), image_name, font=drawfont, fill="white")

        output_image = np.array(image_scalebar)

        return output_image

    def create_ppt_summary(self):
        """
            creates ppt with all exported images
        """

        print("creating ppt-summary")

        # take always n elements from list as list, helpfunction
        def grouper(iterable, n, fillvalue=None):
            args = [iter(iterable)] * n
            return zip_longest(*args, fillvalue=fillvalue)

        # prepare presentation
        prs = Presentation()
        title_slide_layout = prs.slide_layouts[0]
        slide = prs.slides.add_slide(title_slide_layout)
        title = slide.shapes.title
        subtitle = slide.placeholders[1]

        title.text = str(self.filename)
        subtitle.text = "lif-summary"

        ###############################
        # constants for 2 x 3 layout

        Pt_per_cm = 72.0 / 2.54

        slide_width = Pt(25.4 * Pt_per_cm)
        slide_height = Pt(19.05 * Pt_per_cm)

        headspace = Pt(30)
        image_height = Pt(200)

        d_horizontal = (slide_width - 3 * image_height) / 4
        d_vertical = (slide_height - headspace - 2 * image_height) / 3

        ##############################

        # pick 6 images

        for images_6group in grouper(self.categorized_series['img_simple'], 6,
                                     None):

            #add new slide
            blank_slide_layout = prs.slide_layouts[6]
            slide = prs.slides.add_slide(blank_slide_layout)

            #iterate through rows, columns
            for rowindex in range(2):
                for columnindex in range(3):

                    image_index = rowindex * 3 + columnindex
                    if (images_6group[image_index]) != None:
                        image_name = images_6group[image_index]['name']
                        imagepath = os.path.join(self.filename, "compressed",
                                                 "images", image_name + ".jpg")
                        pic = slide.shapes.add_picture(
                            imagepath,
                            d_horizontal * (columnindex + 1) +
                            image_height * columnindex,
                            headspace + d_vertical * (rowindex + 1) +
                            image_height * rowindex,
                            height=image_height)
        """
        # pick videos, experimental, works but stillimage is loudspeaker -> create individual stillimage
        for video in self.categorized_series['img_multiT']:
            blank_slide_layout = prs.slide_layouts[6]
            slide = prs.slides.add_slide(blank_slide_layout)
            videopath = os.path.join(self.filename,"compressed","videos",video['name'] + ".mp4")
            slide.shapes.add_movie(videopath,d_vertical,d_vertical,slide_height-2*d_vertical,slide_height-2*d_vertical)
        """

        outputpath = os.path.join(str(self.filename),
                                  str(self.filename) + '_summary.pptx')
        prs.save(outputpath)
コード例 #19
0
ファイル: test.py プロジェクト: jojoelfe/readlif
 def test_depth(self):
     obj = LifFile("./tests/xyzt_test.lif").get_image(0)
     self.assertEqual(obj.bit_depth[0], 8)
コード例 #20
0
ファイル: test.py プロジェクト: jojoelfe/readlif
 def test_scale(self):
     obj = LifFile("./tests/xyzt_test.lif").get_image(0)
     self.assertAlmostEqual(obj.scale[0], 9.8709062997224)
コード例 #21
0
def download_file():

    export_option = request.args.get('export_option')

    if os.path.exists(session['export_dir']):
        shutil.rmtree(session['export_dir'])
    os.makedirs(session['export_dir'])

    lif_file = LifFile(session['file_path'])
    img_list = [i for i in lif_file.get_iter_image()]
    data = download_image(export_option, session['export_dir'], img_list,
                          session['stack_list'], session['stack_dict_list'],
                          session['stack'], session['zframe'], session['channel'],
                          session['bg_thresh'], session['adaptive_thresh'],
                          session['erosion'], session['dilation'],
                          session['min_dist'], session['gamma'], session['gain'], CONNECTIVITY, CIRCLE_RADIUS)


    data_df = pd.DataFrame(data)
    export_file_path_data = os.path.join(session['export_dir'], "data.csv")
    data_df.to_csv(export_file_path_data, index=False, sep=";")

    config = {'background_threshold': session['bg_thresh'],
              'adaptive_threshold': session['adaptive_thresh'],
              'erosion_iteration': session['erosion'],
              'dilation_iteration': session['dilation'],
              'minimum_distance': session['min_dist']}

    export_file_path_config = os.path.join(session['export_dir'], "config.txt")
    with open(export_file_path_config, 'w') as file:
        file.write(json.dumps(config))

    def retrieve_file_paths(dirName):

        # setup file paths variable
        filePaths = []

        # Read all directory, subdirectories and file lists
        for root, directories, files in os.walk(dirName):
            for filename in files:
                # Create the full filepath by using os module.
                filePath = os.path.join(root, filename)
                filePaths.append(filePath)

        # return all paths
        return filePaths

    # Call the function to retrieve all files and folders of the assigned directory
    filePaths = retrieve_file_paths(session['export_dir'])

    # printing the list of all files to be zipped
    logger.info('The following list of files will be zipped:')
    for fileName in filePaths:
        logger.info(fileName)

    zip_file_path = session['export_dir'] + '.zip'
    zip_file = zipfile.ZipFile(zip_file_path, 'w')
    with zip_file:
        # writing each file one by one
        for file in filePaths:
            zip_file.write(file)

    return_data = io.BytesIO()
    with open(zip_file_path, 'rb') as fo:
        return_data.write(fo.read())
    # (after writing, cursor will be at last byte, so move it to start)
    return_data.seek(0)

    os.remove(zip_file_path)
    shutil.rmtree(session['export_dir'])

    return send_file(return_data, mimetype='application/zip',
                     attachment_filename='download.zip')
コード例 #22
0
ファイル: test.py プロジェクト: nimne/readlif
 def test_arbitrary_plane_on_xzt_img(self):
     obj = LifFile(
         "./tests/LeicaLASX_wavelength-sweep_example.lif").get_image(0)
     with self.assertRaises(NotImplementedError):
         obj.get_plane(display_dims=(1, 5), c=0, requested_dims={2: 31})
コード例 #23
0
ファイル: test.py プロジェクト: nimne/readlif
 def test_new_lasx(self):
     obj = LifFile("./tests/new_lasx.lif")
     self.assertEqual(len(obj.image_list), 1)
コード例 #24
0
    def _compute_offsets(lif: LifFile) -> Tuple[List[np.ndarray], np.ndarray]:
        """
        Compute the offsets for each of the YX planes so that the LifFile object
        doesn't need to be created for each YX plane read.

        Parameters
        ----------
        lif : LifFile
            The LifFile object with an open file pointer to the file.

        Returns
        -------
        List[numpy.ndarray]
            The list of numpy arrays holds the offsets and it should be accessed as
            [S][T,C,Z].
        numpy.ndarray
            The second numpy array holds the plane read length per Scene.

        """
        scene_list = []
        scene_img_length_list = []

        for s_index, img in enumerate(lif.get_iter_image()):
            pixel_type = LifReader.get_pixel_type(lif.xml_root, s_index)
            (
                x_size,
                y_size,
                z_size,
                t_size,
            ) = img.dims  # in comments in this block these correspond to X, Y, Z, T
            c_size = img.channels  # C
            img_offset, img_block_length = img.offsets
            offsets = np.zeros(shape=(t_size, c_size, z_size), dtype=np.uint64)
            t_offset = c_size * z_size
            z_offset = c_size
            seek_distance = c_size * z_size * t_size
            if img_block_length == 0:
                # In the case of a blank image, we can calculate the length from
                # the metadata in the LIF. When this is read by the parser,
                # it is set to zero initially.
                log.debug(
                    "guessing image length: LifFile assumes 1byte per pixel,"
                    " but I think this is wrong!"
                )
                image_len = seek_distance * x_size * y_size * pixel_type.itemsize
            else:  # B = bytes per pixel
                image_len = int(
                    img_block_length / seek_distance
                )  # B*X*Y*C*Z*T / C*Z*T = B*X*Y = size of an YX plane

            for t_index in range(t_size):
                t_requested = t_offset * t_index  # C*Z*t_index
                for c_index in range(c_size):
                    c_requested = c_index
                    for z_index in range(z_size):
                        z_requested = z_offset * z_index  # z_index * C
                        item_requested = (
                            t_requested + z_requested + c_requested
                        )  # the number of YX frames to jump
                        # self.offsets[0] is the offset to the beginning of the image
                        # block here we index into that block to get the offset for any
                        # YX frame in this image block
                        offsets[t_index, c_index, z_index] = np.uint64(
                            img.offsets[0] + image_len * item_requested
                        )

            scene_list.append(offsets)
            scene_img_length_list.append(image_len)

        return scene_list, np.asarray(scene_img_length_list, dtype=np.uint64)
コード例 #25
0
    def _daread(
        img: Path,
        offsets: List[np.ndarray],
        read_lengths: np.ndarray,
        chunk_by_dims: List[str] = [
            Dimensions.SpatialZ,
            Dimensions.SpatialY,
            Dimensions.SpatialX,
        ],
        S: int = 0,
    ) -> Tuple[da.core.Array, str]:
        """
        Read a LIF image file as a delayed dask array where certain dimensions act as
        the chunk size.

        Parameters
        ----------
        img: Path
            The filepath to read.
        offsets: List[numpy.ndarray]
            A List of numpy ndarrays offsets, see _compute_offsets for more details.
        read_lengths: numpy.ndarray
            A 1D numpy array of read lengths, the index is the scene index
        chunk_by_dims: List[str]
            The dimensions to use as the for mapping the chunks / blocks.
            Default: [Dimensions.SpatialZ, Dimensions.SpatialY, Dimensions.SpatialX]
            Note: SpatialY and SpatialX will always be added to the list if not present.
        S: int
            If the image has different dimensions on any scene from another, the dask
            array construction will fail.
            In that case, use this parameter to specify a specific scene to construct a
            dask array for.
            Default: 0 (select the first scene)

        Returns
        -------
        img: dask.array.core.Array
            The constructed dask array where certain dimensions are chunked.
        dims: str
            The dimension order as a string.
        """
        # Get image dims indicies
        lif = LifFile(filename=img)
        image_dim_indices = LifReader._dims_shape(lif=lif)

        # Catch inconsistent scene dimension sizes
        if len(image_dim_indices) > 1:
            # Choose the provided scene
            try:
                image_dim_indices = image_dim_indices[S]
                log.info(
                    f"File contains variable dimensions per scene, "
                    f"selected scene: {S} for data retrieval."
                )
            except IndexError:
                raise exceptions.InconsistentShapeError(
                    f"The LIF image provided has variable dimensions per scene. "
                    f"Please provide a valid index to the 'S' parameter to create a "
                    f"dask array for the index provided. "
                    f"Provided scene index: {S}. Scene index range: "
                    f"0-{len(image_dim_indices)}."
                )
        else:
            # If the list is length one that means that all the scenes in the image
            # have the same dimensions
            # Just select the first dictionary in the list
            image_dim_indices = image_dim_indices[0]

        # Uppercase dimensions provided to chunk by dims
        chunk_by_dims = [d.upper() for d in chunk_by_dims]

        # Always add Y and X dims to chunk by dims because that is how LIF files work
        if Dimensions.SpatialY not in chunk_by_dims:
            log.info(
                "Adding the Spatial Y dimension to chunk by dimensions as it was not "
                "found."
            )
            chunk_by_dims.append(Dimensions.SpatialY)
        if Dimensions.SpatialX not in chunk_by_dims:
            log.info(
                "Adding the Spatial X dimension to chunk by dimensions as it was not "
                "found."
            )
            chunk_by_dims.append(Dimensions.SpatialX)

        # Setup read dimensions for an example chunk
        first_chunk_read_dims = {}
        for dim, (dim_begin_index, dim_end_index) in image_dim_indices.items():
            # Only add the dimension if the dimension isn't a part of the chunk
            if dim not in chunk_by_dims:
                # Add to read dims
                first_chunk_read_dims[dim] = dim_begin_index

        # Read first chunk for information used by dask.array.from_delayed
        sample, sample_dims = LifReader._get_array_from_offset(
            im_path=img,
            offsets=offsets,
            read_lengths=read_lengths,
            meta=lif.xml_root,
            read_dims=first_chunk_read_dims,
        )

        # Get the shape for the chunk and operating shape for the dask array
        # We also collect the chunk and non chunk dimension ordering so that we can
        # swap the dimensions after we block the dask array together.
        sample_chunk_shape = []
        operating_shape = []
        non_chunk_dimension_ordering = []
        chunk_dimension_ordering = []
        for i, dim_info in enumerate(sample_dims):
            # Unpack dim info
            dim, size = dim_info

            # If the dim is part of the specified chunk dims then append it to the
            # sample, and, append the dimension to the chunk dimension ordering
            if dim in chunk_by_dims:
                sample_chunk_shape.append(size)
                chunk_dimension_ordering.append(dim)

            # Otherwise, append the dimension to the non chunk dimension ordering, and,
            # append the true size of the image at that dimension
            else:
                non_chunk_dimension_ordering.append(dim)
                operating_shape.append(
                    image_dim_indices[dim][1] - image_dim_indices[dim][0]
                )

        # Convert shapes to tuples and combine the non and chunked dimension orders as
        # that is the order the data will actually come out of the read data as
        sample_chunk_shape = tuple(sample_chunk_shape)
        blocked_dimension_order = (
            non_chunk_dimension_ordering + chunk_dimension_ordering
        )

        # Fill out the rest of the operating shape with dimension sizes of 1 to match
        # the length of the sample chunk. When dask.block happens it fills the
        # dimensions from inner-most to outer-most with the chunks as long as the
        # dimension is size 1. Basically, we are adding empty dimensions to the
        # operating shape that will be filled by the chunks from dask
        operating_shape = tuple(operating_shape) + (1,) * len(sample_chunk_shape)

        # Create empty numpy array with the operating shape so that we can iter through
        # and use the multi_index to create the readers.
        lazy_arrays = np.ndarray(operating_shape, dtype=object)

        # We can enumerate over the multi-indexed array and construct read_dims
        # dictionaries by simply zipping together the ordered dims list and the current
        # multi-index plus the begin index for that plane. We then set the value of the
        # array at the same multi-index to the delayed reader using the constructed
        # read_dims dictionary.
        dims = [d for d in Dimensions.DefaultOrder]
        begin_indicies = tuple(image_dim_indices[d][0] for d in dims)
        for i, _ in np.ndenumerate(lazy_arrays):
            # Add the czi file begin index for each dimension to the array dimension
            # index
            this_chunk_read_indicies = (
                current_dim_begin_index + curr_dim_index
                for current_dim_begin_index, curr_dim_index in zip(begin_indicies, i)
            )

            # Zip the dims with the read indices
            this_chunk_read_dims = dict(
                zip(blocked_dimension_order, this_chunk_read_indicies)
            )

            # Remove the dimensions that we want to chunk by from the read dims
            for d in chunk_by_dims:
                if d in this_chunk_read_dims:
                    this_chunk_read_dims.pop(d)

            # Add delayed array to lazy arrays at index
            lazy_arrays[i] = da.from_delayed(
                delayed(LifReader._imread)(
                    img, offsets, read_lengths, lif.xml_root, this_chunk_read_dims
                ),
                shape=sample_chunk_shape,
                dtype=sample.dtype,
            )

        # Convert the numpy array of lazy readers into a dask array and fill the inner
        # most empty dimensions with chunks
        merged = da.block(lazy_arrays.tolist())

        # Because we have set certain dimensions to be chunked and others not
        # we will need to transpose back to original dimension ordering
        # Example being, if the original dimension ordering was "SZYX" and we want to
        # chunk by "S", "Y", and "X" we created an array with dimensions ordering "ZSYX"
        transpose_indices = []
        transpose_required = False
        for i, d in enumerate(Dimensions.DefaultOrder):
            new_index = blocked_dimension_order.index(d)
            if new_index != i:
                transpose_required = True
                transpose_indices.append(new_index)
            else:
                transpose_indices.append(i)

        # Only run if the transpose is actually required
        # The default case is "Z", "Y", "X", which _usually_ doesn't need to be
        # transposed because that is _usually_
        # The normal dimension order of the LIF file anyway
        if transpose_required:
            merged = da.transpose(merged, tuple(transpose_indices))

        # Because dimensions outside of Y and X can be in any order and present or not
        # we also return the dimension order string.
        return merged, "".join(dims)
コード例 #26
0
ファイル: test.py プロジェクト: nimne/readlif
 def test_settings(self):
     obj = LifFile("./tests/testdata_2channel_xz.lif").get_image(0)
     self.assertEqual(obj.settings["ObjectiveNumber"], '11506353')
コード例 #27
0
ファイル: test.py プロジェクト: jojoelfe/readlif
 def test_not_lif_file(self):
     with self.assertRaises(ValueError):
         LifFile("./tests/tiff/c0z0t0.tif")
コード例 #28
0
    def _get_array_from_offset(
        im_path: Path,
        offsets: List[np.ndarray],
        read_lengths: np.ndarray,
        meta: Element,
        read_dims: Optional[Dict[str, int]] = None,
    ) -> Tuple[np.ndarray, List[Tuple[str, int]]]:
        """
        Gets specified bitmap data from the lif file (private).

        Parameters
        ----------
        im_path: Path
            Path to the LIF file to read.
        offsets: List[numpy.ndarray]
            A List of numpy ndarrays offsets, see _compute_offsets for more details.
        read_lengths: numpy.ndarray
            A 1D numpy array of read lengths, the index is the scene index
        read_dims: Optional[Dict[str, int]]
            The dimensions to read from the file as a dictionary of string to integer.
            Default: None (Read all data from the image)

        Returns
        -------
        numpy.ndarray
            a stack of images as a numpy.ndarray
        List[Tuple[str, int]]
            The shape of the data being returned
        """
        if read_dims is None:
            read_dims = {}

        lif = LifFile(im_path)

        # Data has already been checked for consistency. The dims are either consistent
        # or S is specified selected_ranges get's the ranges for the Dimension for the
        # range unless the dim is explicitly specified
        selected_ranges = LifReader._read_dims_to_ranges(lif, read_dims)
        s_index = read_dims[Dimensions.Scene] if Dimensions.Scene in read_dims else 0
        lif_img = lif.get_image(img_n=s_index)
        x_size = lif_img.dims[0]
        y_size = lif_img.dims[1]
        pixel_type = LifReader.get_pixel_type(meta, s_index)

        # The ranged dims
        ranged_dims = [
            (dim, len(selected_ranges[dim]))
            for dim in [
                Dimensions.Scene,
                Dimensions.Time,
                Dimensions.Channel,
                Dimensions.SpatialZ,
            ]
        ]

        img_stack = []

        # Loop through the dim ranges to return the requested image stack
        with open(str(im_path), "rb") as image:
            for s_index in selected_ranges[Dimensions.Scene]:
                for t_index in selected_ranges[Dimensions.Time]:
                    for c_index in selected_ranges[Dimensions.Channel]:
                        for z_index in selected_ranges[Dimensions.SpatialZ]:
                            # Use the precalculated offset to jump to the begining of
                            # the desired YX plane
                            image.seek(offsets[s_index][t_index, c_index, z_index])
                            # Read the image data as a bytearray
                            byte_array = image.read(read_lengths[s_index])
                            # Convert the bytearray to a the type pixel_type
                            typed_array = np.frombuffer(
                                byte_array, dtype=pixel_type
                            ).reshape(x_size, y_size)
                            # LIF stores YX planes so transpose them to get YX
                            typed_array = typed_array.transpose()
                            # Append the YX plane to the image stack.
                            img_stack.append(typed_array)

        shape = [len(selected_ranges[dim[0]]) for dim in ranged_dims]
        shape.append(y_size)
        shape.append(x_size)
        ranged_dims.append((Dimensions.SpatialY, y_size))
        ranged_dims.append((Dimensions.SpatialX, x_size))
        return (
            np.array(img_stack).reshape(*shape),
            ranged_dims,
        )  # in some subset of STCZYX order