예제 #1
0
    def folder(
        self,
        input: Path,
        output: Path,
        skip_existing: bool = False,
        reverse: bool = False,
        delete_input: bool = False,
    ) -> None:
        # TODO preserve folder structure on/off
        input = input.resolve()
        output = output.resolve()
        if not input.exists():
            self.log.error(f'Folder "{input}" does not exist.')
            sys.exit(1)
        elif input.is_file():
            self.log.error(f'Folder "{input}" is a file.')
            sys.exit(1)
        elif output.is_file():
            self.log.error(f'Folder "{output}" is a file.')
            sys.exit(1)
        elif not output.exists():
            output.mkdir(parents=True)

        images: List[Path] = []
        for ext in ["png", "jpg", "jpeg", "gif", "bmp", "tiff", "tga"]:
            images.extend(input.glob(f"**/*.{ext}"))
        images = sorted(list(images), reverse=reverse)

        if self.zip:
            output_zip_path = output.joinpath(
                f"{input.stem}_{'_'.join([Path(x).stem for x in self.model_chain])}.zip"
            )
            if skip_existing and output_zip_path.is_file():
                self.log.warning(
                    f"Zip {output_zip_path.stem} already exists, skipping")
                exit()
        with Progress(
                # SpinnerColumn(),
                "[progress.description]{task.description}",
                BarColumn(),
                "[progress.percentage]{task.percentage:>3.0f}%",
                TimeRemainingColumn(),
        ) as progress:
            task_upscaling = progress.add_task("Upscaling", total=len(images))
            if self.zip:
                cm = zipfs.WriteZipFS(output_zip_path)
            else:
                cm = nullcontext()
            with cm as zip_fs:
                threads = []
                for idx, img_path in enumerate(images, 1):
                    img_input_path_rel = img_path.relative_to(input)
                    output_dir = output.joinpath(img_input_path_rel).parent
                    img_output_path_rel = output_dir.joinpath(
                        f"{img_path.stem}.{'jpg' if self.jpg else 'png'}")
                    if not self.zip:
                        output_dir.mkdir(parents=True, exist_ok=True)
                    # if len(self.model_chain) == 1:
                    #     self.log.info(
                    #         f'Processing {str(idx).zfill(len(str(len(images))))}: "{img_input_path_rel}"'
                    #     )
                    if not self.zip and skip_existing and img_output_path_rel.is_file(
                    ):
                        self.log.warning("Already exists, skipping")
                        if delete_input:
                            img_path.unlink(missing_ok=True)
                        progress.advance(task_upscaling)
                        continue
                    # read image
                    if (img_path.suffix.lower() == ".bmp"
                            or img_path.suffix.lower() == ".tga"):
                        with WandImage(
                                filename=str(img_path.absolute())) as wimg:
                            if wimg.format == "TGA":
                                wimg.flip()
                            img = np.array(wimg)
                            img = cv2.cvtColor(img, cv2.COLOR_BGRA2RGBA)
                    else:
                        img = cv2.imread(str(img_path.absolute()),
                                         cv2.IMREAD_UNCHANGED)

                    progress_text = f'{str(idx).zfill(len(str(len(images))))} - "{img_input_path_rel}"'
                    if self.multi_gpu:
                        device, _ = self.get_available_device()
                    else:
                        device = list(self.devices.keys())[0]

                    folder_thread_func_args = {
                        "img": img,
                        "zip_fs": zip_fs,
                        "img_output_path_rel": img_output_path_rel,
                        "device": device,
                        "task_upscaling": task_upscaling,
                        "progress": progress,
                        "progress_text": progress_text,
                        "output_zip_path":
                        output_zip_path if self.zip else None,
                    }
                    if self.multi_gpu:
                        x = Thread(
                            target=self.__folder_thread_func,
                            kwargs=folder_thread_func_args,
                        )
                        threads.append(x)
                        x.daemon = True
                        x.start()
                    else:
                        self.__folder_thread_func(**folder_thread_func_args)

                    if delete_input:
                        img_path.unlink(missing_ok=True)

                for thread in threads:
                    thread.join()
