Skip to content

Commit a466db1

Browse files
committed
chore(image_processing): update module readme
1 parent 613acb8 commit a466db1

1 file changed

Lines changed: 111 additions & 110 deletions

File tree

apps/image_processing/core/README.md

Lines changed: 111 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22

33
This app is responsible for handling image processing and transformations.
44

5-
## Core Concepts
5+
## Core concepts
66

77
- **Image Transformations**: Provides capabilities to transform images in various ways and provide different processing strategies.
88
- **Transformation Management**: Provides a high-level interface for general image operations.
99
- **Transformers**: Define different strategies for applying transformations, such as in parallel or one by one.
1010
- **Detectors**: Detect objects in images using computer vision models.
1111

12-
## Key Components
12+
## Key components
1313

1414
- **`transformations.py`**: Applies a transformation to an image using the Pillow (PIL) library and returns a transformed PIL image copy.
1515
- **`transformers.py`**: Defines different strategies for applying transformations.
@@ -18,88 +18,87 @@ This app is responsible for handling image processing and transformations.
1818
- `ImageChainTransformer`: Applies transformations sequentially, where the output of one transformation becomes the input for the next.
1919
- **`managers.py`**: Manages the overall image processing workflow.
2020
- `ImageLocalManager`: Manages transformations for images stored locally on the filesystem. It opens the image, applies transformations using a specified transformer, and saves the resulting images to a structured directory.
21-
- **`data_models.py` (within `src/`)**: Defines internal data structures used during the transformation process, such as `InternalImageTransformationFilters` which are direct mappings to PIL's internal representations.
2221

23-
## How it Works (Example Flow from `apps.places.tasks.py`)
22+
## How is expected to be used:
2423

25-
The `transform_uploaded_images` task in `apps.places.tasks.py` demonstrates how this `image_processing` app is used:
26-
27-
1. A list of `ImageTransformationDefinition` objects is defined, each specifying an `identifier`, a `transformation` type (e.g., `ImageTransformations.THUMBNAIL`), and optional `filters`.
24+
1. Define a list of transformation objects is defined, each specifying an `identifier`, a `transformation` name type (e.g., `thumbnail`), and optional `filters`.
2825
``` python
29-
from apps.image_processing.api.constants import (
30-
ImageTransformations,
31-
TransformationFilterBlurFilter,
32-
TransformationFilterDither,
33-
TransformationFilterThumbnailResampling,
34-
)
35-
from apps.image_processing.api.data_models import (
36-
ImageTransformationDefinition,
37-
TransformationFiltersBlackAndWhite,
38-
TransformationFiltersBlur,
39-
TransformationFiltersThumbnail,
40-
)
41-
42-
43-
transformations = [
44-
# Thumbnail
45-
ImageTransformationDefinition(
46-
identifier="THUMBNAIL/s_320_gap_8_lanczos",
47-
transformation=ImageTransformations.THUMBNAIL,
48-
filters=TransformationFiltersThumbnail(
49-
size=(320, 320),
50-
reducing_gap=8,
51-
resample=TransformationFilterThumbnailResampling.LANCZOS,
52-
),
53-
),
54-
# Black and White
55-
ImageTransformationDefinition(
56-
identifier="BNW/none",
57-
transformation=ImageTransformations.BLACK_AND_WHITE,
58-
filters=TransformationFiltersBlackAndWhite(
59-
dither=TransformationFilterDither.NONE
60-
),
61-
),
62-
# Blur
63-
ImageTransformationDefinition(
64-
identifier="BLUR/gaussian_86",
65-
transformation=ImageTransformations.BLUR,
66-
filters=TransformationFiltersBlur(
67-
filter=TransformationFilterBlurFilter.GAUSSIAN_BLUR,
68-
radius=86,
69-
),
70-
)
71-
]
26+
transformations = [
27+
{
28+
"identifier": "00",
29+
"transformation": "black_and_white"
30+
},
31+
{
32+
"identifier": "22",
33+
"transformation": "blur",
34+
"filters": {
35+
"filter": "box_blur",
36+
"radius": 10
37+
}
38+
},
39+
{
40+
"identifier": "11",
41+
"transformation": "thumbnail"
42+
}
43+
]
7244
```
7345

