예제 #1
0
    def __init__(
        self,
        images: List[ModuleOrPartialModuleImage],
        same_camera: bool = False,
        allow_different_dtypes=False,
    ):
        """Initialize a module image sequence
        
        Args:
            images (List[ModuleImage]): The list of images
            same_camera (bool): Indicates if all images are from the same camera
            allow_different_dtypes (bool): Allow images to have different datatypes?
        """

        cols = images[0].cols
        rows = images[0].rows
        for img in images:
            if img.cols != cols:
                logging.error(
                    "Cannot create sequence from different module configurations"
                )
                exit()
            if img.rows != rows:
                logging.error(
                    "Cannot create sequence from different module configurations"
                )
                exit()

        super().__init__(images, same_camera, allow_different_dtypes)
예제 #2
0
    def grid(self) -> np.ndarray:
        """Create a grid of corners according to the module geometry
        
        Returns:
            grid: (cols*rows, 2)-array of coordinates on a regular grid
        """

        if self.cols is not None and self.rows is not None:
            x, y = np.mgrid[0:self.cols + 1:1, 0:self.rows + 1:1]
            grid = np.stack([x.flatten(), y.flatten()], axis=1)
            return grid
        else:
            logging.error("Module geometry is not initialized")
            exit()
예제 #3
0
    def __init__(
        self,
        images: List[Image],
        same_camera: bool = False,
        allow_different_dtypes=False,
    ):
        """Initialize a module image sequence
        
        Args:
            images (List[Image]): The list of images
            came_camera (bool): Indicates, if all images are from the same camera and hence share the same intrinsic parameters
            allow_different_dtypes (bool): Allow images to have different datatypes?
        """

        self._images = images
        self._same_camera = same_camera
        self._allow_different_dtypes = allow_different_dtypes
        self._meta_df = None
        if len(self.images) == 0:
            logging.error("Creation of an empty sequence is not supported")
            exit()

        # check that all have the same modality, dimension, dtype and module configuration
        shape = self.images[0].shape
        dtype = self.images[0].dtype
        modality = self.images[0].modality
        # for img in self.images:
        #    if img.dtype != dtype and not allow_different_dtypes:
        #        logging.error(
        #            'Cannot create sequence from mixed dtypes. Consider using the "allow_different_dtypes" argument, when reading images.'
        #        )
        #        exit()
        #    if img.shape != shape and same_camera:
        #        logging.error(
        #            'Cannot create sequence from mixed shapes. Consider using the "same_camera" argument, when reading images.'
        #        )
        #        exit()
        #    if img.modality != modality:
        #        logging.error("Cannot create a sequence from mixed modalities.")
        #        exit()

        # namespace for accessing pandas methods
        self.pandas = self._PandasHandler(self)