예제 #2
0
    def _pilbox_operation(self, operation):
        """
        Use Pilbox to transform an image
        """

        image = PilboxImage(BytesIO(self.data))

        if operation == "region":
            image.region(self.options.get("rect").split(','))

        elif operation == "rotate":
            image.rotate(deg=self.options.get("deg"),
                         expand=self.options.get("expand"))
        elif operation == "resize":
            max_width = self.options.get("max-width")
            max_height = self.options.get("max-height")

            resize_width = self.options.get("w")
            resize_height = self.options.get("h")

            # Make sure widths and heights are integers
            if resize_width:
                resize_width = int(resize_width)
            if resize_height:
                resize_height = int(resize_height)
            if max_width:
                max_width = int(max_width)
            if max_height:
                max_height = int(max_height)

            # Image size management
            with WandImage(blob=self.data) as image_info:
                # Don't allow expanding of images
                if resize_width or resize_height:
                    width_oversize = resize_width > image_info.width
                    height_oversize = resize_height > image_info.height

                    if (width_oversize or height_oversize):
                        expand_message = (
                            "Resize error: Maximum dimensions for this image "
                            "are {0}px wide by {1}px high.").format(
                                image_info.width, image_info.height)

                        raise PilboxError(400, log_message=expand_message)

                # Process max_width and max_height
                if not resize_width and max_width:
                    if max_width < image_info.width:
                        resize_width = max_width

                if not resize_height and max_height:
                    if max_height < image_info.height:
                        resize_height = max_height

            if resize_height or resize_width:
                image.resize(width=resize_width,
                             height=resize_height,
                             mode=self.options.get("mode"),
                             filter=self.options.get("filter"),
                             background=self.options.get("bg"),
                             retain=self.options.get("retain"),
                             position=self.options.get("pos"))

        self.data = image.save(quality=self.options.get("q")).read()