74-
2. Use the `image_local_transform` service with the `image_path`, the list of `transformations`, and a `parent_folder` name.
46+
2. Use the `image_processing_transform` function to apply transformations to an image, specifying the `user_id`, `image_path`, `transformations`, `parent_folder` (optional), and `chain` (optional).
7547
``` python
76-
from apps.image_processing.api.services.processing import image_local_transform
77-
78-
applied_transformations = image_local_transform(
79-
user_id=request.user.id,
80-
image_path="path/to/local/image.png",
48+
from apps.image_processing.models import (
49+
ProcessingImage,
50+
)
51+
from apps.image_processing_api.services import image_processing_transform
52+
...
53+
image = ProcessingImage.objects.create(...)
54+
applied_transformations = image_processing_transform(
55+
user=request.user.id,
56+
image_ids=[image.id],
8157
transformations=transformations,
82-
parent_folder="parent_folder",
83-
chain=True, # Defaults to False
58+
is_chain=True, # Defaults to False
8459
)
8560

8661
for transformation in applied_transformations:
87-
logger.info(f"{transformation.identifier}: {transformation.path}")
88-
62+
logger.info(f"{transformation['id']} - {transformation['task_id']} - {transformation['task_status']}")
63+
...
8964
```
9065

9166
## How It Works Inside:
9267

93-
### Transformations (`image_processing.src.transformations`):
68+
### Transformations (`image_processing.core.transformations`):
9469

9570
The core logic for applying custom image transformations. This includes the actual transformation logic using the Pillow (PIL) library. They take an image, apply a transformation based on the provided filters, and return a transformed PIL image copy.
9671

97-
Make your own transformation like this:
72+
Make your own transformation like this and create the corresponding filters interfaces:
9873
```python
99-
from apps.image_processing.src.data_models import InternalImageTransformation
10074
from PIL import Image as PImage
10175

76+
from .base import (
77+
ExternalTransformationFilters,
78+
InternalImageTransformation,
79+
InternalImageTransformationFilters,
80+
)
81+
82+
# Define custom filters
83+
@dataclass
84+
class InternalTransformationFiltersCrop(InternalImageTransformationFilters):
85+
x : ComplexObject()
86+
y : ComplexObject2()
87+
88+
89+
# Define external representation with native types and `to_internal` method
90+
@dataclass
91+
class ExternalTransformationFiltersCrop(InternalTransformationFiltersCrop):
92+
x : float
93+
y : float
94+
95+
def to_internal(self) -> InternalTransformationFiltersCrop:
96+
return InternalTransformationFiltersCrop(
97+
x = ComplexObject(...),
98+
y = ComplexObject2(...)
99+
)
102100

101+
# Define the transformation
103102
class TransformationCrop(InternalImageTransformation): # Inherit from InternalImageTransformation
104103
def _image_transform(
105104
self,
@@ -120,21 +119,11 @@ class TransformationCrop(InternalImageTransformation): # Inherit from InternalIm
120119

121120
```
122121

123-
## Filters (`image_processing.src.data_models`):
122+
### Transformation filters (`image_processing.core.transformations.base`):
124123

125-
Defines the internal representations of image transformations. Each subclass corresponds to a specific transformation type and its associated filters.
124+
Each transformation has its own set of filters, which are defined in the `ExternalTransformationFilters` interface. These filters are used to specify the parameters for the transformation.
126125

127-
Make your own filters like this:
128-
```python
129-
from apps.image_processing.src.data_models import InternalImageTransformationFilters
130-
131-
@dataclass
132-
class InternalTransformationFiltersCrop(InternalImageTransformationFilters): # Inherit from InternalImageTransformationFilters
133-
x_left: float
134-
y_top: float
135-
x_right: float
136-
y_bottom: float
137-
```
126+
The `ExternalTransformationFilters` implementations must be able to be converted to the corresponding `InternalImageTransformationFilters` implementations using the `to_internal` method.
138127

139128
### Transformers (`image_processing.src.transformers`):
140129

@@ -143,17 +132,23 @@ Defines different strategies for applying transformations, they take a list of
143132

