def create_pptx_for_nanoindents(path, pptx_filename, pptx_template: Optional[AbstractTemplate] = None): pptx = PPTXCreator(template=pptx_template) pptx.add_title_slide(f"AFM on Nanoindents - {path.stem}") measurements = load_pygdf_measurements(path) for measurement in measurements: indent_analyzer = GDEFIndentAnalyzer(measurement) print(measurement.comment) slide = pptx.add_slide(measurement.comment) figure = measurement.create_plot() if figure is None: continue pptx.add_matplotlib_figure(figure, slide, position_2x2_00()) table_shape = pptx.add_table(slide, measurement.get_summary_table_data(), position_2x2_01(), table_style=summary_table()) minimize_table_height(table_shape) # figure.savefig(f"{measurement.basename.with_suffix('.png')}") # , transparent=transparent) indent_analyzer.add_indent_pile_up_mask_to_axes(figure.axes[0], roughness_part=0.05) # figure.savefig(f"{measurement.basename.with_name(measurement.basename.stem + '_masked.png')}", dpi=96) pptx.add_matplotlib_figure(figure, slide, position_2x2_10()) table_shape = pptx.add_table(slide, indent_analyzer.get_summary_table_data(), position_2x2_11(), table_style=summary_table()) minimize_table_height(table_shape) figure.clear() pptx.save(path.joinpath(f"{pptx_filename}.pptx"), overwrite=True) # todo: remove overwrite=True when testing is finished
def run(save_dir: str): pp = PPTXCreator(TemplateExample()) slide_01 = pp.add_slide("Table style example 01 - slide 01") slide_02 = pp.add_slide("Table style example 01 - slide 02") slide_03 = pp.add_slide("Table style example 01 - slide 03") slide_04 = pp.add_slide("Table style example 01 - slide 04") slide_05 = pp.add_slide("Table style example 01 - slide 05") slide_06 = pp.add_slide("Table style example 01 - slide 06") # data for a table with 5 rows and 3 cols. table_data = [] table_data.append([1, "The second column is longer."]) # rows can have different length table_data.append([2, "Table entries don't have to be strings,"]) # there is specific type needed for entries (implemented as text=f"{entry}") table_data.append([3, "because its implemented as text=f'{entry}'"]) table_data.append([4, "also note: the number of entries per row is", " not fixed"]) table_data.append([5, "That's it for now."]) # We can add these data as a table title_slide with PPTXCreator.add_table()... table_01 = pp.add_table(slide_01, table_data) # ... but if you open the slide in PowerPoint there are a few issues: # 1) the table is positioned in the top left corner - overlapping the title # 2) the first row is formated differently (like a column header) # 3) all columns have the same width (1 inch) # 4) the table width is too small (for the used paragraph size) # Lets handle the the position first using optional PPTXPosition parameter table_02 = pp.add_table(slide_02, table_data, PPTXPosition(0.02, 0.14)) # for more control we use PPTXTableStyle table_style = PPTXTableStyle() table_style.first_row_header = False table_style.width = 6.1 # table width in inches table_style.col_ratios = [0.3, 5, 1.3] table_03 = pp.add_table(slide_03, table_data, PPTXPosition(0.02, 0.14), table_style) # It's also possible to add the position directly to the table style: table_style.position = PPTXPosition(0.02, 0.14) # or to set the table width as a fraction of slide width: table_style.set_width_as_fraction(0.49) # change row/col bending table_style.col_banding = True table_style.row_banding = False table_04 = pp.add_table(slide_04, table_data, table_style=table_style) # we could also add a paragraph-style and a cell-style table_style.font_style = PPTXFontStyle().set(italic=True, name="Arial", color_rgb=(100, 200, 30)) # todo: cell-style table_05 = pp.add_table(slide_05, table_data, table_style=table_style) # you could also use a table style on an existing table table_06 = pp.add_table(slide_06, table_data) table_style.write_shape(table_06) pp.save(os.path.join(save_dir, "table_style_example_01.pptx"), overwrite=True)
def run(save_dir: str): pp = PPTXCreator(TemplateExample()) PPTXFontStyle.lanaguage_id = MSO_LANGUAGE_ID.ENGLISH_UK PPTXFontStyle.name = "Roboto" title_slide = pp.add_title_slide("General example 01 - title slide") font = font_title( ) # returns a PPTXFontStyle instance with bold paragraph and size = 32 Pt font.write_shape( title_slide.shapes.title ) # change paragraph attributes for all paragraphs in shape slide2 = pp.add_slide("General example 01 - page2") pp.add_slide("General example 01 - page3") pp.add_slide("General example 01 - page4") pp.add_content_slide() # add slide with hyperlinks to all other slides text = "This text has three paragraphs. This is the first.\n" \ "Das ist der zweite ...\n" \ "... and the third." my_font = font_default() my_font.size = 16 text_shape = pp.add_text_box(title_slide, text, PPTXPosition(0.02, 0.24), my_font) my_font.set(size=22, bold=True, language_id=MSO_LANGUAGE_ID.GERMAN, strikethrough=TEXT_STRIKE_VALUES.SingleStrike, caps=TEXT_CAPS_VALUES.All) my_font.write_paragraph(text_shape.text_frame.paragraphs[1]) my_font.set(size=18, bold=False, italic=True, name="Vivaldi", language_id=MSO_LANGUAGE_ID.ENGLISH_UK, underline=MSO_TEXT_UNDERLINE_TYPE.WAVY_DOUBLE_LINE, color_rgb=(255, 0, 0), strikethrough=None, caps=None) my_font.write_paragraph(text_shape.text_frame.paragraphs[2]) table_data = [] table_data.append([1, 2]) # rows can have different length table_data.append( [4, slide2, 6] ) # there is specific type needed for entries (implemented as text=f"{entry}") table_data.append(["", 8, 9]) table = pp.add_table(title_slide, table_data, PPTXPosition(0.02, 0.4)) paragraph_style = PPTXParagraphStyle() paragraph_style.set(alignment=PP_PARAGRAPH_ALIGNMENT.CENTER) paragraph_style.write_shape(table) if matplotlib_installed: fig = create_demo_figure() pp.add_matplotlib_figure(fig, title_slide, PPTXPosition(0.3, 0.4)) pp.add_matplotlib_figure(fig, title_slide, PPTXPosition(0.3, 0.4, fig.get_figwidth(), -1.0), zoom=0.4) pp.add_matplotlib_figure(fig, title_slide, PPTXPosition(0.3, 0.4, fig.get_figwidth(), 0.0), zoom=0.5) pp.add_matplotlib_figure(fig, title_slide, PPTXPosition(0.3, 0.4, fig.get_figwidth(), 1.5), zoom=0.6) pp.add_text_box(title_slide, "Use latex-like syntax \nto create formula:", PPTXPosition(0.748, 0.23)) pp.add_latex_formula(f"\mu={5}^{5}", title_slide, PPTXPosition(0.75, 0.35)) formula02 = "\\int_0^\\infty e^{-x^2} dx=\\frac{\\sqrt{\\pi}}{2}" pp.add_latex_formula(formula02, title_slide, PPTXPosition(0.75, 0.45)) pp.add_latex_formula(formula02, title_slide, PPTXPosition(0.75, 0.55), font_size=24, color="red") formula03 = "\\hat{x}, \\check{x}, \\tilde{a}, \\bar{\\ell}, \\dot{y}, \\ddot{y}, \\vec{z_1}, \\vec{z}_1" pp.add_latex_formula(formula03, title_slide, PPTXPosition(0.75, 0.65), font_size=24, color="blue") formula04 = r"\frac{3}{4} \binom{3}{4} \genfrac{}{}{0}{}{3}{4}" pp.add_latex_formula(formula04, title_slide, PPTXPosition(0.75, 0.75), font_size=44, color="g") pp.save(os.path.join(save_dir, "general_example_01.pptx")) try: # only on Windows with PowerPoint installed: filename_pptx = os.path.join(save_dir, "general_example_01.pptx") filename_pdf = os.path.join(save_dir, "general_example_01.pdf") foldername_png = os.path.join(save_dir, "general_example_01_pngs") # use absolute path, because its not clear where PowerPoint saves PDF/PNG ... otherwise pp.save(filename_pptx, create_pdf=True, overwrite=True) pp.save_as_pdf(filename_pdf, overwrite=True) pp.save_as_png(foldername_png, overwrite_folder=True) except Exception as e: print(e)
class GDEFReporter: """ Class to create *.pptx files with results from AFM-measurements (*.gdf) """ def __init__(self, gdf_containers: Union[GDEFContainer, List[GDEFContainer], None] = None): """ :param gdf_containers: list of GDEFContainer objects, that contain measurements from a single *.gdf file each """ self.gdf_containers: GDEFContainerList = GDEFContainerList( gdf_containers) self.primary_gdf_folder = gdf_containers[ 0].path.parent # todo: check for multiple folders self.title = f"AFM - {self.primary_gdf_folder.stem}" self.subtitle = self.title self.pptx = None self.title_slide = None def create_summary_pptx(self, filtered: bool = False, pptx_template=TemplateExample()): self.pptx = PPTXCreator(template=pptx_template) self.title_slide = self.pptx.add_title_slide(self.title) table_data = self.get_files_date_table_data() table_style = PPTXTableStyle() table_style.set_width_as_fraction(0.55) self.pptx.add_table(self.title_slide, table_data, PPTXPosition(0.0, 0.224, 0.1, 0.1), table_style) for container in self.gdf_containers: slide = self.pptx.add_slide(f"Overview - {container.basename}.gdf") if filtered: measurements = container.measurements else: measurements = container.filtered_measurements self.pptx.add_matplotlib_figure( self.create_summary_figure(measurements), slide, PPTXPosition(0, 0.115), zoom=0.62) table_style = summary_table() table_style.font_style.set(size=11) table_style.set_width_as_fraction(0.245) table_data = measurements[0].get_summary_table_data() table_data.append(["comment", measurements[0].comment]) table_shape = self.pptx.add_table(slide, table_data, PPTXPosition(0.75, 0.115), table_style) minimize_table_height(table_shape) return self.pptx def get_files_date_table_data(self): result = [["file", "date"]] for container in self.gdf_containers: result.append([ f"{container.path.stem}.gdf", container.last_modification_datetime ]) return result # todo: split up and move part to GDEF_Plotter or plotter_utils def create_summary_figure(self, measurements: List[GDEFMeasurement], figure_size=(16, 10)): n = len(measurements) if n == 0: return plt.subplots(1, figsize=figure_size, dpi=300) optimal_ratio = figure_size[0] / figure_size[1] dummy_fig = measurements[0].create_plot() single_plot_ratio = dummy_fig.get_figwidth() / dummy_fig.get_figheight( ) optimal_ratio /= single_plot_ratio possible_ratios = [] for i in range(1, n + 1): for j in range(1, n + 1): if i * j >= n: x, y = i, j possible_ratios.append((x, y)) break # sort ratios by best fit to optimal ratio: possible_ratios[:] = sorted( possible_ratios, key=lambda ratio: abs(ratio[0] / ratio[1] - optimal_ratio)) best_ratio = possible_ratios[0][1], possible_ratios[0][0] result, ax_list = plt.subplots(*best_ratio, figsize=figure_size, dpi=300) for i, measurement in enumerate(measurements): y = i // best_ratio[0] x = i - (y * best_ratio[0]) if best_ratio[1] > 1: measurement.set_topography_to_axes(ax_list[x, y], True) elif best_ratio[0] > 1: measurement.set_topography_to_axes(ax_list[x], True) else: measurement.set_topography_to_axes(ax_list, True) i = len(measurements) while i < best_ratio[0] * best_ratio[1]: y = i // best_ratio[0] x = i - (y * best_ratio[0]) ax_list[x, y].set_axis_off() i += 1 result.tight_layout() return result @classmethod def create_stiched_data(cls, measurements, initial_x_offset_fraction=0.35, show_control_plots=False): values_list = [] for measurement in measurements: values_list.append(measurement.values) return GDEFSticher(values_list, initial_x_offset_fraction, show_control_plots).values @classmethod def _create_image_data(cls, data: np.ndarray): """ Transform given data array into a n array with uint8 values between 0 and 255. :param data: :return: """ data_min = np.nanmin(data) data = (data - np.min([0, data_min])) / (np.nanmax(data) - np.min( [0, data_min])) # normalize the data to 0 - 1 data = 255 * data # Now scale by 255 return data.astype(np.uint8) @classmethod def data_to_png(cls, data: np.ndarray, mode='L'): """ Can be used to get a png-object for pptx or to save to hard disc. Mode 'L' means greyscale. Mode'LA' is greyscale with alpha channel. """ image_data = cls._create_image_data(data) return png.from_array(image_data, mode=mode) # .save(f"{samplename}_stiched.png")
class PI88ToPPTX: def __init__(self, measurements_path=None, template=None): self.path = measurements_path self.measurements = [] if self.path: self.load_tdm_files(measurements_path) self.plotter = PI88Plotter(self.measurements) self.pptx_creator = PPTXCreator(template=template) self.prs = self.pptx_creator.prs self.position = self.pptx_creator.default_position self.poisson_ratio = 0.3 self.beta = 1.0 self.measurements_unloading_data: dict = {} def load_tdm_files(self, path: str, sort_key=os.path.getctime ): # sorted by creation time (using windows) self.measurements.extend(load_tdm_files(path, sort_key)) def add_measurements( self, measurements: Union[PI88Measurement, Iterable[PI88Measurement]]) -> None: """ Adds a single PI88Measurement or a list o PI88Measurement's to the plotter. """ if measurements: try: self.measurements.extend(measurements) except TypeError: self.measurements.append(measurements) def clear_measurements(self): self.measurements = [] self.measurements_unloading_data = {} def add_matplotlib_figure(self, fig: Figure, slide: Slide, position: PPTXPosition = None, **kwargs): """ :param fig: :param slide_index: :param position: :param kwargs: e.g. width and height :return: prs.shapes.picture.Picture """ return self.pptx_creator.add_matplotlib_figure(fig, slide, position, **kwargs) def create_summary_slide(self, title: str = None, layout=None): if title is None: title = f"Summary - {self.path}" result = self.pptx_creator.add_slide(title, layout) plotter = PI88Plotter(self.measurements) fig = plotter.get_load_displacement_plot() fig.axes[0].legend(loc="best") self.add_matplotlib_figure(fig, result, PPTXPosition(0.02, 0.15)) self.create_measurements_result_data_table(result) return result def create_modulus_hardness_summary_slide(self, layout=None): title = "Summary - reduced modulus and hardness" result = self.pptx_creator.add_slide(title, layout) plotter = PI88Plotter(self.measurements) fig = plotter.get_reduced_modulus_plot() self.add_matplotlib_figure(fig, result, PPTXPosition(0.02, 0.15)) avg_reduced_modulus = statistics.mean(fig.axes[0].lines[0].get_ydata()) fig = plotter.get_hardness_plot() self.add_matplotlib_figure(fig, result, PPTXPosition(0.52, 0.15)) avg_hardness = statistics.mean(fig.axes[0].lines[0].get_ydata()) text = f"avg. Er = {avg_reduced_modulus} GPa - avg. H = {avg_hardness}" self.pptx_creator.add_text_box(result, text, PPTXPosition(0.05, 0.85)) return result def create_title_slide(self, title=None, layout=None, default_content=False): if title is None: title = f"NI results {self.path}" result = self.pptx_creator.add_title_slide(title, layout) self.create_measurements_meta_table(result) plotter = PI88Plotter(self.measurements) fig = plotter.get_load_displacement_plot() self.add_matplotlib_figure(fig, result, PPTXPosition(0.57, 0.24)) return result def create_measurement_slide(self, measurement: PI88Measurement, layout=None, graph_styler=None): title = measurement.base_name # filename[:-4].split("/")[-1].split("\\")[-1] result = self.pptx_creator.add_slide(title, layout) self.create_measurement_result_table(result, measurement) self.create_measurement_meta_data_table(result, measurement) plotter = PI88Plotter(measurement) if graph_styler is not None: plotter.graph_styler = graph_styler fig = plotter.get_load_displacement_plot() if (measurement, self.poisson_ratio, self.beta) in self.measurements_unloading_data: fit_data = self.measurements_unloading_data[(measurement, self.poisson_ratio, self.beta)] fit_disp, fit_load = get_power_law_fit_curve(**fit_data) fig.axes[0].plot(fit_disp, fit_load, **get_power_law_fit_curve_style().dict, label="power-law-fit") self.add_matplotlib_figure(fig, result, PPTXPosition(0.02, 0.15)) return result def create_measurement_slides(self, measurements: Optional[ List[PI88Measurement]] = None, layout=None) -> list: result = [] if measurements is None: measurements = self.measurements graph_styler = GraphStyler(len(self.measurements)) for measurement in measurements: result.append( self.create_measurement_slide(measurement, layout, graph_styler)) return result def create_measurement_meta_data_table( self, slide, measurement, table_style: PPTXTableStyle = None) -> Shape: table_data = get_measurement_meta_table_data(measurement) result = self.pptx_creator.add_table(slide, table_data, PPTXPosition(0.521, 0.16)) if table_style is None: table_style = style_sheets.table_no_header() table_style.set_width_as_fraction(0.4) table_style.write_shape(result) return result def _get_measurement_result_table_data(self, measurement: PI88Measurement, poisson_ratio: float, beta: float) -> list: if (measurement, poisson_ratio, beta) in self.measurements_unloading_data: data = self.measurements_unloading_data[(measurement, poisson_ratio, beta)] else: data = calc_unloading_data(measurement, beta=self.beta, poisson_ratio=self.poisson_ratio) self.measurements_unloading_data[(measurement, poisson_ratio, beta)] = data return get_measurement_result_table_data(measurement, data) def create_measurement_result_table( self, slide, measurement, table_style: PPTXTableStyle = None) -> Shape: table_data = self._get_measurement_result_table_data( measurement, self.poisson_ratio, self.beta) result = self.pptx_creator.add_table(slide, table_data, PPTXPosition(0.521, 0.56)) if table_style is None: table_style = style_sheets.table_no_header() table_style.set_width_as_fraction(0.4) table_style.write_shape(result) return result def create_measurements_meta_table(self, slide, table_style: PPTXTableStyle = None): table_data = get_measurements_meta_table_data(self.measurements) result = self.pptx_creator.add_table(slide, table_data) if table_style is None: table_style = table_style_measurements_meta() table_style.write_shape(result) return result def create_measurements_result_data_table( self, slide, table_style: PPTXTableStyle = None): for measurement in self.measurements: if measurement not in self.measurements_unloading_data.keys(): self._get_measurement_result_table_data( measurement, self.poisson_ratio, self.beta) # todo: better method (name change?) table_data = get_measurements_result_table_data( self.measurements_unloading_data.values()) result = self.pptx_creator.add_table(slide, table_data) if table_style is None: table_style = table_style_summary() table_style.write_shape(result) return result def get_average_measurements_unloading_data(self): sum_Er = sum_E = sum_hardness = 0 for data in self.measurements_unloading_data.values(): sum_Er += data['Er'] sum_E += data['E'] sum_hardness += data['hardness'] n = len(self.measurements_unloading_data.values()) if n > 0: return sum_Er / n, sum_E / n, sum_hardness / n else: return 0, 0, 0 def save(self, filename="delme.prs"): self.prs.save(filename)