예제 #3
0
    def first_button(self):
        vba_directory = self.entry1.get()
        pdf_pack_name = self.entry2.get()
        trackingsheet = self.entry3.get()

        # Build OS Path
        cropped_directory = os.path.join(vba_directory, 'Cropped Images')

        # Make Cropped Files directory if it doesn't already exist
        os.makedirs(cropped_directory, exist_ok=True)

        # Prepare Paths
        pdfSuffix = '.pdf'
        pdfPack = os.path.join(vba_directory, pdf_pack_name + pdfSuffix)
        pdfSaver = os.path.join(cropped_directory, pdf_pack_name)

        # Convert PDF to separate tiff files
        try:
            with (WandImage(filename=pdfPack, resolution=200)) as source:
                images = source.sequence
                pages = len(images)
                for i in range(pages):
                    WandImage(images[i]).save(filename=pdfSaver + str(i + 1) +
                                              '.tiff')
        except:
            messagebox.showerror(
                'WARNING - Something went wrong',
                "Verify that you\'ve correctly entered "
                "the directories and pdf file name")

        # Iterate through images to begin parsing
        for page in range(1, pages + 1):
            # Select page number to parse text from
            pagenumber = str(page)
            FileToParse = pdfSaver + pagenumber + '.tiff'

            # Read & Write image back as tiff
            ImageToConvert = cv2.imread(FileToParse)
            cv2.imwrite(cropped_directory + "/" + "A_FirstStep.tiff",
                        ImageToConvert)

            # Convert to Black and White - slide pixels to the closest white or red
            col = IMG.open(cropped_directory + "/" + "A_FirstStep.tiff")
            gray = col.convert('L')
            bw = gray.point(lambda x: 0 if x < 128 else 255, '1')
            bw.save(cropped_directory + "/" + "A_Black and White Claim.tiff")

            # Create dictionary with crop specifications for each field of interest
            DictionaryOfCrops = {}
            DictionaryOfCrops["Claim Address"] = (20, 170, 20, 530)
            DictionaryOfCrops["Patient Control Number"] = (20, 75, 1070, 1580)
            DictionaryOfCrops["Medical Record"] = (75, 105, 1070, 1400)
            DictionaryOfCrops["Type of Bill"] = (75, 105, 1563, 1650)
            DictionaryOfCrops["Fed Tax Number"] = (130, 175, 1015, 1220)
            DictionaryOfCrops["Statement From"] = (135, 175, 1220, 1360)
            DictionaryOfCrops["Statement To"] = (135, 175, 1365, 1510)
            DictionaryOfCrops["Patient Name"] = (200, 240, 20, 530)
            DictionaryOfCrops["Patient Birth Date"] = (260, 310, 20, 205)
            DictionaryOfCrops["ConditionCode18"] = (260, 310, 680, 742)
            DictionaryOfCrops["ConditionCode19"] = (260, 310, 742, 802)
            DictionaryOfCrops["ConditionCode20"] = (260, 310, 802, 862)
            DictionaryOfCrops["ConditionCode21"] = (260, 310, 862, 922)
            DictionaryOfCrops["ConditionCode22"] = (260, 310, 922, 982)
            DictionaryOfCrops["ConditionCode23"] = (260, 310, 982, 1042)
            DictionaryOfCrops["ConditionCode24"] = (260, 310, 1042, 1102)
            DictionaryOfCrops["ConditionCode25"] = (260, 310, 1102, 1162)
            DictionaryOfCrops["ConditionCode26"] = (260, 310, 1162, 1222)
            DictionaryOfCrops["ConditionCode27"] = (260, 310, 1222, 1282)
            DictionaryOfCrops["ConditionCode28"] = (260, 310, 1282, 1342)
            DictionaryOfCrops["Payer Address"] = (400, 570, 20, 860)
            DictionaryOfCrops["Value Codes Amounts 39 - Code"] = (440, 570,
                                                                  880, 940)
            DictionaryOfCrops["Value Codes Amounts 39 - Amount"] = (440, 570,
                                                                    940, 1095)
            DictionaryOfCrops["Value Codes Amounts 39 - Decimals"] = (440, 570,
                                                                      1095,
                                                                      1142)
            DictionaryOfCrops["Value Codes Amounts 40 - Code"] = (440, 570,
                                                                  1142, 1200)
            DictionaryOfCrops["Value Codes Amounts 40 - Amount"] = (440, 570,
                                                                    1200, 1365)
            DictionaryOfCrops["Value Codes Amounts 40 - Decimals"] = (440, 570,
                                                                      1365,
                                                                      1403)
            DictionaryOfCrops["Value Codes Amounts 41 - Code"] = (440, 570,
                                                                  1403, 1460)
            DictionaryOfCrops["Value Codes Amounts 41 - Amount"] = (440, 570,
                                                                    1461, 1620)
            DictionaryOfCrops["Value Codes Amounts 41 - Decimals"] = (440, 570,
                                                                      1620,
                                                                      1665)
            DictionaryOfCrops["Revenue Codes"] = (600, 1340, 20, 120)
            DictionaryOfCrops["Treatment Descriptions"] = (600, 1340, 121, 620)
            DictionaryOfCrops["HCPC Codes"] = (600, 1340, 620, 900)
            DictionaryOfCrops["Service Dates"] = (600, 1340, 910, 1059)
            DictionaryOfCrops["Service Units"] = (600, 1340, 1059, 1220)
            DictionaryOfCrops["Service Charges"] = (600, 1340, 1214, 1365)
            DictionaryOfCrops["Service Charges Decimals"] = (600, 1340, 1355,
                                                             1420)
            DictionaryOfCrops["Pages Number"] = (1340, 1372, 130, 280)
            DictionaryOfCrops["Pages Total Number"] = (1340, 1372, 280, 500)
            DictionaryOfCrops["Creation Date"] = (1340, 1372, 905, 1100)
            DictionaryOfCrops["Total Value"] = (1338, 1372, 1200, 1355)
            DictionaryOfCrops["Total Value Decimals"] = (1338, 1372, 1355,
                                                         1410)
            DictionaryOfCrops["Payer Name"] = (1400, 1510, 20, 480)
            DictionaryOfCrops["Document Control No"] = (1662, 1700, 630, 1160)
            DictionaryOfCrops["DX Code"] = (1766, 1802, 193, 355)
            DictionaryOfCrops["Physician Last"] = (1900, 1936, 1020, 1375)
            DictionaryOfCrops["Physician First"] = (1900, 1936, 1375, 1660)

            # Iterate through each field, crop and save image
            img = cv2.imread(cropped_directory + "/" +
                             'A_Black and White Claim.tiff')
            for key in DictionaryOfCrops:
                crop_img = img[
                    DictionaryOfCrops[key][0]:DictionaryOfCrops[key][1],
                    DictionaryOfCrops[key][2]:DictionaryOfCrops[key][3]]
                cv2.imwrite(
                    cropped_directory + "/" + "Cropped_" + key + ".tiff",
                    crop_img)

            # Create two tuples, one for fields with strict numerical expectation and one for string expectation
            StringFieldList = ("Claim Address", "Patient Name",
                               "Payer Address", "Treatment Descriptions",
                               "Payer Name", "Physician Last",
                               "Physician First", "DX Code", "HCPC Codes")
            NumericFieldList = (
                "Patient Control Number", "Medical Record", "Type of Bill",
                "Fed Tax Number", "Statement From", "Statement To",
                "Patient Birth Date", "ConditionCode18", "ConditionCode19",
                "ConditionCode20", "ConditionCode21", "ConditionCode22",
                "ConditionCode23", "ConditionCode24", "ConditionCode25",
                "ConditionCode26", "ConditionCode27", "ConditionCode28",
                "Value Codes Amounts 39 - Code",
                "Value Codes Amounts 39 - Amount",
                "Value Codes Amounts 39 - Decimals",
                "Value Codes Amounts 40 - Code",
                "Value Codes Amounts 40 - Amount",
                "Value Codes Amounts 40 - Decimals",
                "Value Codes Amounts 41 - Code",
                "Value Codes Amounts 41 - Amount",
                "Value Codes Amounts 41 - Decimals", "Revenue Codes",
                "Service Dates", "Service Units", "Service Charges",
                "Service Charges Decimals", "Pages Number",
                "Pages Total Number", "Creation Date", "Total Value",
                "Total Value Decimals", "Document Control No")

            # Create OCR Extraction Dictionary, run tesseract OCR through each of our lists
            TesseractExtracts = {}

            for value in StringFieldList:
                img = IMG.open(cropped_directory + "/" + 'Cropped_' + value +
                               '.tiff')
                img.load()
                TesseractExtracts[value] = pytesseract.image_to_string(
                    img, config='-psm 6')

            for value in NumericFieldList:
                img = IMG.open(cropped_directory + "/" + 'Cropped_' + value +
                               '.tiff')
                img.load()
                TesseractExtracts[value] = pytesseract.image_to_string(
                    img, config='-c tessedit_char_whitelist=0123456789 -psm 6')

            # Split OCR extracts by page break
            for values in TesseractExtracts:
                TesseractExtracts[values] = TesseractExtracts[values].split(
                    '\n')

            # Delete list items with empty content
            for values in TesseractExtracts:
                TesseractExtracts[values] = [
                    i for i in TesseractExtracts[values] if i != ''
                ]

            # Create a list with fields that text should be single sentences
            CollapsableFields = ("Claim Address", "Payer Address",
                                 "Payer Name")
            CollapsedTesseractExtracts = {}

            # Flatten single sentences and join with 'space'
            for values in TesseractExtracts:
                if values in CollapsableFields:
                    CollapsedTesseractExtracts[values] = ' '.join(
                        TesseractExtracts[values])
                else:
                    CollapsedTesseractExtracts[values] = TesseractExtracts[
                        values]

            # Create empty Pandas Dataframe
            extractionframe = pd.DataFrame(columns=[
                "Claim Address", "Patient Control Number", "Medical Record",
                "Type of Bill", "Fed Tax Number", "Statement From",
                "Statement To", "Patient Name", "Patient Birth Date",
                "ConditionCode18", "ConditionCode19", "ConditionCode20",
                "ConditionCode21", "ConditionCode22", "ConditionCode23",
                "ConditionCode24", "ConditionCode25", "ConditionCode26",
                "ConditionCode27", "ConditionCode28", "Payer Address",
                "Value Codes Amounts 39 - Code",
                "Value Codes Amounts 39 - Amount",
                "Value Codes Amounts 39 - Decimals",
                "Value Codes Amounts 40 - Code",
                "Value Codes Amounts 40 - Amount",
                "Value Codes Amounts 40 - Decimals",
                "Value Codes Amounts 41 - Code",
                "Value Codes Amounts 41 - Amount",
                "Value Codes Amounts 41 - Decimals", "Revenue Codes",
                "Treatment Descriptions", "HCPC Codes", "Service Dates",
                "Service Units", "Service Charges", "Service Charges Decimals",
                "Pages Number", "Pages Total Number", "Creation Date",
                "Total Value", "Total Value Decimals", "Payer Name",
                "Document Control No", "DX Code", "Physician Last",
                "Physician First"
            ])

            # Populate dataframe
            for values in CollapsedTesseractExtracts:
                if isinstance(CollapsedTesseractExtracts[values],
                              list) is False:
                    try:
                        extractionframe.loc[
                            1, values] = CollapsedTesseractExtracts[values]
                    except:
                        pass
                else:
                    for i in range(len(CollapsedTesseractExtracts[values])):
                        extractionframe.loc[
                            i + 1,
                            values] = CollapsedTesseractExtracts[values][i]

            # Populate document and page
            extractionframe.loc[1, 'document'] = pdf_pack_name
            extractionframe.loc[1, 'source'] = 'page_' + pagenumber

            # Forward Fill NaN values
            extractionframe = extractionframe.fillna(method='ffill')

            # Replace NaN with empty string
            extractionframe = extractionframe.replace(np.nan, '', regex=True)

            try:
                # Load Workbook
                load_path = os.path.join(vba_directory,
                                         trackingsheet + '.xlsm')

                workbook = load_workbook(load_path, keep_vba=True)

                # Select Outputsheet
                output_sheet = workbook['Output']

                # Append Results
                for row in dataframe_to_rows(extractionframe,
                                             index=False,
                                             header=False):
                    output_sheet.append(row)

                workbook.save(load_path)

            except:
                messagebox.showerror(
                    'WARNING - Something went wrong',
                    "Verify that you\'ve correctly entered "
                    "the excel file name and that it is "
                    "of the provided, VBA-enabled format"
                    "\n \n"
                    "Alternatively, verify that the files is not"
                    "in use while trying to run the script")
                Tk.quit()
