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()
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()
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()
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)
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())
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/", "")
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)
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
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