Ejemplo n.º 1
0
class Image2D(Rect2D):

    def __init__(self, *args, x=None, y=None, width=None, height=None, data=None, dimensions=None, opacity=1.,
                 stroke=0.65, **kwargs):
        self.isSet = False

        self.annotation = [] # should contain the objects for annotating imaging --> shapes and texts
        self.letter = None # when objects are swapped need change the letter
        if args:
            if len(args) == 1:
                self.filename = args[0]
        else:
            self.filename = None

        if x is None and y is None and width is not None and height is not None:
            super(Image2D, self).__init__(0, 0, width, height)
            self.isSet = True
        elif x is None and y is None and width is None and height is None:
            # print('in 0')
            self.img = Img(self.filename)
            self.qimage = self.img.getQimage()
            width = self.img.get_width()
            height = self.img.get_height()
            super(Image2D, self).__init__(0, 0, width, height)
            self.isSet = True
        elif x is not None and y is not None and width is not None and height is not None:
            self.img = None
            super(Image2D, self).__init__(x, y, width, height)
            self.isSet = True
        elif data is None:
            if self.filename is not None:
                self.img = Img(self.filename)
                self.qimage = self.img.getQimage()
                if x is None:
                    x = 0
                if y is None:
                    y = 0
                super(Image2D, self).__init__(x, y, self.img.get_width(), self.img.get_height())
                self.isSet = True
        elif data is not None:
            self.img = Img(data,
                           dimensions=dimensions)  # need width and height so cannot really be only a numpy stuff --> cause no width or height by default --> or need tags such as image type for dimensions
            self.qimage = self.img.getQimage()
            # need Image dimensions id data is not of type IMG --> could check that
            if x is None:
                x = 0
            if y is None:
                y = 0
            super(Image2D, self).__init__(x, y, self.img.get_width(), self.img.get_height())
            self.isSet = True
        self.stroke = stroke  # DO I REALLY NEED STROKE
        self.opacity = opacity

    # @return the block incompressible width
    def getIncompressibleWidth(self):
        extra_space = 0 # can add some if boxes around to add text
        return extra_space

    # @return the block incompressible height
    def getIncompressibleHeight(self):
        extra_space = 0  # can add some if boxes around to add text
        return extra_space

    def setLetter(self, letter):
        self.letter = letter

    def draw(self, painter, draw=True):
        if draw:
            painter.save()
        painter.setOpacity(self.opacity)
        if draw:
            if self.img is not None:
                qsource = QRectF(0,0,self.img.get_width(), self.img.get_height())
                painter.drawImage(self, self.qimage , qsource) # , flags=QtCore.Qt.AutoColor
            else:
                painter.drawRect(self)
            painter.restore()

        # then need to draw the letter
        if self.letter is not None:
            self.letter.set_P1(self.get_P1())
            self.letter.draw(painter)

        if self.annotation is not None and self.annotation:
            for annot in self.annotation:
                annot.drawAndFill(draw=draw)

    def fill(self, painter, draw=True):
        if self.fill_color is None:
            return
        if draw:
            painter.save()
        painter.setOpacity(self.opacity)
        if draw:
            if self.img is not None:
                qsource = QRectF(0, 0, self.img.get_width(), self.img.get_height())
                painter.drawImage(self, self.qimage , qsource)
            else:
                painter.drawRect(self)
            painter.restore()

    def drawAndFill(self, painter):
        painter.save()
        if self.img is not None:
            qsource = QRectF(0, 0, self.img.get_width(), self.img.get_height())
            painter.drawImage(self, self.qimage , qsource)
        else:
            painter.drawRect(self)
        painter.restore()

    def __add__(self, other):
        from epyseg.figure.row import Row # KEEP Really required to avoid circular imports
        return Row(self, other)

    # create a Fig with divide
    def __truediv__(self, other):
        from deprecated_demos.ezfig_tests.col import col # KEEP Really required to avoid circular imports
        return col(self, other)

    #Force the montage width to equal 'width_in_px'
    def setToWidth(self, width_in_px):
        pure_image_width = self.width()
        ratio = width_in_px / pure_image_width
        self.setWidth(width_in_px)
        self.setHeight(self.height()*ratio)

    def setToHeight(self, height_in_px):
        pure_image_height = self.height()
        self.setHeight(height_in_px)
        ratio = height_in_px / pure_image_height
        self.setWidth(self.width()*ratio)
