FlowCyPy 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. FlowCyPy/__init__.py +15 -0
  2. FlowCyPy/_version.py +16 -0
  3. FlowCyPy/classifier.py +196 -0
  4. FlowCyPy/coupling_mechanism/__init__.py +4 -0
  5. FlowCyPy/coupling_mechanism/empirical.py +47 -0
  6. FlowCyPy/coupling_mechanism/mie.py +205 -0
  7. FlowCyPy/coupling_mechanism/rayleigh.py +115 -0
  8. FlowCyPy/coupling_mechanism/uniform.py +39 -0
  9. FlowCyPy/cytometer.py +198 -0
  10. FlowCyPy/detector.py +616 -0
  11. FlowCyPy/directories.py +36 -0
  12. FlowCyPy/distribution/__init__.py +16 -0
  13. FlowCyPy/distribution/base_class.py +59 -0
  14. FlowCyPy/distribution/delta.py +86 -0
  15. FlowCyPy/distribution/lognormal.py +94 -0
  16. FlowCyPy/distribution/normal.py +95 -0
  17. FlowCyPy/distribution/particle_size_distribution.py +110 -0
  18. FlowCyPy/distribution/uniform.py +96 -0
  19. FlowCyPy/distribution/weibull.py +80 -0
  20. FlowCyPy/event_correlator.py +244 -0
  21. FlowCyPy/flow_cell.py +122 -0
  22. FlowCyPy/helper.py +85 -0
  23. FlowCyPy/logger.py +322 -0
  24. FlowCyPy/noises.py +29 -0
  25. FlowCyPy/particle_count.py +102 -0
  26. FlowCyPy/peak_locator/__init__.py +4 -0
  27. FlowCyPy/peak_locator/base_class.py +163 -0
  28. FlowCyPy/peak_locator/basic.py +108 -0
  29. FlowCyPy/peak_locator/derivative.py +143 -0
  30. FlowCyPy/peak_locator/moving_average.py +114 -0
  31. FlowCyPy/physical_constant.py +19 -0
  32. FlowCyPy/plottings.py +270 -0
  33. FlowCyPy/population.py +239 -0
  34. FlowCyPy/populations_instances.py +49 -0
  35. FlowCyPy/report.py +236 -0
  36. FlowCyPy/scatterer.py +373 -0
  37. FlowCyPy/source.py +249 -0
  38. FlowCyPy/units.py +26 -0
  39. FlowCyPy/utils.py +191 -0
  40. FlowCyPy-0.5.0.dist-info/LICENSE +21 -0
  41. FlowCyPy-0.5.0.dist-info/METADATA +252 -0
  42. FlowCyPy-0.5.0.dist-info/RECORD +44 -0
  43. FlowCyPy-0.5.0.dist-info/WHEEL +5 -0
  44. FlowCyPy-0.5.0.dist-info/top_level.txt +1 -0