예제 #4
0
    def process(self, descriptor: StreamDescriptor, context: dict):

        # Ensuring the wand package is installed.
        ensure_wand()
        # noinspection PyPackageRequirements
        from wand.image import Image as WandImage

        # Copy the original info
        # generating thumbnail and storing in buffer
        # noinspection PyTypeChecker
        img = WandImage(file=descriptor)

        if self.crop is None and (
                self.format is None or img.format == self.format) and (
                    (self.width is None or img.width == self.width) and
                    (self.height is None or img.height == self.height)):
            img.close()
            descriptor.prepare_to_read(backend='memory')
            return

        if 'length' in context:
            del context['length']

        # opening the original file
        output_buffer = io.BytesIO()
        with img:
            # Changing format if required.
            if self.format and img.format != self.format:
                img.format = self.format

            # Changing dimension if required.
            if self.width or self.height:
                width, height, _ = validate_width_height_ratio(
                    self.width, self.height, None)
                img.resize(
                    width(img.size) if callable(width) else width,
                    height(img.size) if callable(height) else height)

            # Cropping
            if self.crop:
                img.crop(
                    **{
                        key: int(
                            int(value[:-1]) / 100 *
                            (img.width if key in ('width', 'left',
                                                  'right') else img.height)
                        ) if key in ('left', 'top', 'right', 'bottom', 'width',
                                     'height') and isinstance(value, str)
                        and '%' in value else value
                        for key, value in self.crop.items()
                    })

            img.save(file=output_buffer)

            context.update(content_type=img.mimetype,
                           width=img.width,
                           height=img.height,
                           extension=guess_extension(img.mimetype))

        output_buffer.seek(0)
        descriptor.replace(output_buffer, position=0, **context)