Ejemplo n.º 2
0
class Image2D(Rect2D):
    TOP_LEFT = 0
    TOP_RIGHT = 1
    BOTTOM_LEFT = 2
    BOTTOM_RIGHT = 3
    CENTERED = 4

    def __init__(self,
                 *args,
                 x=None,
                 y=None,
                 width=None,
                 height=None,
                 data=None,
                 dimensions=None,
                 opacity=1.,
                 **kwargs):
        self.isSet = False
        self.scale = 1
        self.translation = QPointF()

        # crops
        self.__crop_left = 0
        self.__crop_right = 0
        self.__crop_top = 0
        self.__crop_bottom = 0
        self.img = None
        self.annotation = [
        ]  # should contain the objects for annotating imaging --> shapes and texts
        self.letter = None  # when objects are swapped need change the letter
        self.top_left_objects = []
        self.top_right_objects = []
        self.bottom_right_objects = []
        self.bottom_left_objects = []
        self.centered_objects = []

        # if the image is inserted as an inset then draw it as a fraction of parent width
        # inset parameters
        self.fraction_of_parent_image_width_if_image_is_inset = 0.25
        self.border_size = None  # no border by default
        self.border_color = 0xFFFFFF  # white border by default

        if args:
            if len(args) == 1:
                if isinstance(args[0], str):
                    self.filename = args[0]
                elif isinstance(args[0], Img):
                    self.filename = None
                    self.img = args[0]
                    self.qimage = self.img.getQimage()
                    if x is None:
                        x = 0
                    if y is None:
                        y = 0
                    super(Image2D, self).__init__(x, y, self.img.get_width(),
                                                  self.img.get_height())
                    self.isSet = True
        else:
            self.filename = None

        if x is None and y is None and width is not None and height is not None:
            super(Image2D, self).__init__(0, 0, width, height)
            self.isSet = True
        elif x is None and y is None and width is None and height is None and self.filename is not None:
            # print('in 0')
            try:
                self.img = Img(self.filename)
            except:
                logger.error('could not load image ' + str(self.filename))
                return
            self.qimage = self.img.getQimage()
            width = self.img.get_width()
            height = self.img.get_height()
            super(Image2D, self).__init__(0, 0, width, height)
            self.isSet = True
        elif x is not None and y is not None and width is not None and height is not None and self.img is None:
            self.img = None
            super(Image2D, self).__init__(x, y, width, height)
            self.isSet = True
        elif data is None:
            if self.filename is not None:
                self.img = Img(self.filename)
                self.qimage = self.img.getQimage()
                if x is None:
                    x = 0
                if y is None:
                    y = 0
                super(Image2D, self).__init__(x, y, self.img.get_width(),
                                              self.img.get_height())
                self.isSet = True
        elif data is not None:
            self.img = Img(
                data, dimensions=dimensions
            )  # need width and height so cannot really be only a numpy stuff --> cause no width or height by default --> or need tags such as image type for dimensions
            self.qimage = self.img.getQimage()
            # need Image dimensions id data is not of type IMG --> could check that
            if x is None:
                x = 0
            if y is None:
                y = 0
            super(Image2D, self).__init__(x, y, self.img.get_width(),
                                          self.img.get_height())
            self.isSet = True
        self.opacity = opacity

    # @return the block incompressible width
    def getIncompressibleWidth(self):
        extra_space = 0  # can add some if boxes around to add text
        return extra_space

    # @return the block incompressible height
    def getIncompressibleHeight(self):
        extra_space = 0  # can add some if boxes around to add text
        return extra_space

    def add_object(self, object, position):
        if position == Image2D.TOP_LEFT:
            self.top_left_objects.append(object)
        elif position == Image2D.BOTTOM_RIGHT:
            self.bottom_right_objects.append(object)
        elif position == Image2D.BOTTOM_LEFT:
            self.bottom_left_objects.append(object)
        elif position == Image2D.CENTERED:
            self.centered_objects.append(object)
        else:
            self.top_right_objects.append(object)

    # TODO --> check if contains it
    def remove_object(self, object, position):
        if position == Image2D.TOP_LEFT:
            self.top_left_objects.remove(object)
        elif position == Image2D.BOTTOM_RIGHT:
            self.bottom_right_objects.remove(object)
        elif position == Image2D.BOTTOM_LEFT:
            self.bottom_left_objects.remove(object)
        elif position == Image2D.CENTERED:
            self.centered_objects.remove(object)
        else:
            self.top_right_objects.remove(object)

    def remove_all_objects(self, position):
        if position == Image2D.TOP_LEFT:
            del self.top_left_objects
            self.top_left_objects = []
        elif position == Image2D.BOTTOM_RIGHT:
            del self.bottom_right_objects
            self.bottom_right_objects = []
        elif position == Image2D.BOTTOM_LEFT:
            del self.bottom_left_objects
            self.bottom_left_objects = []
        elif position == Image2D.CENTERED:
            del self.centered_objects
            self.centered_objects = []
        else:
            del self.top_right_objects
            self.top_right_objects = []

    def setLettering(self, letter):
        if isinstance(letter, TAText2D):
            self.letter = letter
        elif isinstance(letter, str):
            if letter.strip() == '':
                self.letter = None
            else:
                self.letter = TAText2D(letter)

    # def getRect2D(self):
    #     # self.__class__ = Rect2D
    #     # return super()
    #     # TODO ideally I'd like to get the Rect2D parent but I should think what the best way is to get it...
    #     return self

    def draw(self, painter, draw=True):
        if draw:
            painter.save()
            painter.setOpacity(self.opacity)
            # painter.setClipRect(self)  # only draw in self --> very useful for inset borders # pb clip rect does not work for svg --> remove for now users can add it manually if desired or I can add it if people really want it and then I should draw relevant lines or shifted rects --> do that later
            # prevents drawing outside from the image
            rect_to_plot = self.boundingRect(
                scaled=True
            )  #scaled=True #self.adjusted(self.__crop_left, self.__crop_top, self.__crop_right, self.__crop_bottom) # need remove the crops with that
            # self.scale = 1
            # if self.scale is not None and self.scale != 1:
            # #     # TODO KEEP THE ORDER THIS MUST BE DONE THIS WAY OR IT WILL GENERATE PLENTY OF BUGS...
            #     new_width = rect_to_plot.width() * self.scale
            #     new_height = rect_to_plot.height() * self.scale
            # #     # print(rect_to_plot.width(), rect_to_plot.height())  # here ok
            # #     # setX changes width --> why is that
            # #
            # #     # TODO BE EXTREMELY CAREFUL AS SETX AND SETY CAN CHANGE WIDTH AND HEIGHT --> ALWAYS TAKE SIZE BEFORE OTHERWISE THERE WILL BE A PB AND ALWAYS RESET THE SIZE WHEN SETX IS CALLED!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
            # #     # Sets the left edge of the rectangle to the given x coordinate. May change the width, but will never change the right edge of the rectangle. --> NO CLUE WHY SHOULD CHANGE WIDTH THOUGH BUT BE CAREFUL!!!
            # #     rect_to_plot.setX(rect_to_plot.x() * self.scale)
            # #     rect_to_plot.setY(rect_to_plot.y() * self.scale)
            # #     # maybe to avoid bugs I should use translate instead rather that set x but ok anyways
            # #     # print(rect_to_plot.width(), rect_to_plot.height())# bug here --> too big
            # #
            # #     # print(new_height, new_height, self.width(), self.scale, self.scale* self.width())
            #     rect_to_plot.setWidth(new_width)
            #     rect_to_plot.setHeight(new_height)

            if self.img is not None:
                x = 0
                y = 0
                w = self.img.get_width()
                h = self.img.get_height()
                if self.__crop_top is not None:
                    y = self.__crop_top
                    h -= self.__crop_top
                if self.__crop_left is not None:
                    x = self.__crop_left
                    w -= self.__crop_left
                if self.__crop_right is not None:
                    w -= self.__crop_right
                if self.__crop_bottom is not None:
                    h -= self.__crop_bottom
                # pb here --> see how to really crop
                qsource = QRectF(x, y, w, h)
                painter.drawImage(rect_to_plot, self.qimage,
                                  qsource)  # , flags=QtCore.Qt.AutoColor
            else:
                painter.drawRect(rect_to_plot)

            # letter is good
            extra_space = 3

            # draw annotations first
            if self.annotation is not None and self.annotation:
                # need clone the object then set its P1 with respect to position or need a trick to keep original ref and have an updated one just for display but then need renew it all the time --> see how I can do that...
                # maybe clone is not smart as it duplicates resources without a need for it
                # but then need clone the original rect and draw with respect to that
                # and I indeed need scale the shape --> TODO too
                # indeed thanks to cloning I always preserve original info --> not bad

                # annot position is good
                # TODO see how to do that cause not so easy --> think carefully and take inspiration from EZF and improve it
                for annot in self.annotation:
                    # always empty --> why is that
                    # print('init',annot.get_P1())
                    # always assume everything is done at 0,0 then do translation
                    # annot.set_P1(self.get_P1().x() + annot.get_P1().x(),                    self.get_P1().y() + annot.get_P1().y())  # always relative to the parent image
                    # annot.set_P1(self.get_P1())  # always relative to the parent image
                    # print(annot.get_P1())

                    # print('init', self.get_P1(), 'scale', self.get_scale())
                    annot.set_to_translation(rect_to_plot.topLeft())

                    annot.set_to_scale(
                        self.scale)  # will f**k the stuff but ok for a test
                    # print('scaled',annot.get_P1())
                    annot.draw(painter=painter)
                    # print('tranbs', annot.translation)

            # and indeed I need also to take crop into account in order not to misposition things...

            if self.letter is not None:
                self.letter.set_P1(rect_to_plot.topLeft().x() + extra_space,
                                   rect_to_plot.topLeft().y() + extra_space)

            # then draw text and insets --> on top of annotations
            # TODO need align insets differently than others and need align its bounding box also differently --> TODO but almost there
            if len(self.top_right_objects) != 0 or len(
                    self.top_left_objects) != 0 or len(
                        self.bottom_left_objects) != 0 or len(
                            self.bottom_right_objects) != 0 or len(
                                self.centered_objects) != 0:
                # align a scale bar to various positions
                # maybe if there is a letter first point should be place below stuff
                # top_left = Point2D(self.get_P1())
                top_left_shifted = Point2D(rect_to_plot.topLeft())
                # top_left_shifted.setX(top_left_shifted.x() )# + extra_space
                # top_left_shifted.setY(top_left_shifted.y() )#+ extra_space

                # print('before', top_left)
                # if self.letter is not None:
                #     packY(extra_space, self.letter, top_left_shifted)
                # print('after', top_left)

                # insets should be aligned to unshifted values
                # whereas texts should be aligned to shifted ones
                # what if I try all unshifted
                # cause in a way it's simpler

                # top_right = Point2D(self.get_P1())
                top_right_shifted = Point2D(rect_to_plot.topLeft())
                top_right_shifted.setX(top_right_shifted.x() +
                                       rect_to_plot.width())  #- extra_space
                top_right_shifted.setY(top_right_shifted.y())  #+ extra_space

                # bottom_left = Point2D(self.get_P1())
                bottom_left_shifted = Point2D(rect_to_plot.topLeft())
                bottom_left_shifted.setX(
                    bottom_left_shifted.x())  #+ extra_space
                bottom_left_shifted.setY(
                    bottom_left_shifted.y() + rect_to_plot.height()
                )  #- extra_space  # should align right then pack on top of that --> may need a direction in packing--> TODO

                bottom_right = Point2D(rect_to_plot.topLeft())
                bottom_right_shifted = Point2D(rect_to_plot.topLeft())
                bottom_right_shifted.setX(
                    bottom_right_shifted.x() +
                    rect_to_plot.width())  # - extra_space
                bottom_right_shifted.setY(
                    bottom_right_shifted.y() +
                    rect_to_plot.height())  #- extra_space

                center = Point2D(rect_to_plot.topLeft())
                center.setX(center.x() + rect_to_plot.width() / 2)
                center.setY(center.y() + rect_to_plot.height() / 2)

                if len(self.top_left_objects) != 0:
                    # change inset size first
                    for obj in self.top_left_objects:
                        if isinstance(obj, Image2D):
                            obj.setToWidth(
                                rect_to_plot.width() * obj.
                                fraction_of_parent_image_width_if_image_is_inset
                            )

                    # if letter exists align with respect to it

                    alignTop(top_left_shifted, *self.top_left_objects)
                    alignLeft(top_left_shifted, *self.top_left_objects)

                    if self.letter is not None:
                        # packY(extra_space, self.letter, top_left_shifted)
                        top_left_shifted = self.letter

                    # in fact images really need be aligned left of the image but the others need be aligned with the letter that has an extra space --> TODO --> change some day

                    packY(extra_space, top_left_shifted,
                          *self.top_left_objects)

                    # all images need be shifted back??? to be aligned left

                    for obj in self.top_left_objects:
                        # for drawing of inset borders
                        # if isinstance(obj, Image2D):
                        #     # make it draw a border and align it
                        #     # painter.save()
                        #     img_bounds = Rect2D(obj)
                        #     img_bounds.stroke = 3
                        #     # img_bounds.translate(-img_bounds.stroke / 2, -img_bounds.stroke / 2)
                        #     img_bounds.color = 0xFFFF00
                        #     img_bounds.fill_color = 0xFFFF00
                        #     img_bounds.draw(painter=painter)
                        #     # print(img_bounds)
                        #     # painter.restore()
                        obj.draw(painter=painter)

                if len(self.top_right_objects) != 0:
                    # change inset size first
                    for obj in self.top_right_objects:
                        if isinstance(obj, Image2D):
                            obj.setToWidth(
                                rect_to_plot.width() * obj.
                                fraction_of_parent_image_width_if_image_is_inset
                            )
                    alignRight(top_right_shifted, *self.top_right_objects)
                    alignTop(top_right_shifted, *self.top_right_objects)
                    packY(extra_space, top_right_shifted,
                          *self.top_right_objects)
                    for obj in self.top_right_objects:
                        # # for drawing of inset borders
                        # if isinstance(obj, Image2D):
                        #     # make it draw a border and align it
                        #     # painter.save()
                        #     img_bounds = Rect2D(obj)
                        #     img_bounds.stroke = 3
                        #     # img_bounds.translate(img_bounds.stroke / 2, -img_bounds.stroke / 2)
                        #     img_bounds.color = 0xFFFF00
                        #     img_bounds.fill_color = 0xFFFF00
                        #     img_bounds.draw(painter=painter)
                        #     # print(img_bounds)
                        #     # painter.restore()
                        obj.draw(painter=painter)

                if len(self.bottom_right_objects) != 0:
                    # change inset size first
                    for obj in self.bottom_right_objects:
                        if isinstance(obj, Image2D):
                            obj.setToWidth(
                                rect_to_plot.width() * obj.
                                fraction_of_parent_image_width_if_image_is_inset
                            )
                    alignRight(bottom_right_shifted,
                               *self.bottom_right_objects)
                    alignBottom(bottom_right_shifted,
                                *self.bottom_right_objects)
                    packYreverse(extra_space, bottom_right_shifted,
                                 *self.bottom_right_objects)
                    # packY(3, top_right, *self.top_right_objects) # I do need to invert packing order
                    for obj in self.bottom_right_objects:
                        # # for drawing of inset borders
                        # if isinstance(obj, Image2D):
                        #     # make it draw a border and align it
                        #     # painter.save()
                        #     img_bounds = Rect2D(obj)
                        #     img_bounds.stroke = 3
                        #     # img_bounds.translate(-img_bounds.stroke / 2, img_bounds.stroke / 2)
                        #     # should I clip it to the image size --> maybe it's the best
                        #     img_bounds.color = 0xFFFF00
                        #     img_bounds.fill_color = 0xFFFF00
                        #     img_bounds.draw(painter=painter)
                        #     # print(img_bounds)
                        #     # painter.restore()
                        obj.draw(painter=painter)

                if len(self.bottom_left_objects) != 0:
                    # change inset size first
                    for obj in self.bottom_left_objects:
                        if isinstance(obj, Image2D):
                            obj.setToWidth(
                                rect_to_plot.width() * obj.
                                fraction_of_parent_image_width_if_image_is_inset
                            )
                    alignLeft(bottom_left_shifted, *self.bottom_left_objects)
                    alignBottom(bottom_left_shifted, *self.bottom_left_objects)
                    packYreverse(extra_space, bottom_left_shifted,
                                 *self.bottom_left_objects)
                    for obj in self.bottom_left_objects:
                        # # for drawing of inset borders
                        # if isinstance(obj, Image2D):
                        #     # make it draw a border and align it
                        #     # painter.save()
                        #     img_bounds = Rect2D(obj)
                        #     img_bounds.stroke = 3
                        #     # img_bounds.translate(-img_bounds.stroke/2, img_bounds.stroke/2)
                        #     img_bounds.color = 0xFFFF00
                        #     img_bounds.fill_color = 0xFFFF00
                        #     img_bounds.draw(painter=painter)
                        #     # print(img_bounds)
                        #     # painter.restore()
                        obj.draw(painter=painter)

                if len(self.centered_objects) != 0:
                    # change inset size first
                    for obj in self.centered_objects:
                        if isinstance(obj, Image2D):
                            obj.setToWidth(
                                rect_to_plot.width() * obj.
                                fraction_of_parent_image_width_if_image_is_inset
                            )
                    alignCenterH(center, *self.centered_objects)
                    alignCenterV(center, *self.centered_objects)
                    for obj in self.centered_objects:
                        # # for drawing of inset borders
                        # if isinstance(obj, Image2D):
                        #     # make it draw a border and align it
                        #     # painter.save()
                        #     img_bounds = Rect2D(obj)
                        #     img_bounds.stroke = 3
                        #     img_bounds.color = 0xFFFF00
                        #     img_bounds.fill_color = 0xFFFF00
                        #     img_bounds.draw(painter=painter)
                        #     # print(img_bounds)
                        #     # painter.restore()
                        obj.draw(painter=painter)

            # then need to draw the letter at last so that it is always on top
            if self.letter is not None:
                self.letter.draw(painter)

            painter.restore()
            # # TOP left 2
            # scale_bar = ScaleBar(30, '<font color="#FF00FF">10µm</font>')
            # scale_bar.set_scale(self.get_scale())
            # # scale_bar.set_P1(self.get_P1().x()+extra_space, self.get_P1().y()+extra_space)
            # scale_bar.set_P1(self.get_P1())
            # alignLeft(top_left, scale_bar)
            # alignTop(top_left, scale_bar)
            # # scale_bar.set_P1(scale_bar.get_P1().x()-extra_space, scale_bar.get_P1().x()+extra_space)
            # scale_bar.drawAndFill(painter=painter)

            # TOP right 2
            # scale_bar = ScaleBar(30, '<font color="#FF00FF">10µm</font>')
            # scale_bar.set_scale(self.get_scale())
            # # scale_bar.set_P1(self.get_P1().x()+extra_space, self.get_P1().y()+extra_space)
            # scale_bar.set_P1(self.get_P1())
            # alignRight(top_right, scale_bar)
            # alignTop(top_right, scale_bar)
            # # scale_bar.set_P1(scale_bar.get_P1().x()-extra_space, scale_bar.get_P1().x()+extra_space)
            # scale_bar.drawAndFill(painter=painter)

            # bottom left 2

            # # big bug in scale --> the size of the stuff isn't respected
            # # 288 is the size of image 0
            # scale_bar = ScaleBar(288, '<font color="#FF00FF">10µm</font>')
            # scale_bar.set_scale(self.get_scale())
            # # scale_bar.set_P1(self.get_P1().x()+extra_space, self.get_P1().y()+extra_space)
            # scale_bar.set_P1(self.get_P1())
            # alignLeft(bottom_left, scale_bar)
            # alignBottom(bottom_left, scale_bar)
            # # scale_bar.set_P1(scale_bar.get_P1().x()-extra_space, scale_bar.get_P1().x()+extra_space)
            # scale_bar.drawAndFill(painter=painter)

            # # bottom right 2
            # scale_bar = ScaleBar(30, '<font color="#FF00FF">10µm</font>')
            # scale_bar.set_scale(self.get_scale())
            # # scale_bar.set_P1(self.get_P1().x()+extra_space, self.get_P1().y()+extra_space)
            # scale_bar.set_P1(self.get_P1())
            # alignRight(bottom_right, scale_bar)
            # alignBottom(bottom_right, scale_bar)
            # # scale_bar.set_P1(scale_bar.get_P1().x()-extra_space, scale_bar.get_P1().x()+extra_space)
            # scale_bar.drawAndFill(painter=painter)

            # add a bunch of inner objects that should be packed left if they exist, and some right and some top, etc
            # so that these objects are packed
            # maybe loop over them
            # could have as many text labels as desired
            # only one letter
            # as many insets as needed

            # # center 2
            # scale_bar = ScaleBar(411/2, '<font color="#FFFFFF">10µm</font>')
            # scale_bar.set_scale(self.get_scale())
            # # scale_bar.set_P1(self.get_P1().x()+extra_space, self.get_P1().y()+extra_space)
            # scale_bar.set_P1(self.get_P1())
            # alignCenterH(center, scale_bar)
            # alignCenterV(center, scale_bar)
            # # scale_bar.set_P1(scale_bar.get_P1().x()-extra_space, scale_bar.get_P1().x()+extra_space)
            # scale_bar.drawAndFill(painter=painter)

            # TODO create 5 reference points for each object and align to those
            # loop over all extra objects that need be added

    # def fill(self, painter, draw=True):
    # if self.fill_color is None:
    #     return
    # if draw:
    #     painter.save()
    # painter.setOpacity(self.opacity)
    # if draw:
    #     if self.img is not None:
    #         qsource = QRectF(0, 0, self.img.get_width(), self.img.get_height())
    #         painter.drawImage(self, self.qimage , qsource)
    #     else:
    #         painter.drawRect(self)
    #     painter.restore()
    # self.draw(painter=painter, draw=draw)

    # def drawAndFill(self, painter):
    # painter.save()
    # if self.img is not None:
    #     qsource = QRectF(0, 0, self.img.get_width(), self.img.get_height())
    #     painter.drawImage(self, self.qimage , qsource)
    # else:
    #     painter.drawRect(self)
    # painter.restore()
    # self.draw(painter=painter)

    # def __add__(self, other):
    def __or__(self, other):
        from epyseg.figure.row import Row  # KEEP Really required to avoid circular imports
        return Row(self, other)

    # create a Fig with divide
    # def __truediv__(self, other):
    def __truediv__(self, other):
        from epyseg.figure.column import Column  # KEEP Really required to avoid circular imports
        return Column(self, other)

    def __floordiv__(self, other):
        return self.__truediv__(other=other)

    # ai je vraiment besoin de ça ? en fait le ratio suffit et faut aussi que j'intègre le crop sinon va y avoir des erreurs
    # Force the montage width to equal 'width_in_px'
    def setToWidth(self, width_in_px):
        # pure_image_width = self.width()
        # ratio = width_in_px / pure_image_width
        # self.setWidth(width_in_px)
        # self.setHeight(self.height() * ratio)
        # # we recompute the scale for the scale bar # TODO BE CAREFUL IF EXTRAS ARE ADDED TO THE OBJECT AS THIS WOULD PERTURB THE COMPUTATIONS
        # self.update_scale()
        pure_image_width = self.width(
            scaled=False)  # need original height and with in fact
        # if self.__crop_left is not None:
        #     pure_image_width -= self.__crop_left
        # if self.__crop_right is not None:
        #     pure_image_width -= self.__crop_right
        scale = width_in_px / pure_image_width
        self.scale = scale

    def setToHeight(self, height_in_px):
        # pure_image_height = self.height()
        # self.setHeight(height_in_px)
        # ratio = height_in_px / pure_image_height
        # self.setWidth(self.width() * ratio)
        # # we recompute the scale for the scale bar # TODO BE CAREFUL IF EXTRAS ARE ADDED TO THE OBJECT AS THIS WOULD PERTURB THE COMPUTATIONS
        # self.update_scale()
        pure_image_height = self.height(scaled=False)
        # if self.__crop_top is not None:
        #     pure_image_height-=self.__crop_top
        # if self.__crop_bottom is not None:
        #     pure_image_height-=self.__crop_bottom
        scale = height_in_px / pure_image_height
        self.scale = scale

    # def update_scale(self):
    #     # we recompute the scale for the scale bar # TODO BE CAREFUL IF EXTRAS ARE ADDED TO THE OBJECT AS THIS WOULD PERTURB THE COMPUTATIONS
    #     self.scale = self.get_scale()

    # def get_scale(self):
    #     # we recompute the scale for the scale bar # TODO BE CAREFUL IF EXTRAS ARE ADDED TO THE OBJECT AS THIS WOULD PERTURB THE COMPUTATIONS
    #     return self.width() / self.img.get_width()

    def crop(self, left=None, right=None, top=None, bottom=None, all=None):
        # print(self.boundingRect())
        if left is not None:
            self.__crop_left = left
            # self.setWidth(self.img.get_width() - self.__crop_left)
        if right is not None:
            self.__crop_right = right
            # self.setWidth(self.img.get_width() - self.__crop_right)
        if top is not None:
            self.__crop_top = top
            # self.setHeight(self.img.get_height() - self.__crop_top)
        if bottom is not None:
            self.__crop_bottom = bottom
            # self.setHeight(self.img.get_height() - self.__crop_bottom)
        if all is not None:
            self.__crop_left = all
            self.__crop_right = all
            self.__crop_top = all
            self.__crop_bottom = all
            # self.setWidth(self.img.get_width() - self.__crop_left)
            # self.setWidth(self.img.get_width() - self.__crop_right)
            # self.setHeight(self.img.get_height() - self.__crop_top)
            # self.setHeight(self.img.get_height() - self.__crop_bottom)

        # see how to crop actually because I need to create a qimage
        # self.qimage = self.img.crop()
        # print(self.boundingRect())

    # def set_to_scale(self, factor):
    #     self.scale = factor

    def set_to_translation(self, translation):
        self.translation = translation

    def boundingRect(self, scaled=True):
        # en fait pas good besoin de prendre les crops et le scale en compte
        # is
        # rect_to_plot = self.adjusted(self.__crop_left, self.__crop_top, -self.__crop_right, -self.__crop_bottom)
        rect_to_plot = self.adjusted(0, 0,
                                     -self.__crop_right - self.__crop_left,
                                     -self.__crop_bottom - self.__crop_top)
        # rect_to_plot = self.adjusted(-self.__crop_left, -self.__crop_top, -self.__crop_right, -self.__crop_bottom)
        # rect_to_plot = self.adjusted(0,0,0,0)
        # print('begin rect_to_plot', rect_to_plot, self.scale)
        # if kwargs['draw']==True or kwargs['fill']==True:
        # if self.scale is None or self.scale==1:
        #     painter.drawRect(self)
        # else:
        # on clone le rect
        if self.scale is not None and self.scale != 1 and scaled:
            # TODO KEEP THE ORDER THIS MUST BE DONE THIS WAY OR IT WILL GENERATE PLENTY OF BUGS...
            new_width = rect_to_plot.width() * self.scale
            new_height = rect_to_plot.height() * self.scale
            # print(rect_to_plot.width(), rect_to_plot.height())  # here ok
            # setX changes width --> why is that

            # TODO BE EXTREMELY CAREFUL AS SETX AND SETY CAN CHANGE WIDTH AND HEIGHT --> ALWAYS TAKE SIZE BEFORE OTHERWISE THERE WILL BE A PB AND ALWAYS RESET THE SIZE WHEN SETX IS CALLED!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
            # Sets the left edge of the rectangle to the given x coordinate. May change the width, but will never change the right edge of the rectangle. --> NO CLUE WHY SHOULD CHANGE WIDTH THOUGH BUT BE CAREFUL!!!
            # rect_to_plot.setX(rect_to_plot.x() * self.scale)
            # rect_to_plot.setY(rect_to_plot.y() * self.scale)
            # maybe to avoid bugs I should use translate instead rather that set x but ok anyways
            # print(rect_to_plot.width(), rect_to_plot.height())# bug here --> too big

            # print(new_height, new_height, self.width(), self.scale, self.scale* self.width())
            rect_to_plot.setWidth(new_width)
            rect_to_plot.setHeight(new_height)
        return rect_to_plot

    # def set_P1(self, *args):
    #     if not args:
    #         logger.error("no coordinate set...")
    #         return
    #     if len(args) == 1:
    #         self.moveTo(args[0].x(), args[0].y())
    #     else:
    #         self.moveTo(QPointF(args[0], args[1]))

    def get_P1(self):
        return self.boundingRect().topLeft()

    def width(self, scaled=True):
        return self.boundingRect(scaled=scaled).width()

    def height(self, scaled=True):
        return self.boundingRect(scaled=scaled).height()