예제 #4
0
def locate_module_and_cells(
    sequence: ModuleImageOrSequence,
    estimate_distortion: bool = True,
    orientation: str = None,
    return_bounding_boxes: bool = False,
) -> Union[Tuple[ModuleImageOrSequence, ObjectAnnotations], ModuleImageSequence]:
    """Locate a single module and its cells

    Note:
        This methods implements the following paper:
        Hoffmann, Mathis, et al. "Fast and robust detection of solar modules in electroluminescence images."
        International Conference on Computer Analysis of Images and Patterns. Springer, Cham, 2019.

    Args:
        sequence (ModuleImageOrSequence): A single module image or a sequence of module images 
        estimate_distortion (bool): Set True to estimate lens distortion, else False 
        orientation (str): Orientation of the module ('horizontal' or 'vertical' or None).
            If set to None (default), orientation is automatically determined
        return_bounding_boxes (bool): Indicates, if bounding boxes of returned modules are returned

    Returns:
        images: The same image/sequence with location information added
    """

    if sequence[0].modality != EL_IMAGE:
        logging.error("Module localization is not supporting given imaging modality")
        exit()

    result = list()
    failures = 0
    mcs = list()
    dts = list()
    flags = list()
    transforms = list()
    for img in tqdm(sequence.images):
        t, mc, dt, f = apply(
            img.data,
            img.cols,
            img.rows,
            is_module_detail=isinstance(img, PartialModuleImage),
            orientation=orientation,
        )
        transforms.append(t)
        flags.append(f)
        mcs.append(mc)
        dts.append(dt)

    if estimate_distortion:
        if sequence.same_camera:
            # do joint estimation
            logging.info(
                "Jointly estimating parameters for lens distortion. This might take some time.."
            )

            mcs_new = list()
            dts_new = list()
            valid = list()
            for mc, dt, f in zip(mcs, dts, flags):
                if mc is not None and dt is not None:
                    mcs_new.append(mc[f])
                    dts_new.append(dt[f])
                    valid.append(True)
                else:
                    valid.append(False)
            transforms = FullMultiTransform(
                mcs_new,
                dts_new,
                image_width=sequence.shape[1],
                image_height=sequence.shape[0],
                n_dist_coeff=1,
            )
            transforms_new = list()
            i = 0
            for v in valid:
                if v:
                    transforms_new.append(transforms[i])
                    i += 1
                else:
                    transforms_new.append(None)
            transforms = transforms_new

        else:
            transforms = list()
            for mc, dt, f, img in zip(mcs, dts, flags, sequence.images):
                if mc is not None and dt is not None:
                    t = FullTransform(
                        mc[f],
                        dt[f],
                        image_width=img.shape[1],
                        image_height=img.shape[0],
                        n_dist_coeff=1,
                    )
                    transforms.append(t)
                else:
                    transforms.append(None)

    for t, img in zip(transforms, sequence.images):
        if t is not None and t.valid:
            img_res = type(img).from_other(img, meta={"transform": t})
            result.append(img_res)
        else:
            result.append(deepcopy(img))
            failures += 1
    if failures > 0:
        logging.warning("Module localization falied for {:d} images".format(failures))

    result = ModuleImageSequence.from_other(sequence, images=result)

    if not return_bounding_boxes:
        return result
    else:
        boxes = dict()

        # compute polygon for every module and accumulate results
        for img in result:
            if img.has_meta("transform"):
                c = img.cols
                r = img.rows
                coords = np.array([[0.0, 0.0], [c, 0.0], [c, r], [0.0, r]])
                coords_transformed = img.get_meta("transform")(coords)
                poly = Polygon(
                    [
                        (x, y)
                        for x, y in zip(
                            coords_transformed[:, 0].tolist(),
                            coords_transformed[:, 1].tolist(),
                        )
                    ]
                )
                boxes[img.path.name] = [("Module", poly)]
            else:
                boxes[img.path.name] = []

        return result, boxes
예제 #5
0
def segment_module_part(
    image: ModuleImage,
    first_col: int,
    first_row: int,
    cols: int,
    rows: int,
    size: int = None,
    padding: float = 0.0,
) -> PartialModuleImage:
    """Segment a part of a module

    Args:
        image (ModuleImage): The corresponding module image
        first_col (int): First column to appear in the segment
        first_row (int): First row to appear in the segment
        cols (int): Number of columns of the segment
        rows (int): Number of rows of the segment
        size (int): Size of a cell in pixels (automatically chosen by default)
        padding (float): Optional padding around the given segment relative to the cell size
                         (must be in [0..1[ )

    Returns:
        segment: The resulting segment
    """

    if not image.has_meta("transform") or not image.get_meta("transform").valid:
        logging.error(
            "The ModuleImage does not have a valid transform. Did module localization succeed?"
        )
        exit()

    t = image.get_meta("transform")

    if padding >= 1.0 or padding < 0.0:
        logging.error("padding needs to be in [0..1[")
        exit()

    last_col = first_col + cols
    last_row = first_row + rows

    size = t.mean_scale() if size is None else size
    result = warp_image(
        image.data,
        t,
        first_col - padding,
        first_row - padding,
        1 / size,
        1 / size,
        cols + 2 * padding,
        rows + 2 * padding,
    )
    result = result.astype(image.data.dtype)
    transform = HomographyTransform(
        np.array(
            [
                [first_col - padding, first_row - padding],
                [last_col + padding, first_row - padding],
                [last_col + padding, last_row + padding],
                [first_col - padding, last_row + padding],
            ]
        ),
        np.array(
            [
                [0.0, 0.0],
                [result.shape[1], 0.0],
                [result.shape[1], result.shape[0]],
                [0.0, result.shape[0]],
            ]
        ),
    )

    # bounding box in original image coords
    bb = [
        [first_col - padding, first_row - padding],
        [first_col + cols + padding, first_row + rows + padding],
    ]
    bb = t(np.array(bb))
    bb = Polygon.from_bounds(bb[0][0], bb[0][1], bb[1][0], bb[1][1])
    original = image.from_other(image, meta={"segment_module_original_box": bb})

    return PartialModuleImage.from_other(
        image,
        drop_meta_types=[Polygon],  # geometric attributes are invalid now..
        data=result,
        cols=cols + min(first_col, 0),
        rows=rows + min(first_row, 0),
        first_col=first_col if first_col >= 0 else None,
        first_row=first_row if first_row >= 0 else None,
        meta={"transform": transform, "segment_module_original": original},
    )