예제 #5
0
def to_wand_image(float_tensor):
    with io.BytesIO() as buf:
        TF.to_pil_image(float_tensor).save(buf, "BMP")
        return WandImage(blob=buf.getvalue())
예제 #6
0
def create_img(arg1, arg2, arg3):
    img_width = 600
    img_height = 200

    text1 = arg1
    text1_font = 50
    text1_y = 40

    text2 = arg2
    text2_font = 20
    text2_x = (img_width / 2) - (len(text2) * 6)
    text2_y = 120

    text3 = arg3
    text3_font = 20
    text3_x = (img_width / 2) - (len(text3) * 5)
    text3_y = img_height - 50

    folder = "static/"
    tmp_text_file = folder + str(uuid.uuid4()) + ".png"
    tmp_img_file = folder + str(uuid.uuid4()) + ".png"

    # CREATING DROPSHADOW
    #==============================

    image = Image.new('RGB', (img_width, img_height), 'white')
    draw = ImageDraw.Draw(image)
    font = ImageFont.truetype("./static/fonts/LibreBaskerville-Regular.ttf",
                              text1_font)
    x, y = draw.textsize(text1, font=font)

    # make sure text is within box
    while x > 600:
        text1_font -= 1
        font = ImageFont.truetype(
            "./static/fonts/LibreBaskerville-Regular.ttf", text1_font)
        x, y = draw.textsize(text1, font=font)

    text1_x = img_width / 2 - (int(x) / 2)

    # draw the shadow by moving and drawing the text to the bottom right for 20 iterations
    # and also making it lighter as we iterate
    n = 0
    while n < 20:
        draw.text((text1_x + n + 2, text1_y + n + 2),
                  text1, (120 + n * 5, 120 + n * 5, 120 + n * 5),
                  font=font)
        if n > 15:
            image = image.filter(ImageFilter.BLUR)
        n += 1

    image.save(tmp_img_file)

    # CREATING EMBOSSED TEXT
    #================================
    image = Image.new('RGBA', (img_width, img_height))
    draw = ImageDraw.Draw(image)
    x, y = draw.textsize(text1, font=font)

    while x > 600:
        text1_font -= 1
        font = ImageFont.truetype(
            "./static/fonts/LibreBaskerville-Regular.ttf", text1_font)
        x, y = draw.textsize(text1, font=font)

    text1_x = img_width / 2 - (int(x) / 2)
    text = draw.text((text1_x, text1_y), text1, ('#26343F'), font=font)
    image.save(tmp_text_file, 'PNG')

    # Uses 3rd party bevel script that uses imagemagick to add effects
    command = "./lib/bevel.sh -w 10 -f inner -o raised {0} {0}".format(
        tmp_text_file)

    import subprocess
    subprocess.check_output(['bash', '-c', command])

    text = Image.open(tmp_text_file)
    edit1_img = Image.open(tmp_img_file)
    edit1_img.paste(text, (0, 0), text)
    edit1_img.save(tmp_img_file, 'PNG')

    # CREATING OTHER WORDS
    #===============================

    with Drawing() as draw:
        with WandImage(filename=tmp_img_file) as img:

            draw.font = './static/fonts/Calibri.ttf'
            draw.font_size = text2_font
            draw.fill_color = Color('#222324')
            text2 = " ".join(text2)
            x = draw.get_font_metrics(img,
                                      text2)[11]  #12th is the x in the metric
            text2_x = (img_width / 2) - (int(x) / 2)
            draw.text(text2_x, text2_y, text2)

            draw.font = './static/fonts/matura-mt-script.ttf'
            draw.font_size = text3_font
            draw.fill_color = Color('#7F9DAC')
            x = draw.get_font_metrics(img,
                                      text3)[11]  #12th is the x in the metric
            text3_x = (img_width / 2) - (int(x) / 2)
            draw.text(text3_x, text3_y, text3)

            draw.draw(img)
            img.save(filename=tmp_img_file)
            return tmp_img_file.replace("static/", "")