144133
Make your own transformers like this:
145134
```python
146-
from apps.image_processing.src.data_models import InternalImageTransformationResult
147-
from apps.image_processing.src.transformers import BaseImageTransformer
148-
149135
from PIL import Image as PImage
150136

137+
from apps.image_processing.core.transformers.base import (
138+
InternalImageTransformationResult,
139+
)
140+
from apps.image_processing.models import TransformationBatch
141+
142+
from .base import BaseImageTransformer
143+
151144

152-
# Apply transformations one by one and returna list of
153-
# the transformed images with their corresponding identifiers
154-
class ImageSequentialTransformer(BaseImageTransformer): # Inherit from BaseImageTransformer
155-
# Must implement the `transform` method
156-
def transform(self, image: PImage.Image) -> list[InternalImageTransformationResult]:
145+
class ImageSequentialTransformer(BaseImageTransformer): # Inherit from BaseImageTransformer
146+
name = TransformationBatch.SEQUENTIAL # Give it a name
147+
148+
# Must implement the `_transform` method
149+
def _transform(
150+
self, image: PImage.Image
151+
) -> list[InternalImageTransformationResult]:
157152
transformations = []
158153
for transform_data in self.transformations_data:
159154
transformation = transform_data.transformation( # InternalImageTransformation instance
@@ -163,7 +158,9 @@ class ImageSequentialTransformer(BaseImageTransformer): # Inherit from BaseImag
163158
transformations.append(
164159
InternalImageTransformationResult(
165160
identifier=transform_data.identifier, # Return the same identifier
166-
image=transformation.image_transformed, # Return the transformed image
161+
transformation_name=transform_data.transformation.name,
162+
applied_filters=transform_data.filters,
163+
image=transformation.image_transformed, # Transformed image
167164
)
168165
)
169166
return transformations # Must return list[InternalImageTransformationResult]
@@ -187,21 +184,6 @@ from apps.image_processing.src.data_models import InternalTransformationManagerS
187184

188185

189186
class ImageS3Manager(BaseImageManager): # Inherit from BaseImageManager
190-
@property
191-
def _s3_bucket(self):
192-
resource_s3 = boto3.resource('s3', region_name='us-outwest-1')
193-
the_bucket = resource_s3.Bucket("the_bucket")
194-
return the_bucket
195-
196-
def _load_and_save(self, image: PImage.Image, identifier: str):
197-
image_buffer = BytesIO()
198-
image.save(image_buffer, format='png')
199-
image_buffer.seek(0)
200-
image_uploaded = self._s3_bucket.Object.put(
201-
body=image_buffer, Key=identifier
202-
)
203-
return image_uploaded.url
204-
205187
# Must implement the `_get_image` method
206188
def _get_image(self) -> PImage.Image:
207189
# Handle your custom manager image loading logic
@@ -211,8 +193,14 @@ class ImageS3Manager(BaseImageManager): # Inherit from BaseImageManager
211193
file_stream = response['Body']
212194
return PImage.open(file_stream)
213195

214-
# Must implement the `save` method to save the transformed images
215-
# returned by the self.transformer
196+
# Can add extra custom properties
197+
@property
198+
def _s3_bucket(self):
199+
resource_s3 = boto3.resource('s3', region_name='us-outwest-1')
200+
the_bucket = resource_s3.Bucket("the_bucket")
201+
return the_bucket
202+
203+
# Can define extra custom methods
216204
def save(self, parent_folder: str) -> list[InternalTransformationManagerSaveResult]:
217205
# Handle your custom manager image transformations saving logic
218206
# and return list[InternalTransformationManagerSaveResult]
@@ -226,32 +214,45 @@ class ImageS3Manager(BaseImageManager): # Inherit from BaseImageManager
226214
)
227215
)
228216
return saved_images
217+
218+
# Another custom method
219+
def _load_and_save(self, image: PImage.Image, identifier: str):
220+
image_buffer = BytesIO()
221+
image.save(image_buffer, format='png')
222+
image_buffer.seek(0)
223+
image_uploaded = self._s3_bucket.Object.put(
224+
body=image_buffer, Key=identifier
225+
)
226+
return image_uploaded.url
229227
```
230228

231229
### Mix all together:
232230

233231
```python
232+
from apps.image_processing.core.transformers.base import (
233+
InternalImageTransformationDefinition
234+
)
235+
234236
...
237+
image = ProcessingImage.objects.get(id=image_id, user_id=user_id)
235238
transformations= [
236239
InternalImageTransformationDefinition(
237240
identifier="CROP/default",
238241
transformation=TransformationCrop,
239242
filters=InternalTransformationFiltersCrop(
240-
x_left=80.4,
241-
y_top=20,
242-
x_right=2,
243-
y_bottom=0
243+
x=80.4,
244+
y=20,
244245
)
245246
),
246247
...
247248
]
248249
image_transformer = ImageSequentialTransformer(transformations)
249-
image_manager = ImageS3Manager(
250-
image_path="s3/image/key",
250+
s3_manager = ImageS3Manager(
251+
image=image,
251252
transformer=image_transformer,
252253
)
253-
image_manager.apply_transformations()
254-
image_manager.save(parent_folder="output")
254+
s3_manager.apply_transformations()
255+
s3_manager.save(parent_folder="output")
255256
...
256257

257258
```

0 commit comments

Comments
 (0)