imagebaker 0.0.1__tar.gz → 0.0.2__tar.gz

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 (45) hide show
  1. {imagebaker-0.0.1 → imagebaker-0.0.2}/PKG-INFO +1 -1
  2. imagebaker-0.0.2/imagebaker/core/configs/__init__.py +1 -0
  3. imagebaker-0.0.2/imagebaker/core/configs/configs.py +149 -0
  4. imagebaker-0.0.2/imagebaker/core/defs/__init__.py +1 -0
  5. imagebaker-0.0.2/imagebaker/core/defs/defs.py +239 -0
  6. imagebaker-0.0.2/imagebaker/core/plugins/__init__.py +0 -0
  7. imagebaker-0.0.2/imagebaker/core/plugins/base_plugin.py +39 -0
  8. imagebaker-0.0.2/imagebaker/core/plugins/cosine_plugin.py +39 -0
  9. imagebaker-0.0.2/imagebaker/models/__init__.py +0 -0
  10. {imagebaker-0.0.1 → imagebaker-0.0.2}/imagebaker.egg-info/PKG-INFO +1 -1
  11. {imagebaker-0.0.1 → imagebaker-0.0.2}/imagebaker.egg-info/SOURCES.txt +8 -0
  12. {imagebaker-0.0.1 → imagebaker-0.0.2}/setup.py +1 -1
  13. {imagebaker-0.0.1 → imagebaker-0.0.2}/LICENSE +0 -0
  14. {imagebaker-0.0.1 → imagebaker-0.0.2}/README.md +0 -0
  15. {imagebaker-0.0.1 → imagebaker-0.0.2}/imagebaker/__init__.py +0 -0
  16. {imagebaker-0.0.1/imagebaker/models → imagebaker-0.0.2/imagebaker/core}/__init__.py +0 -0
  17. {imagebaker-0.0.1 → imagebaker-0.0.2}/imagebaker/layers/__init__.py +0 -0
  18. {imagebaker-0.0.1 → imagebaker-0.0.2}/imagebaker/layers/annotable_layer.py +0 -0
  19. {imagebaker-0.0.1 → imagebaker-0.0.2}/imagebaker/layers/base_layer.py +0 -0
  20. {imagebaker-0.0.1 → imagebaker-0.0.2}/imagebaker/layers/canvas_layer.py +0 -0
  21. {imagebaker-0.0.1 → imagebaker-0.0.2}/imagebaker/list_views/__init__.py +0 -0
  22. {imagebaker-0.0.1 → imagebaker-0.0.2}/imagebaker/list_views/annotation_list.py +0 -0
  23. {imagebaker-0.0.1 → imagebaker-0.0.2}/imagebaker/list_views/canvas_list.py +0 -0
  24. {imagebaker-0.0.1 → imagebaker-0.0.2}/imagebaker/list_views/image_list.py +0 -0
  25. {imagebaker-0.0.1 → imagebaker-0.0.2}/imagebaker/list_views/layer_list.py +0 -0
  26. {imagebaker-0.0.1 → imagebaker-0.0.2}/imagebaker/list_views/layer_settings.py +0 -0
  27. {imagebaker-0.0.1 → imagebaker-0.0.2}/imagebaker/models/base_model.py +0 -0
  28. {imagebaker-0.0.1 → imagebaker-0.0.2}/imagebaker/models/rtdetr_v2.py +0 -0
  29. {imagebaker-0.0.1 → imagebaker-0.0.2}/imagebaker/models/sam_model.py +0 -0
  30. {imagebaker-0.0.1 → imagebaker-0.0.2}/imagebaker/models/segmentation.py +0 -0
  31. {imagebaker-0.0.1 → imagebaker-0.0.2}/imagebaker/tabs/__init__.py +0 -0
  32. {imagebaker-0.0.1 → imagebaker-0.0.2}/imagebaker/tabs/baker_tab.py +0 -0
  33. {imagebaker-0.0.1 → imagebaker-0.0.2}/imagebaker/tabs/layerify_tab.py +0 -0
  34. {imagebaker-0.0.1 → imagebaker-0.0.2}/imagebaker/window/__init__.py +0 -0
  35. {imagebaker-0.0.1 → imagebaker-0.0.2}/imagebaker/window/app.py +0 -0
  36. {imagebaker-0.0.1 → imagebaker-0.0.2}/imagebaker/window/main_window.py +0 -0
  37. {imagebaker-0.0.1 → imagebaker-0.0.2}/imagebaker/workers/__init__.py +0 -0
  38. {imagebaker-0.0.1 → imagebaker-0.0.2}/imagebaker/workers/baker_worker.py +0 -0
  39. {imagebaker-0.0.1 → imagebaker-0.0.2}/imagebaker/workers/layerfy_worker.py +0 -0
  40. {imagebaker-0.0.1 → imagebaker-0.0.2}/imagebaker/workers/model_worker.py +0 -0
  41. {imagebaker-0.0.1 → imagebaker-0.0.2}/imagebaker.egg-info/dependency_links.txt +0 -0
  42. {imagebaker-0.0.1 → imagebaker-0.0.2}/imagebaker.egg-info/entry_points.txt +0 -0
  43. {imagebaker-0.0.1 → imagebaker-0.0.2}/imagebaker.egg-info/requires.txt +0 -0
  44. {imagebaker-0.0.1 → imagebaker-0.0.2}/imagebaker.egg-info/top_level.txt +0 -0
  45. {imagebaker-0.0.1 → imagebaker-0.0.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: imagebaker
3
- Version: 0.0.1
3
+ Version: 0.0.2
4
4
  Summary: A package for baking images.
5
5
  Home-page: https://github.com/q-viper/Image-Baker
6
6
  Author: Ramkrishna Acharya
@@ -0,0 +1 @@
1
+ from .configs import * # noqa
@@ -0,0 +1,149 @@
1
+ from pathlib import Path
2
+ from typing import List, Tuple
3
+
4
+ from PySide6.QtCore import Qt
5
+ from PySide6.QtGui import QColor
6
+ from pydantic import BaseModel, Field
7
+
8
+ from imagebaker.core.defs import Label, ModelType
9
+
10
+
11
+ class DrawConfig(BaseModel):
12
+ color: QColor = Field(default_factory=lambda: QColor(255, 255, 255))
13
+ point_size: int = 5
14
+ line_width: int = 5
15
+ control_point_size: int = 1.5
16
+ ellipse_size: int = 8
17
+ pen_alpha: int = 150
18
+ brush_alpha: int = 50
19
+ brush_fill_pattern: Qt.BrushStyle = Qt.BrushStyle.DiagCrossPattern
20
+ thumbnail_size: Tuple[int, int] = Field(default_factory=lambda: (50, 50))
21
+ background_color: QColor = Field(default_factory=lambda: QColor(0, 0, 0, 255))
22
+ label_font_size: int = 12
23
+ label_font_background_color: QColor = Field(
24
+ default_factory=lambda: QColor(0, 0, 0, 150)
25
+ )
26
+ handle_color: QColor = Field(default_factory=lambda: QColor(0, 255, 255, 150))
27
+ handle_width: int = 2
28
+ handle_point_size: int = 6
29
+ handle_edge_size: int = 4
30
+
31
+ button_width: int = 30
32
+
33
+ class Config:
34
+ arbitrary_types_allowed = True
35
+
36
+
37
+ class BaseConfig(BaseModel):
38
+ project_name: str = "ImageBaker"
39
+ version: str = "0.1.0"
40
+ project_dir: Path = Path(".")
41
+
42
+ is_debug: bool = True
43
+ deque_maxlen: int = 10
44
+
45
+ # drawing configs #
46
+ # ON SELECTION
47
+ selected_draw_config: DrawConfig = DrawConfig(
48
+ color=QColor(255, 0, 0),
49
+ point_size=5,
50
+ line_width=5,
51
+ ellipse_size=8,
52
+ pen_alpha=150,
53
+ brush_alpha=50,
54
+ thumbnail_size=(50, 50),
55
+ brush_fill_pattern=Qt.BrushStyle.CrossPattern,
56
+ )
57
+ normal_draw_config: DrawConfig = DrawConfig()
58
+ zoom_in_factor: float = 1.1
59
+ zoom_out_factor: float = 0.9
60
+
61
+ @property
62
+ def assets_folder(self):
63
+ return self.project_dir / "assets"
64
+
65
+ class Config:
66
+ arbitrary_types_allowed = True
67
+
68
+
69
+ class LayerConfig(BaseConfig):
70
+ show_labels: bool = True
71
+ show_annotations: bool = True
72
+
73
+ default_label: Label = Field(
74
+ default_factory=lambda: Label("Unlabeled", QColor(255, 255, 255))
75
+ )
76
+ predefined_labels: List[Label] = Field(
77
+ default_factory=lambda: [
78
+ Label("Unlabeled", QColor(255, 255, 255)),
79
+ Label("Label 1", QColor(255, 0, 0)),
80
+ Label("Label 2", QColor(0, 255, 0)),
81
+ Label("Label 3", QColor(0, 0, 255)),
82
+ Label("Custom", QColor(128, 128, 128)),
83
+ ]
84
+ )
85
+
86
+ def get_label_color(self, label):
87
+ for lbl in self.predefined_labels:
88
+ if lbl.name == label:
89
+ return lbl.color
90
+ return self.default_label.color
91
+
92
+
93
+ class CanvasConfig(BaseConfig):
94
+ save_on_bake: bool = True
95
+ bake_timeout: float = -1.0
96
+ filename_format: str = "{project_name}_{timestamp}"
97
+ export_format: str = "png"
98
+ max_xpos: int = 1000
99
+ max_ypos: int = 1000
100
+ max_scale: int = 10
101
+ # whether to allow the use of sliders to change layer properties
102
+ allow_slider_usage: bool = True
103
+
104
+ write_annotations: bool = True
105
+ write_labels: bool = True
106
+ write_masks: bool = True
107
+ fps: int = 5
108
+
109
+ @property
110
+ def export_folder(self):
111
+ folder = self.project_dir / "assets" / "exports"
112
+ folder.mkdir(parents=True, exist_ok=True)
113
+ return folder
114
+
115
+
116
+ class CursorDef:
117
+ POINT_CURSOR: Qt.CursorShape = Qt.CrossCursor
118
+ POLYGON_CURSOR: Qt.CursorShape = Qt.CrossCursor
119
+ RECTANGLE_CURSOR: Qt.CursorShape = Qt.CrossCursor
120
+ IDLE_CURSOR: Qt.CursorShape = Qt.ArrowCursor
121
+ PAN_CURSOR: Qt.CursorShape = Qt.OpenHandCursor
122
+ ZOOM_IN_CURSOR: Qt.CursorShape = Qt.SizeFDiagCursor
123
+ ZOOM_OUT_CURSOR: Qt.CursorShape = Qt.SizeBDiagCursor
124
+ TRANSFORM_UPDOWN: Qt.CursorShape = Qt.SizeVerCursor
125
+ TRANSFORM_LEFTRIGHT: Qt.CursorShape = Qt.SizeHorCursor
126
+ TRANSFORM_ALL: Qt.CursorShape = Qt.SizeAllCursor
127
+ GRAB_CURSOR: Qt.CursorShape = Qt.OpenHandCursor
128
+ GRABBING_CURSOR: Qt.CursorShape = Qt.ClosedHandCursor
129
+
130
+
131
+ class DefaultModelConfig(BaseModel):
132
+ model_type: ModelType = ModelType.DETECTION
133
+ model_name: str = "Dummy Model"
134
+ model_description: str = "This is a dummy model"
135
+ model_version: str = "1.0"
136
+ model_author: str = "Anonymous"
137
+ model_license: str = "MIT"
138
+ input_size: Tuple[int, int] = (224, 224)
139
+ input_channels: int = 3
140
+ class_names: List[str] = ["class1", "class2", "class3"]
141
+ device: str = "cpu"
142
+ return_annotated_image: bool = False
143
+
144
+ @property
145
+ def num_classes(self):
146
+ return len(self.class_names)
147
+
148
+ class Config:
149
+ arbitrary_types_allowed = True
@@ -0,0 +1 @@
1
+ from .defs import * # noqa
@@ -0,0 +1,239 @@
1
+ from PySide6.QtCore import QPointF, QRectF
2
+ from PySide6.QtGui import QColor, QPolygonF
3
+ from PySide6.QtGui import QImage
4
+
5
+ from enum import Enum
6
+ from dataclasses import dataclass, field
7
+ import numpy as np
8
+ from datetime import datetime
9
+ from pydantic import BaseModel
10
+ from pathlib import Path
11
+
12
+
13
+ class MouseMode(Enum):
14
+ IDLE = 0
15
+ POINT = 1
16
+ POLYGON = 2
17
+ RECTANGLE = 3
18
+ PAN = 4
19
+ ZOOM_IN = 5
20
+ ZOOM_OUT = 6
21
+ RESIZE = 7
22
+ RESIZE_HEIGHT = 8
23
+ RESIZE_WIDTH = 9
24
+ GRAB = 11
25
+
26
+
27
+ class ModelType(str, Enum):
28
+ DETECTION = "detection"
29
+ SEGMENTATION = "segmentation"
30
+ CLASSIFICATION = "classification"
31
+ PROMPT = "prompt"
32
+
33
+
34
+ class PredictionResult(BaseModel):
35
+ class_name: str = None
36
+ class_id: int = None
37
+ score: float = None
38
+ rectangle: list[int] | None = None
39
+ mask: np.ndarray | None = None
40
+ keypoints: list[list[int, int]] | None = None
41
+ polygon: np.ndarray | None = None
42
+ prompt: str | None = None
43
+ annotated_image: np.ndarray | None = None
44
+ annotation_time: str | None = None
45
+
46
+ class Config:
47
+ arbitrary_types_allowed = True
48
+
49
+
50
+ @dataclass
51
+ class LayerState:
52
+ layer_id: str = ""
53
+ state_step: int = 0
54
+ layer_name: str = "Layer"
55
+ opacity: float = 255.00
56
+ position: QPointF = field(default_factory=lambda: QPointF(0, 0))
57
+ rotation: float = 0.00
58
+ scale: float = 1.00
59
+ scale_x: float = 1.00
60
+ scale_y: float = 1.00
61
+ transform_origin: QPointF = field(default_factory=lambda: QPointF(0.0, 0.0))
62
+ order: int = 0
63
+ visible: bool = True
64
+ allow_annotation_export: bool = True
65
+ playing: bool = False
66
+ selected: bool = False
67
+ is_annotable: bool = True
68
+ status: str = "Ready"
69
+
70
+ def copy(self):
71
+ return LayerState(
72
+ layer_id=self.layer_id,
73
+ layer_name=self.layer_name,
74
+ opacity=self.opacity,
75
+ position=QPointF(self.position.x(), self.position.y()),
76
+ rotation=self.rotation,
77
+ scale=self.scale,
78
+ scale_x=self.scale_x,
79
+ scale_y=self.scale_y,
80
+ transform_origin=QPointF(
81
+ self.transform_origin.x(), self.transform_origin.y()
82
+ ),
83
+ order=self.order,
84
+ visible=self.visible,
85
+ allow_annotation_export=self.allow_annotation_export,
86
+ playing=self.playing,
87
+ selected=self.selected,
88
+ is_annotable=self.is_annotable,
89
+ status=self.status,
90
+ )
91
+
92
+
93
+ @dataclass
94
+ class Label:
95
+ name: str = "Unlabeled"
96
+ color: QColor = field(default_factory=lambda: QColor(255, 255, 255))
97
+
98
+
99
+ @dataclass
100
+ class Annotation:
101
+ annotation_id: int
102
+ label: str
103
+ color: QColor = field(default_factory=lambda: QColor(255, 255, 255))
104
+ points: list[QPointF] = field(default_factory=list)
105
+ # [x, y, width, height]
106
+ rectangle: QRectF = None
107
+ polygon: QPolygonF = None
108
+ is_complete: bool = False
109
+ selected: bool = False
110
+ score: float = None
111
+ annotator: str = "User"
112
+ annotation_time: str = field(
113
+ default_factory=lambda: datetime.now().strftime("%Y-%m-%d %H:%M:%S")
114
+ )
115
+ visible: bool = True
116
+ file_path: Path = field(default_factory=lambda: Path("Runtime"))
117
+ is_model_generated: bool = False
118
+ model_name: str = None
119
+
120
+ def copy(self):
121
+ ann = Annotation(
122
+ annotation_id=self.annotation_id,
123
+ label=self.label,
124
+ color=QColor(self.color.red(), self.color.green(), self.color.blue()),
125
+ points=[QPointF(p) for p in self.points],
126
+ rectangle=QRectF(self.rectangle) if self.rectangle else None,
127
+ polygon=QPolygonF(self.polygon) if self.polygon else None,
128
+ is_complete=self.is_complete,
129
+ selected=self.selected,
130
+ score=self.score,
131
+ annotator=self.annotator,
132
+ annotation_time=self.annotation_time,
133
+ visible=self.visible,
134
+ file_path=self.file_path,
135
+ is_model_generated=self.is_model_generated,
136
+ model_name=self.model_name,
137
+ )
138
+ ann.is_selected = False
139
+ return ann
140
+
141
+ @property
142
+ def name(self):
143
+ return f"{self.annotation_id} {self.label}"
144
+
145
+ @staticmethod
146
+ def save_as_json(annotations: list["Annotation"], path: str):
147
+ import json
148
+
149
+ annotations_dict = []
150
+ for annotation in annotations:
151
+ rectangle = None
152
+ if annotation.rectangle:
153
+ rectangle = [
154
+ annotation.rectangle.x(),
155
+ annotation.rectangle.y(),
156
+ annotation.rectangle.width(),
157
+ annotation.rectangle.height(),
158
+ ]
159
+ polygon = None
160
+ if annotation.polygon:
161
+ polygon = [[p.x(), p.y()] for p in annotation.polygon]
162
+ points = None
163
+ if annotation.points:
164
+ points = [[p.x(), p.y()] for p in annotation.points]
165
+ data = {
166
+ "annotation_id": annotation.annotation_id,
167
+ "label": annotation.label,
168
+ "color": annotation.color.getRgb(),
169
+ "points": points,
170
+ "rectangle": rectangle,
171
+ "polygon": polygon,
172
+ "is_complete": annotation.is_complete,
173
+ "selected": annotation.selected,
174
+ "score": annotation.score,
175
+ "annotator": annotation.annotator,
176
+ "annotation_time": annotation.annotation_time,
177
+ "visible": annotation.visible,
178
+ "file_path": str(annotation.file_path),
179
+ "is_model_generated": annotation.is_model_generated,
180
+ "model_name": annotation.model_name,
181
+ }
182
+ annotations_dict.append(data)
183
+
184
+ with open(path, "w") as f:
185
+ json.dump(annotations_dict, f, indent=4)
186
+
187
+ @staticmethod
188
+ def load_from_json(path: str):
189
+ import json
190
+
191
+ with open(path, "r") as f:
192
+ data = json.load(f)
193
+
194
+ annotations = []
195
+ for d in data:
196
+ annotation = Annotation(
197
+ annotation_id=d["annotation_id"],
198
+ label=d["label"],
199
+ color=QColor(*d["color"]),
200
+ is_complete=d.get("is_complete", False),
201
+ selected=d.get("selected", False),
202
+ score=d.get("score", None),
203
+ annotator=d.get("annotator", None),
204
+ annotation_time=d.get(
205
+ "annotation_time", datetime.now().strftime("%Y-%m-%d %H:%M:%S")
206
+ ),
207
+ visible=d.get("visible", True),
208
+ file_path=Path(d.get("file_path", "Runtime")),
209
+ is_model_generated=d.get("is_model_generated", False),
210
+ model_name=d.get("model_name", None),
211
+ )
212
+
213
+ # Handle points safely
214
+ points_data = d.get("points")
215
+ if points_data:
216
+ annotation.points = [QPointF(*p) for p in points_data]
217
+
218
+ # Handle rectangle safely
219
+ rect_data = d.get("rectangle")
220
+ if rect_data:
221
+ annotation.rectangle = QRectF(*rect_data)
222
+
223
+ # Handle polygon safely
224
+ polygon_data = d.get("polygon")
225
+ if polygon_data:
226
+ annotation.polygon = QPolygonF([QPointF(*p) for p in polygon_data])
227
+
228
+ annotations.append(annotation)
229
+
230
+ return annotations
231
+
232
+
233
+ @dataclass
234
+ class BakingResult:
235
+ filename: Path
236
+ image: QImage = field(default=None)
237
+ masks: list[np.ndarray] = field(default_factory=list)
238
+ mask_names: list[str] = field(default_factory=list)
239
+ annotations: list[Annotation] = field(default_factory=list)
File without changes
@@ -0,0 +1,39 @@
1
+ from abc import ABC, abstractmethod
2
+ from imagebaker.core.defs import LayerState
3
+ from imagebaker import logger
4
+
5
+
6
+ class BasePlugin(ABC):
7
+
8
+ def __init__(self, layer_state: LayerState, final_step: int = -1):
9
+ self.initial_layer_state = layer_state
10
+ self.final_step = final_step
11
+ self.current_step = 0
12
+
13
+ def __str__(self):
14
+ return self.__class__.__name
15
+
16
+ @abstractmethod
17
+ def compute_step(self, step: int):
18
+ """
19
+ Compute the step based on the given step.
20
+
21
+ :param step: The step to compute the step by.
22
+ """
23
+ pass
24
+
25
+ def update(self, step: int = 1):
26
+ """
27
+ Returns the updated state after passed step.
28
+
29
+ :param step: The step to update the state by.
30
+ """
31
+ if (
32
+ self.final_step != -1
33
+ and step > self.final_step
34
+ or self.current_step >= self.final_step
35
+ ):
36
+ logger.info(f"Final step reached for {self}. Returning last step.")
37
+ step = self.final_step
38
+ self.compute_step(step)
39
+ self.current_step += step
@@ -0,0 +1,39 @@
1
+ from imagebaker.core.plugins.base_plugin import BasePlugin
2
+ from imagebaker.core.defs import LayerState
3
+
4
+ import numpy as np
5
+ from PySide6.QtCore import QPointF
6
+
7
+
8
+ class CosinePlugin(BasePlugin):
9
+ """
10
+ Cosine Plugin implementation.
11
+ """
12
+
13
+ def __init__(self, layer_state: LayerState, amplitude=50, frequency=0.1):
14
+ """
15
+ Initialize the CosinePlugin.
16
+
17
+ :param layer_state: The LayerState to modify.
18
+ :param amplitude: The amplitude of the cosine wave (how far it moves).
19
+ :param frequency: The frequency of the cosine wave (how fast it oscillates).
20
+ """
21
+ super().__init__(layer_state)
22
+ self.amplitude = amplitude
23
+ self.frequency = frequency
24
+
25
+ def compute_step(self, step):
26
+ """
27
+ Update the x and y positions of the LayerState based on a cosine curve.
28
+
29
+ :param step: The step to compute the cosine value for.
30
+ """
31
+ # Compute the new x position based on the cosine curve
32
+ layer_state = LayerState()
33
+ layer_state.position = QPointF(
34
+ self.initial_layer_state.position.x()
35
+ + self.amplitude * np.cos(self.frequency * step),
36
+ self.initial_layer_state.position.y(),
37
+ )
38
+
39
+ return layer_state
File without changes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: imagebaker
3
- Version: 0.0.1
3
+ Version: 0.0.2
4
4
  Summary: A package for baking images.
5
5
  Home-page: https://github.com/q-viper/Image-Baker
6
6
  Author: Ramkrishna Acharya
@@ -8,6 +8,14 @@ imagebaker.egg-info/dependency_links.txt
8
8
  imagebaker.egg-info/entry_points.txt
9
9
  imagebaker.egg-info/requires.txt
10
10
  imagebaker.egg-info/top_level.txt
11
+ imagebaker/core/__init__.py
12
+ imagebaker/core/configs/__init__.py
13
+ imagebaker/core/configs/configs.py
14
+ imagebaker/core/defs/__init__.py
15
+ imagebaker/core/defs/defs.py
16
+ imagebaker/core/plugins/__init__.py
17
+ imagebaker/core/plugins/base_plugin.py
18
+ imagebaker/core/plugins/cosine_plugin.py
11
19
  imagebaker/layers/__init__.py
12
20
  imagebaker/layers/annotable_layer.py
13
21
  imagebaker/layers/base_layer.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="imagebaker",
5
- version="0.0.1",
5
+ version="0.0.2",
6
6
  packages=find_packages(), # Automatically finds all packages
7
7
  install_requires=[
8
8
  "numpy",
File without changes
File without changes
File without changes