예제 #7
0
    def generate_thumbnail(self,
                           ratio=None,
                           width=None,
                           height=None,
                           filter='undefined',
                           store=current_store,
                           _preprocess_image=None,
                           _postprocess_image=None):
        """Resizes the :attr:`original` (scales up or down) and
        then store the resized thumbnail into the ``store``.

        :param ratio: resize by its ratio.  if it's greater than 1
                      it scales up, and if it's less than 1 it scales
                      down.  exclusive for ``width`` and ``height``
                      parameters
        :type ratio: :class:`numbers.Real`
        :param width: resize by its width.  exclusive for ``ratio``
                      and ``height`` parameters
        :type width: :class:`numbers.Integral`
        :param height: resize by its height.  exclusive for ``ratio``
                       and ``width`` parameters
        :type height: :class:`numbers.Integral`
        :param filter: a filter type to use for resizing.  choose one in
                       :const:`wand.image.FILTER_TYPES`.  default is
                       ``'undefined'`` which means ImageMagick will try
                       to guess best one to use
        :type filter: :class:`str`, :class:`numbers.Integral`
        :param store: the storage to store the resized image file.
                      :data:`~sqlalchemy_imageattach.context.current_store`
                      by default
        :type store: :class:`~sqlalchemy_imageattach.store.Store`
        :param _preprocess_image: internal-use only option for preprocessing
                                  original image before resizing
        :type _preprocess_image:
            :class:`typing.Callable`\ [[:class:`wand.image.Image`],
                                        :class:`wand.image.Image`]
        :param _postprocess_image: internal-use only option for preprocessing
                                   original image before resizing
        :type _postprocess_image:
            :class:`typing.Callable`\ [[:class:`wand.image.Image`],
                                        :class:`wand.image.Image`]
        :returns: the resized thumbnail image.  it might be an already
                  existing image if the same size already exists
        :rtype: :class:`Image`
        :raise IOError: when there's no :attr:`original` image yet

        """
        params = ratio, width, height
        param_count = sum(p is not None for p in params)
        if not param_count:
            raise TypeError('pass an argument ratio, width, or height')
        elif param_count > 1:
            raise TypeError('pass only one argument in ratio, width, or '
                            'height; these parameters are exclusive for '
                            'each other')

        query = self.query
        transient = Session.object_session(query.instance) is None
        state = instance_state(query.instance)
        try:
            added = state.committed_state[query.attr.key].added_items
        except KeyError:
            added = []
        if width is not None:
            if not isinstance(width, numbers.Integral):
                raise TypeError('width must be integer, not ' + repr(width))
            elif width < 1:
                raise ValueError('width must be natural number, not ' +
                                 repr(width))
            # find the same-but-already-generated thumbnail
            for image in added:
                if image.width == width:
                    return image
            if not transient:
                q = query.filter_by(width=width)
                try:
                    return q.one()
                except NoResultFound:
                    pass

            def height(sz):
                return sz[1] * (width / sz[0])
        elif height is not None:
            if not isinstance(height, numbers.Integral):
                raise TypeError('height must be integer, not ' + repr(height))
            elif height < 1:
                raise ValueError('height must be natural number, not ' +
                                 repr(height))
            # find the same-but-already-generated thumbnail
            for image in added:
                if image.height == height:
                    return image
            if not transient:
                q = query.filter_by(height=height)
                try:
                    return q.one()
                except NoResultFound:
                    pass

            def width(sz):
                return sz[0] * (height / sz[1])
        elif ratio is not None:
            if not isinstance(ratio, numbers.Real):
                raise TypeError('ratio must be an instance of numbers.Real, '
                                'not ' + repr(ratio))

            def width(sz):
                return sz[0] * ratio

            def height(sz):
                return sz[1] * ratio

        data = io.BytesIO()
        image = self.require_original()
        with image.open_file(store=store) as f:
            if _preprocess_image is None:
                img = WandImage(file=f)
            else:
                with WandImage(file=f) as img:
                    img = _preprocess_image(img)
            with img:
                if img.mimetype in VECTOR_TYPES:
                    img.format = 'png'
                original_size = img.size
                if callable(width):
                    width = width(original_size)
                if callable(height):
                    height = height(original_size)
                width = int(width)
                height = int(height)
                # find the same-but-already-generated thumbnail
                for image in added:
                    if image.width == width and image.height == height:
                        return image
                if not transient:
                    q = query.filter_by(width=width, height=height)
                    try:
                        return q.one()
                    except NoResultFound:
                        pass
                if len(img.sequence) > 1:
                    img_ctx = img.sequence[0].clone()
                    img_ctx.resize(width, height, filter=filter)
                else:
                    img_ctx = NoopContext(img)
                with img_ctx as single_img:
                    single_img.resize(width, height, filter=filter)
                    if _postprocess_image is None:
                        mimetype = img.mimetype
                        single_img.save(file=data)
                    else:
                        with _postprocess_image(img) as img:
                            mimetype = img.mimetype
                            single_img.save(file=data)
        return self.from_raw_file(data,
                                  store,
                                  size=(width, height),
                                  mimetype=mimetype,
                                  original=False)