FlowCyPy/population.py ADDED
@@ -0,0 +1,239 @@
1
+
2
+ import numpy as np
3
+ from typing import Union
4
+ from FlowCyPy import distribution
5
+ import pandas as pd
6
+ from dataclasses import field
7
+ import pint_pandas
8
+ from pydantic.dataclasses import dataclass
9
+ from pydantic import field_validator
10
+ from FlowCyPy.units import particle
11
+ from FlowCyPy.flow_cell import FlowCell
12
+ from FlowCyPy.utils import PropertiesReport
13
+ import logging
14
+ from PyMieSim.units import Quantity, RIU, meter
15
+ import warnings
16
+ from FlowCyPy.particle_count import ParticleCount
17
+
18
+
19
+ config_dict = dict(
20
+ arbitrary_types_allowed=True,
21
+ kw_only=True,
22
+ slots=True,
23
+ extra='forbid'
24
+ )
25
+
26
+
27
+ @dataclass(config=config_dict)
28
+ class Population(PropertiesReport):
29
+ """
30
+ A class representing a population of scatterers in a flow cytometry setup.
31
+
32
+ Parameters
33
+ ----------
34
+ name : str
35
+ Name of the population distribution.
36
+ refractive_index : Union[distribution.Base, Quantity]
37
+ Refractive index or refractive index distributions.
38
+ size : Union[distribution.Base, Quantity]
39
+ Particle size or size distributions.
40
+ particle_count : ParticleCount
41
+ Scatterer density in particles per cubic meter, default is 1 particle/m³.
42
+
43
+ """
44
+ name: str
45
+ refractive_index: Union[distribution.Base, Quantity]
46
+ size: Union[distribution.Base, Quantity]
47
+ particle_count: ParticleCount = field(init=False)
48
+
49
+ def __post_init__(self):
50
+ """
51
+ Automatically converts all Quantity attributes to their base SI units (i.e., without any prefixes).
52
+ This strips units like millimeter to meter, kilogram to gram, etc.
53
+ """
54
+ # Convert all Quantity attributes to base SI units (without any prefixes)
55
+ for attr_name, attr_value in vars(self).items():
56
+ if isinstance(attr_value, Quantity):
57
+ # Convert the quantity to its base unit (strip prefix)
58
+ setattr(self, attr_name, attr_value.to_base_units())
59
+
60
+ @field_validator('concentration')
61
+ def _validate_concentration(cls, value):
62
+ """
63
+ Validates that the concentration is expressed in units of inverse volume.
64
+
65
+ Parameters
66
+ ----------
67
+ value : Quantity
68
+ The concentration to validate.
69
+
70
+ Returns
71
+ -------
72
+ Quantity
73
+ The validated concentration.
74
+
75
+ Raises
76
+ ------
77
+ ValueError: If the concentration is not expressed in units of inverse volume.
78
+ """
79
+ if not value.check('particles / [length]**3'):
80
+ raise ValueError(f"concentration must be in units of particles per volume (e.g., particles/m^3), but got {value.units}")
81
+ return value
82
+
83
+ @field_validator('refractive_index')
84
+ def _validate_refractive_index(cls, value):
85
+ """
86
+ Validates that the refractive index is either a Quantity or a valid distribution.Base instance.
87
+
88
+ Parameters
89
+ ----------
90
+ value : Union[distribution.Base, Quantity]
91
+ The refractive index to validate.
92
+
93
+ Returns
94
+ -------
95
+ Union[distribution.Base, Quantity]
96
+ The validated refractive index.
97
+
98
+ Raises
99
+ ------
100
+ TypeError
101
+ If the refractive index is not of type Quantity or distribution.Base.
102
+ """
103
+ if isinstance(value, Quantity):
104
+ assert value.check(RIU), "The refractive index value provided does not have refractive index units [RIU]"
105
+ return distribution.Delta(position=value)
106
+
107
+ if isinstance(value, distribution.Base):
108
+ return value
109
+
110
+ raise TypeError(f"refractive_index must be of type Quantity<RIU or refractive_index_units> or distribution.Base, but got {type(value)}")
111
+
112
+ @field_validator('size')
113
+ def _validate_size(cls, value):
114
+ """
115
+ Validates that the size is either a Quantity or a valid distribution.Base instance.
116
+
117
+ Parameters
118
+ ----------
119
+ value : Union[distribution.Base, Quantity]
120
+ The size to validate.
121
+
122
+ Returns
123
+ -------
124
+ Union[distribution.Base, Quantity]
125
+ The validated size.
126
+
127
+ Raises
128
+ ------
129
+ TypeError
130
+ If the size is not of type Quantity or distribution.Base.
131
+ """
132
+ if isinstance(value, Quantity):
133
+ assert value.check(meter), "The size value provided does not have length units [meter]"
134
+ return distribution.Delta(position=value)
135
+
136
+ if isinstance(value, distribution.Base):
137
+ return value
138
+
139
+ raise TypeError(f"suze must be of type Quantity or distribution.Base, but got {type(value)}")
140
+
141
+ def initialize(self, flow_cell: FlowCell) -> None:
142
+ self.dataframe = pd.DataFrame()
143
+
144
+ if isinstance(self.size, Quantity):
145
+ self.size = distribution.Delta(size_value=self.size)
146
+
147
+ self.flow_cell = flow_cell
148
+
149
+ self.n_events = self.particle_count.calculate_number_of_events(
150
+ flow_area=self.flow_cell.flow_area,
151
+ flow_speed=self.flow_cell.flow_speed,
152
+ run_time=self.flow_cell.run_time
153
+ )
154
+
155
+ self._generate_longitudinal_positions()
156
+
157
+ logging.info(f"Population [{self.name}] initialized with an estimated {self.n_events}.")
158
+
159
+ size = self.size.generate(self.n_events)
160
+ self.dataframe['Size'] = pint_pandas.PintArray(size, dtype=size.units)
161
+
162
+ ri = self.refractive_index.generate(self.n_events)
163
+ self.dataframe['RefractiveIndex'] = pint_pandas.PintArray(ri, dtype=ri.units)
164
+
165
+ def _generate_longitudinal_positions(self) -> None:
166
+ r"""
167
+ Generate particle arrival times over the entire experiment duration based on a Poisson process.
168
+
169
+ In flow cytometry, the particle arrival times can be modeled as a Poisson process, where the time
170
+ intervals between successive particle arrivals follow an exponential distribution. The average rate
171
+ of particle arrivals (the particle flux) is given by:
172
+
173
+ .. math::
174
+ \text{Particle Flux} = \rho \cdot v \cdot A
175
+
176
+ where:
177
+ - :math:`\rho` is the scatterer density (particles per cubic meter),
178
+ - :math:`v` is the flow speed (meters per second),
179
+ - :math:`A` is the cross-sectional area of the flow tube (square meters).
180
+
181
+ The number of particles arriving in a given time interval follows a Poisson distribution, and the
182
+ time between successive arrivals follows an exponential distribution. The mean inter-arrival time
183
+ is the inverse of the particle flux:
184
+
185
+ .. math::
186
+ \Delta t \sim \text{Exponential}(1/\lambda)
187
+
188
+ where:
189
+ - :math:`\Delta t` is the time between successive particle arrivals,
190
+ - :math:`\lambda` is the particle flux (particles per second).
191
+
192
+ Steps:
193
+ 1. Compute the particle flux, which is the average number of particles passing through the detection
194
+ region per second.
195
+ 2. Calculate the expected number of particles over the entire experiment duration.
196
+ 3. Generate random inter-arrival times using the exponential distribution.
197
+ 4. Compute the cumulative arrival times by summing the inter-arrival times.
198
+ 5. Ensure that all arrival times fall within the total experiment duration.
199
+
200
+ Returns
201
+ -------
202
+ np.ndarray
203
+ An array of particle arrival times (in seconds) for the entire experiment duration, based on the Poisson process.
204
+ """
205
+ # Step 1: Compute the average particle flux (particles per second)
206
+ particle_flux = self.particle_count.compute_particle_flux(
207
+ flow_speed=self.flow_cell.flow_speed,
208
+ flow_area=self.flow_cell.flow_area,
209
+ run_time=self.flow_cell.run_time
210
+ )
211
+
212
+ # Step 2: Calculate the expected number of particles over the entire experiment
213
+ expected_particles = self.n_events
214
+
215
+ # Step 3: Generate inter-arrival times (exponentially distributed)
216
+ inter_arrival_times = np.random.exponential(
217
+ scale=1 / particle_flux.magnitude,
218
+ size=int(expected_particles.magnitude)
219
+ ) / (particle_flux.units / particle)
220
+
221
+ # Step 4: Compute cumulative arrival times
222
+ arrival_times = np.cumsum(inter_arrival_times)
223
+
224
+ # Step 5: Limit the arrival times to the total experiment duration
225
+ arrival_times = arrival_times[arrival_times <= self.flow_cell.run_time]
226
+
227
+ time = arrival_times[arrival_times <= self.flow_cell.run_time]
228
+ self.dataframe['Time'] = pint_pandas.PintArray(time, dtype=time.units)
229
+
230
+ position = arrival_times * self.flow_cell.flow_speed
231
+
232
+ self.dataframe['Position'] = pint_pandas.PintArray(position, dtype=position.units)
233
+
234
+ self.n_events = len(arrival_times) * particle
235
+
236
+ if self.n_events == 0:
237
+ warnings.warn("Population has been initialized with 0 events.")
238
+
239
+ from FlowCyPy.populations_instances import * # noqa F403
@@ -0,0 +1,49 @@
1
+ from FlowCyPy.units import Quantity, nanometer, RIU, micrometer
2
+ from FlowCyPy.population import Population
3
+ from FlowCyPy import distribution
4
+
5
+ _populations = (
6
+ ('Exosome', 70 * nanometer, 2.0, 1.39 * RIU, 0.02 * RIU),
7
+ ('MicroVesicle', 400 * nanometer, 1.5, 1.39 * RIU, 0.02 * RIU),
8
+ ('ApoptoticBodies', 2 * micrometer, 1.2, 1.40 * RIU, 0.03 * RIU),
9
+ ('HDL', 10 * nanometer, 3.5, 1.33 * RIU, 0.01 * RIU),
10
+ ('LDL', 20 * nanometer, 3.0, 1.35 * RIU, 0.02 * RIU),
11
+ ('VLDL', 50 * nanometer, 2.0, 1.445 * RIU, 0.0005 * RIU),
12
+ ('Platelet', 2000 * nanometer, 2.5, 1.38 * RIU, 0.01 * RIU),
13
+ ('CellularDebris', 3 * micrometer, 1.0, 1.40 * RIU, 0.03 * RIU),
14
+ )
15
+
16
+
17
+ for (name, size, size_spread, ri, ri_spread) in _populations:
18
+ size_distribution = distribution.RosinRammler(
19
+ characteristic_size=size,
20
+ spread=size_spread
21
+ )
22
+
23
+ ri_distribution = distribution.Normal(
24
+ mean=ri,
25
+ std_dev=ri_spread
26
+ )
27
+
28
+ population = Population(
29
+ name=name,
30
+ size=size_distribution,
31
+ refractive_index=ri_distribution,
32
+ )
33
+
34
+ locals()[name] = population
35
+
36
+
37
+ def get_microbeads(size: Quantity, refractive_index: Quantity, name: str) -> Population:
38
+
39
+ size_distribution = distribution.Delta(position=size)
40
+
41
+ ri_distribution = distribution.Delta(position=refractive_index)
42
+
43
+ microbeads = Population(
44
+ name=name,
45
+ size=size_distribution,
46
+ refractive_index=ri_distribution
47
+ )
48
+
49
+ return microbeads
FlowCyPy/report.py ADDED
@@ -0,0 +1,236 @@
1
+ from dataclasses import dataclass, field
2
+ from reportlab.lib.pagesizes import A4
3
+ from reportlab.lib import colors
4
+ from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Frame, PageTemplate, Spacer, Paragraph, Image
5
+ from reportlab.lib.units import inch
6
+ from reportlab.lib.styles import getSampleStyleSheet
7
+ import matplotlib.pyplot as plt
8
+ from pathlib import Path
9
+ import shutil
10
+
11
+
12
+ def df2table(df):
13
+ paragraphs = [[Paragraph(col) for col in df.columns]] + df.values.tolist()
14
+ style = [
15
+ ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
16
+ ('LINEBELOW', (0, 0), (-1, 0), 1, colors.black),
17
+ ('INNERGRID', (0, 0), (-1, -1), 0.25, colors.black),
18
+ ('BOX', (0, 0), (-1, -1), 1, colors.black),
19
+ ('ROWBACKGROUNDS', (0, 0), (-1, -1), [colors.lightgrey, colors.white])
20
+ ]
21
+
22
+ return Table(paragraphs, hAlign='LEFT', style=style)
23
+
24
+
25
+ @dataclass
26
+ class Report:
27
+ flow_cell: object
28
+ scatterer: object
29
+ analyzer: object
30
+ output_filename: str = "flow_report.pdf"
31
+ title_color: object = field(default=colors.darkblue)
32
+ header_color: object = field(default=colors.darkgreen)
33
+ footer_color: object = field(default=colors.gray)
34
+ str_format: str = field(default='.2~P')
35
+
36
+ # Page size constants
37
+ width: float = field(default=A4[0], init=False)
38
+ height: float = field(default=A4[1], init=False)
39
+
40
+ temporary_folder = Path('./temporary_')
41
+
42
+ def add_background(self, canvas, doc):
43
+ """Adds a background color to every page."""
44
+ canvas.saveState()
45
+ canvas.setFillColorRGB(0.95, 0.95, 0.95) # Light grey background
46
+ canvas.rect(0, 0, A4[0], A4[1], fill=True, stroke=False) # Fill the entire page
47
+ canvas.restoreState()
48
+ self.add_watermark(canvas, doc)
49
+
50
+ def add_footer(self, canvas, doc):
51
+ """Adds a footer with page number to every page."""
52
+ canvas.saveState()
53
+ canvas.setFont("Helvetica-Oblique", 8)
54
+ canvas.setFillColor(self.footer_color)
55
+ canvas.drawString(1 * inch, 0.75 * inch, "Flow Cytometry Report")
56
+ canvas.drawRightString(A4[0] - inch, 0.75 * inch, f"Page {doc.page}")
57
+ canvas.restoreState()
58
+
59
+ def get_header(self):
60
+ """Returns a Paragraph for the report header."""
61
+ style = getSampleStyleSheet()['Title']
62
+ style.textColor = self.title_color
63
+ return Paragraph("Flow Cytometry Simulation Report", style)
64
+
65
+ def get_flow_cell_section(self):
66
+ """Returns a Paragraph or Table for the FlowCell section."""
67
+ style = getSampleStyleSheet()['Heading2']
68
+
69
+ table_data = [['Attribute', 'Value']]
70
+ table_data += self.flow_cell.get_properties()
71
+
72
+ table = Table(table_data, hAlign='LEFT')
73
+ table.setStyle(self.get_table_style())
74
+
75
+ return Paragraph("Section 1: FlowCell Parameters", style), table
76
+
77
+ def get_image_from(self, plot_function: object, figure_size, scale: float = 1, align: str = 'RIGHT') -> Image:
78
+ # Generate and save plot image for the scatterer distribution
79
+ plot_filename = self.temporary_folder / f"{id(plot_function)}.png"
80
+ plot_function(show=False, figure_size=figure_size)
81
+ plt.savefig(plot_filename)
82
+ plt.close()
83
+
84
+ # Create an image object to include in the PDF
85
+ image = Image(
86
+ filename=plot_filename,
87
+ hAlign=align,
88
+ height=figure_size[1] * scale * inch,
89
+ width=figure_size[0] * scale * inch
90
+ )
91
+
92
+ return image
93
+
94
+ def get_scatterer_section(self):
95
+ """Returns a Table for the scatterer distribution section."""
96
+ style = getSampleStyleSheet()['Heading2']
97
+
98
+ df = self.scatterer.populations[0].dataframe.pint.dequantify().reset_index().describe(percentiles=[])
99
+ df = df[['Time']]
100
+ print(df)
101
+
102
+ table = df2table(df)
103
+
104
+ # df = self.scatterer.dataframe#.reset_index()
105
+ # _report = df.pint.dequantify().describe(percentiles=[])
106
+ # print(_report)
107
+ # import pandas as pd
108
+ # report = pd.DataFrame()
109
+ # report['Attribute'] = _report['index']
110
+ # report['Index'] = _report['Index']
111
+ # report['Time'] = _report['Time']
112
+ # print(report)
113
+
114
+ # table = df2table(report)
115
+
116
+ table.setStyle(self.get_table_style())
117
+
118
+ self.get_image_from(self.scatterer.plot, figure_size=(8, 8), scale=0.5)
119
+
120
+ # combined_table = Table([[table, image]], hAlign='LEFT')
121
+ combined_table = Table([[table]], hAlign='LEFT')
122
+
123
+ combined_table.setStyle(TableStyle([
124
+ ('VALIGN', (0, 0), (-1, -1), 'TOP'), # Align both the table and image at the top
125
+ ]))
126
+
127
+ return Paragraph("Section 2: Scatterer populations", style), combined_table
128
+
129
+ def get_detector_section(self):
130
+ """Returns a Table for the detector properties."""
131
+ style = getSampleStyleSheet()['Heading2']
132
+ detector_0, detector_1 = self.analyzer.cytometer.detectors
133
+ properties_0 = detector_0.get_properties()
134
+ properties_1 = detector_1.get_properties()
135
+
136
+ # Create table data
137
+ table_data = [['Attribute', f'Detector {detector_0.name}', f'Detector {detector_1.name}']]
138
+ table_data += [[p0[0], p0[1], p1[1]] for p0, p1 in zip(properties_0, properties_1)]
139
+
140
+ # Create and return the table
141
+ table = Table(table_data, hAlign='LEFT')
142
+ table.setStyle(self.get_table_style())
143
+
144
+ image_0 = self.get_image_from(detector_0.plot, align='CENTER', figure_size=(6, 3))
145
+ image_1 = self.get_image_from(detector_1.plot, align='CENTER', figure_size=(6, 3))
146
+
147
+ return Paragraph("Section 3: Detectors parameters", style), table, image_0, image_1
148
+
149
+ def get_analyzer_section(self) -> None:
150
+ """Adds a section displaying analyzer properties."""
151
+ style = getSampleStyleSheet()['Heading2']
152
+
153
+ image = self.get_image_from(self.analyzer.plot_peak, align='CENTER', figure_size=(7, 6), scale=1)
154
+
155
+ return Paragraph("Section 4: Peak Analyzer parameters", style), image
156
+
157
+ def get_table_style(self) -> TableStyle:
158
+ """Returns a consistent table style."""
159
+ return TableStyle([
160
+ ('BACKGROUND', (0, 0), (-1, 0), colors.grey),
161
+ ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
162
+ ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
163
+ ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
164
+ ('FONTSIZE', (0, 0), (-1, -1), 10),
165
+ ('BOTTOMPADDING', (0, 0), (-1, 0), 12),
166
+ ('BACKGROUND', (0, 1), (-1, -1), colors.beige),
167
+ ('GRID', (0, 0), (-1, -1), 1, colors.black),
168
+ ])
169
+
170
+ def add_watermark(self, canvas, doc):
171
+ """Adds a semi-transparent watermark to the current page."""
172
+ canvas.saveState()
173
+ canvas.setFont("Helvetica-Bold", 50)
174
+ canvas.setFillColorRGB(0.9, 0.9, 0.9) # Light grey color for watermark
175
+ canvas.setFillAlpha(0.3) # Transparency level (0.0 fully transparent, 1.0 fully opaque)
176
+
177
+ # Position the watermark in the center of the page
178
+ width, height = A4
179
+ canvas.translate(width / 2, height / 2)
180
+ canvas.rotate(45)
181
+ canvas.drawCentredString(0, 0, "FLOWCYPY REPORT") # Watermark text
182
+
183
+ canvas.restoreState()
184
+
185
+ def generate_report(self):
186
+ """Generates the PDF report, applying a background to every page."""
187
+ # Use SimpleDocTemplate from Platypus
188
+
189
+ if self.temporary_folder.exists():
190
+ shutil.rmtree(self.temporary_folder)
191
+
192
+ self.temporary_folder.mkdir()
193
+
194
+ doc = SimpleDocTemplate(
195
+ self.output_filename,
196
+ pagesize=A4,
197
+ showBoundary=1,
198
+ leftMargin=0.5 * inch,
199
+ rightMargin=0.5 * inch,
200
+ topMargin=0.5 * inch,
201
+ bottomMargin=1.5 * inch
202
+ )
203
+
204
+ spacer = Spacer(1, 0.2 * inch)
205
+
206
+ story = [
207
+ self.get_header(), spacer,
208
+ *self.get_flow_cell_section(), spacer,
209
+ *self.get_scatterer_section(), spacer,
210
+ *self.get_detector_section(), spacer,
211
+ *self.get_analyzer_section()
212
+ ]
213
+
214
+ # Create a page template with the background and header/footer functions
215
+ frame = Frame(
216
+ doc.leftMargin,
217
+ doc.bottomMargin,
218
+ doc.width,
219
+ doc.height,
220
+ id='normal'
221
+ )
222
+
223
+ template = PageTemplate(
224
+ id='background',
225
+ frames=frame,
226
+ onPage=self.add_background,
227
+ onPageEnd=self.add_footer,
228
+ )
229
+
230
+ doc.addPageTemplates([template])
231
+
232
+ # Build the PDF
233
+ doc.build(story)
234
+ print(f"PDF report saved as {self.output_filename}")
235
+
236
+ shutil.rmtree(self.temporary_folder)