예제 #8
0
    def from_raw_file(self,
                      raw_file,
                      store=current_store,
                      size=None,
                      mimetype=None,
                      original=True,
                      extra_args=None,
                      extra_kwargs=None):
        """Similar to :meth:`from_file()` except it's lower than that.
        It assumes that ``raw_file`` is readable and seekable while
        :meth:`from_file()` only assumes the file is readable.
        Also it doesn't make any in-memory buffer while
        :meth:`from_file()` always makes an in-memory buffer and copy
        the file into the buffer.

        If ``size`` and ``mimetype`` are passed, it won't try to read
        image and will use these values instead.

        It's used for implementing :meth:`from_file()` and
        :meth:`from_blob()` methods that are higher than it.

        :param raw_file: the seekable and readable file of the image
        :type raw_file: file-like object, :class:`file`
        :param store: the storage to store the file.
                      :data:`~sqlalchemy_imageattach.context.current_store`
                      by default
        :type store: :class:`~sqlalchemy_imageattach.store.Store`
        :param size: an optional size of the image.
                     automatically detected if it's omitted
        :type size: :class:`tuple`
        :param mimetype: an optional mimetype of the image.
                         automatically detected if it's omitted
        :type mimetype: :class:`str`
        :param original: an optional flag which represents whether
                         it is an original image or not.
                         defualt is :const:`True` (meaning original)
        :type original: :class:`bool`
        :param extra_args: additional arguments to pass to the model's
                           constructor.
        :type extra_args: :class:`collections.abc.Sequence`
        :param extra_kwargs: additional keyword arguments to pass to the
                             model's constructor.
        :type extra_kwargs: :class:`typing.Mapping`\ [:class:`str`,
                                                      :class:`object`]
        :returns: the created image instance
        :rtype: :class:`Image`

        .. versionadded:: 1.0.0
           The ``extra_args`` and ``extra_kwargs`` options.

        """
        query = self.query
        cls = query.column_descriptions[0]['type']
        if not (isinstance(cls, type) and issubclass(cls, Image)):
            raise TypeError('the first entity must be a subtype of '
                            'sqlalchemy_imageattach.entity.Image')

        if original and query.session:
            if store is current_store:
                for existing in query:
                    test_data = existing.identity_map.copy()
                    test_data.update(self.identity_map)
                    if existing.identity_map == test_data:
                        query.remove(existing)
                query.session.flush()
            else:
                with store_context(store):
                    for existing in query:
                        test_data = existing.identity_map.copy()
                        test_data.update(self.identity_map)
                        if existing.identity_map == test_data:
                            query.remove(existing)
                    query.session.flush()
        if size is None or mimetype is None:
            with WandImage(file=raw_file) as wand:
                size = size or wand.size
                mimetype = mimetype or wand.mimetype
        if mimetype.startswith('image/x-'):
            mimetype = 'image/' + mimetype[8:]

        if extra_kwargs is None:
            extra_kwargs = {}
        extra_kwargs.update(self.identity_map)

        if extra_args is None:
            extra_args = ()

        image = cls(size=size,
                    mimetype=mimetype,
                    original=original,
                    *extra_args,
                    **extra_kwargs)
        raw_file.seek(0)
        image.file = raw_file
        image.store = store
        query.append(image)
        return image
예제 #9
0
def update_patient(patient, form, files):
    """Update a patient record with information from submitted form."""
    for field_name, class_name in [('income_sources', IncomeSource),
                                   ('phone_numbers', PhoneNumber),
                                   ('addresses', Address),
                                   ('emergency_contacts', EmergencyContact),
                                   ('household_members', HouseholdMember),
                                   ('employers', Employer),
                                   ('document_images', DocumentImage)]:
        if form[field_name]:
            # If the last row in a many-to-one section doesn't have any data, don't save it
            remove_blank_rows_helper(form[field_name])

            # Add a new child object for each new item in a many-to-one section
            new_row_count = len(form[field_name].entries) - getattr(
                patient, field_name).count()
            if new_row_count > 0:
                for p in range(new_row_count):
                    getattr(patient, field_name).append(class_name())

            # When a user clicks the delete icon on a many-to-one row, it clears
            # all the data in that row. If any existing rows have no data, delete
            # them from patient object and then from the form.
            for row in form[field_name]:
                if not bool([
                        val for key, val in row.data.iteritems()
                        if (val != '' and val is not None and key != 'id'
                            and not (key in ['state', 'employee']))
                ]):
                    row_index = int(row.name[-1])
                    # Delete from patient object
                    db.session.delete(getattr(patient, field_name)[row_index])
                    # Deletion from form FieldList requires popping all entries
                    # after the one to be removed, then readding them
                    to_re_add = []
                    for _ in range(len(form[field_name].entries) - row_index):
                        to_re_add.append(form[field_name].pop_entry())
                    to_re_add.pop()
                    for row in reversed(to_re_add):
                        form[field_name].append_entry(data=row.data)

    # Get binary data and create resized versions of any new document images
    for index, entry in enumerate(form.document_images):
        if entry.file_name.data and entry.file_name.data.filename:
            # This is a new file
            if entry.file_name.data.content_type == 'application/pdf':
                # PIL can't handle PDFs, so use Wand
                pdf = WandImage(file=entry.file_name.data.stream,
                                resolution=500)
                pdf.convert('jpg')
                entry.file_name.data.stream = io.BytesIO(pdf.make_blob('jpeg'))

            large_image = Image.open(entry.file_name.data.stream)
            small_image = large_image.copy()

            large_image_output, small_image_output = io.BytesIO(), io.BytesIO()
            large_image.thumbnail(
                current_app.config['LARGE_DOCUMENT_IMAGE_SIZE'],
                Image.ANTIALIAS)
            large_image.save(large_image_output, format='JPEG')
            small_image.thumbnail(
                current_app.config['SMALL_DOCUMENT_IMAGE_SIZE'],
                Image.ANTIALIAS)
            small_image.save(small_image_output, format='JPEG')

            entry.data_full.data = entry.file_name.data.stream.getvalue()
            entry.data_large.data = large_image_output.getvalue()
            entry.data_small.data = small_image_output.getvalue()
            entry.file_name.data = entry.file_name.data.filename
        else:
            # This is an existing entry, so the file can't change, only the description
            # Fill in the fields that aren't inputs from the saved data so
            # that populate_obj doesn't overwrite them.
            entry.file_name.data = patient.document_images[index].file_name
            entry.data_full.data = patient.document_images[index].data_full
            entry.data_large.data = patient.document_images[index].data_large
            entry.data_small.data = patient.document_images[index].data_small

    # Populate the patient object with all the updated info
    form.populate_obj(patient)
    return