analitics

Pages

Showing posts with label tutorial. Show all posts
Showing posts with label tutorial. Show all posts

Sunday, November 30, 2025

News : the xonsh shell language and command prompt.

Xonsh is a modern, full-featured and cross-platform python shell. The language is a superset of Python 3.6+ with additional shell primitives that you are used to from Bash and IPython. It works on all major systems including Linux, OSX, and Windows. Xonsh is meant for the daily use of experts and novices.
The install is easy with pip tool:
python -m pip install 'xonsh[full]'

Python 3.13.0 : mitmproxy - part 001.

Mitmproxy is an interactive, open‑source proxy tool that lets you intercept, inspect, and modify HTTP and HTTPS traffic in real time. It acts as a "man‑in‑the‑middle" between your computer and the internet, making it possible to debug, test, or analyze how applications communicate online.
Why Python?
  • Mitmproxy is built in Python and exposes a powerful addon API
  • You can write custom scripts to automate tasks and traffic manipulation
  • Block or rewrite requests and responses with flexible logic
  • Inject headers or simulate server responses for testing
  • Integrate with other Python tools for advanced automation
  • Intercept and inspect HTTP and HTTPS traffic in real time
  • Modify requests and responses dynamically with Python scripts
  • Block specific hosts or URLs to prevent unwanted connections
  • Inject custom headers into outgoing requests for debugging or control
  • Rewrite response bodies (HTML, JSON, text) using regex or custom logic
  • Log and save traffic flows for later analysis and replay
  • Simulate server responses to test client behavior offline
  • Automate testing of web applications and APIs with scripted rules
  • Monitor performance metrics such as latency and payload size
  • Integrate with other Python tools for advanced automation and analysis
  • Use a trusted root certificate to decrypt and modify HTTPS traffic securely
Let's install:
pip install mitmproxy
Let's see the python script:
# addon.py
from mitmproxy import http
from mitmproxy import ctx
import re

BLOCKED_HOSTS = {
    "hyte.com",
    "ads.example.org",
}

REWRITE_RULES = [
    # Each rule: (pattern, replacement, content_type_substring)
    (re.compile(rb"Hello World"), b"Salut lume", "text/html"),
    (re.compile(rb"tracking", re.IGNORECASE), b"observare", "text"),
]

ADD_HEADERS = {
    "X-Debug-Proxy": "mitm",
    "X-George-Tool": "true",
}

class GeorgeProxy:
    def __init__(self):
        self.rewrite_count = 0

    def load(self, loader):
        ctx.log.info("GeorgeProxy addon loaded.")

    def request(self, flow: http.HTTPFlow):
        # Block specific hosts early
        host = flow.request.host
        if host in BLOCKED_HOSTS:
            flow.response = http.Response.make(
                403,
                b"Blocked by GeorgeProxy",
                {"Content-Type": "text/plain"}
            )
            ctx.log.warn(f"Blocked request to {host}")
            return

        # Add custom headers to outgoing requests
        for k, v in ADD_HEADERS.items():
            flow.request.headers[k] = v

        ctx.log.info(f"REQ {flow.request.method} {flow.request.url}")

    def response(self, flow: http.HTTPFlow):
        # Only process text-like contents
        ctype = flow.response.headers.get("Content-Type", "").lower()
        raw = flow.response.raw_content

        if raw and any(t in ctype for t in ["text", "html", "json"]):
            new_content = raw
            for pattern, repl, t in REWRITE_RULES:
                if t in ctype:
                    new_content, n = pattern.subn(repl, new_content)
                    self.rewrite_count += n

            if new_content != raw:
                flow.response.raw_content = new_content
                # Update Content-Length only if present
                if "Content-Length" in flow.response.headers:
                    flow.response.headers["Content-Length"] = str(len(new_content))
                ctx.log.info(f"Rewrote content ({ctype}); total matches: {self.rewrite_count}")

        ctx.log.info(f"RESP {flow.response.status_code} {flow.request.url}")

addons = [GeorgeProxy()]
Let's run it:
mitmdump -s addon.py
[21:46:04.435] Loading script addon.py
[21:46:04.504] GeorgeProxy addon loaded.
[21:46:04.506] HTTP(S) proxy listening at *:8080.
[21:46:18.547][127.0.0.1:52128] client connect
[21:46:18.593] REQ GET http://httpbin.org/get
[21:46:18.768][127.0.0.1:52128] server connect httpbin.org:80 (52.44.182.178:80)
[21:46:18.910] RESP 200 http://httpbin.org/get
127.0.0.1:52128: GET http://httpbin.org/get
              << 200 OK 353b
[21:46:19.019][127.0.0.1:52128] client disconnect
[21:46:19.021][127.0.0.1:52128] server disconnect httpbin.org:80 (52.44.182.178:80)
Let's see the result:
curl -x http://127.0.0.1:8080 http://httpbin.org/get
{
  "args": {},
  "headers": {
    "Accept": "*/*",
    "Host": "httpbin.org",
    "Proxy-Connection": "Keep-Alive",
    "User-Agent": "curl/8.13.0",
    "X-Amzn-Trace-Id": "Root=1-692c9f0b-7eaf43e61f276ee62b089933",
    "X-Debug-Proxy": "mitm",
    "X-George-Tool": "true"
  },
  "origin": "84.117.220.94",
  "url": "http://httpbin.org/get"
}
This means
The request successfully went through mitmproxy running on 127.0.0.1:8080. Your addon worked: it injected the custom headers (X-Debug-Proxy, X-George-Tool). The httpbin.org echoed back the request details, showing exactly what the server received.

Python 3.13.0 : Tornado - part 001.

Python Tornado is a high‑performance web framework and asynchronous networking library designed for extreme scalability and real‑time applications. Its standout capability is handling tens of thousands of simultaneous connections efficiently, thanks to non‑blocking I/O.
This is an open source project actively maintained and available on tornadoweb.org.

Python Tornado – Key Capabilities

  • Massive Concurrency: Tornado can scale to tens of thousands of open connections without requiring huge numbers of threads.
  • Non‑blocking I/O: Its asynchronous design makes it ideal for apps that need to stay responsive under heavy load.
  • WebSockets Support: Built‑in support for WebSockets enables real‑time communication between clients and servers.
  • Long‑lived Connections: Perfect for long polling, streaming, or chat applications where connections remain open for extended periods.
  • Coroutines & Async/Await: Tornado integrates tightly with Python’s asyncio, allowing developers to write clean asynchronous code using coroutines.
  • Versatile Use Cases: Beyond web apps, Tornado can act as an HTTP client/server, handle background tasks, or integrate with other services.
Tornado setup: The script creates a web server using the Tornado framework, listening on port 8888.
Route definition: A single route /form is registered, handled by the FormHandler class.
GET request: When you visit http://localhost:8888/form, the server responds with an HTML page (form.html) that contains a simple input form.
POST request: When the form is submitted, the post() method retrieves the value of the name field using self.get_argument("name").
Response: The server then sends back a personalized message
Let's see the script:
import tornado.ioloop
import tornado.web
import os

class FormHandler(tornado.web.RequestHandler):
    def get(self):
        self.render("form.html")  # Render an HTML form

    def post(self):
        name = self.get_argument("name")
        self.write(f"Hello, {name}!")

def make_app():
    return tornado.web.Application([
        (r"/form", FormHandler),
    ],
    template_path=os.path.join(os.path.dirname(__file__), "templates")  # <-- aici
    )

if __name__ == "__main__":
    app = make_app()
    app.listen(8888)
    print("Server pornit pe http://localhost:8888/form")
    tornado.ioloop.IOLoop.current().start()

Thursday, October 30, 2025

Python 3.13.0 : xAI A.P.I. with regional endpoint on xai_sdk python package.

Grok is a family of Large Language Models (LLMs) developed by xAI.
Inspired by the Hitchhiker's Guide to the Galaxy, Grok is a maximally truth-seeking AI that provides insightful, unfiltered truths about the universe.
xAI offers an API for developers to programmatically interact with our Grok models. The same models power our consumer facing services such as Grok.com, the iOS and Android apps, as well as Grok in X experience.
If you want to use a regional endpoint, you need to specify the endpoint url when making request with SDK. In xAI SDK, this is specified through the api_host parameter.
Is not free models available for xAI A.P.I.
See this example from the official website:
import os

from xai_sdk import Client
from xai_sdk.chat import user

client = Client(
api_key=os.getenv("XAI_API_KEY"),
api_host="us-east-1.api.x.ai" # Without the https://
)

chat = client.chat.create(model="grok-4")
chat.append(user("What is the meaning of life?"))

completion = chat.sample()

Monday, October 20, 2025

Python Qt6 : tool for remove duplicate files ...

Today I created a Python script with PyQt6 that allows me to remove duplicate files based on three ways of selecting the type of duplicate.
The script also makes an estimate of the execution time...
Because the source code is relatively simple and can be very easily reconstructed with the help of artificial intelligence, I am not adding it to the posts.
Here is what the application looks like with PyQt6.

Saturday, October 18, 2025

Python Qt6 : tool for cutting images ...

Today I made a script that allows adding custom horizontal and vertical sliders to an image and, depending on the custom distance between them, cuts the image into squares of different sizes.

Python Qt6 : tool for renaming files with creation date .

Since this hacking and the crashes... I've always taken screenshots... Today I created a small script that takes files from a folder and renames them with the creation date in this format...yyyyMMdd_HHmmss .
... obviously artificial intelligence helped me.
This is the source code :
import sys
import os
import shutil
from PyQt6.QtWidgets import QApplication, QWidget, QPushButton, QVBoxLayout, QFileDialog, QMessageBox
from PyQt6.QtCore import QDateTime

class FileRenamer(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Redenumire fișiere cu dată și index")
        self.setGeometry(100, 100, 400, 150)

        layout = QVBoxLayout()

        self.button = QPushButton("Selectează folderul și redenumește fișierele")
        self.button.clicked.connect(self.rename_files)
        layout.addWidget(self.button)

        self.setLayout(layout)

    def rename_files(self):
        folder = QFileDialog.getExistingDirectory(self, "Selectează folderul")
        if not folder:
            return

        files = [f for f in os.listdir(folder) if os.path.isfile(os.path.join(folder, f))]
        files.sort()  # Sortează pentru consistență

        for index, filename in enumerate(files, start=1):
            old_path = os.path.join(folder, filename)
            try:
                creation_time = os.path.getctime(old_path)
                dt = QDateTime.fromSecsSinceEpoch(int(creation_time))
                date_str = dt.toString("yyyyMMdd_HHmmss")
                ext = os.path.splitext(filename)[1]
                new_name = f"{date_str}_{index:03d}{ext}"
                new_path = os.path.join(folder, new_name)

                # Evită suprascrierea fișierelor existente
                if not os.path.exists(new_path):
                    shutil.move(old_path, new_path)
            except Exception as e:
                QMessageBox.critical(self, "Eroare", f"Eroare la fișierul {filename}:\n{str(e)}")
                continue

        QMessageBox.information(self, "Succes", "Fișierele au fost redenumite cu succes!")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = FileRenamer()
    window.show()
    sys.exit(app.exec())

Saturday, October 4, 2025

Friday, September 26, 2025

Python Qt6 : tool for game development with PNG images.

Today, I worked with art artificial intelligence, to create tool for my game development.
I used python and PyQt6 and this tool help me to remove border, resize, split, rename and save images as PNG file type for Godot game engine.

Saturday, August 30, 2025

Python Qt6 : ... management of installations and build python package.

Yesterday I created a small project for managing Python packages and building a new package based on added modules. I only tested the local installations of various Python versions and the creation of a new package, but it worked.
python catafest_build_package_001.py
🔍 Verificare module standard...
[✓] Modul standard 'json' este disponibil.
[✓] Modul standard 'subprocess' este disponibil.
[✓] Modul standard 'platform' este disponibil.
[✓] Modul standard 'datetime' este disponibil.
[✓] Modul standard 'os' este disponibil.
[✓] Modul standard 'sys' este disponibil.

📦 Verificare și instalare module pip...
[✓] Modulul 'PyQt6' este deja instalat.
[✓] Modulul 'build' este deja instalat.
* Creating isolated environment: venv+pip...
* Installing packages in isolated environment:
  - setuptools
  - wheel
...

Thursday, August 21, 2025

Python Qt6 : Use python with ffmpeg tool ...

If you download a video from youtube with high resolution using a tool as yt-dlp then you can get two files with video and audio content:
... one with be with .f401.mp4 and another with .f251-9.webm and using the ffmpeg tool you can create one .mp4 file with both audio and video content.
Let's see a source code with python and PyQt6 module to search into D:\Software folder and create the mp4 file.

import os
import subprocess
from PyQt6.QtWidgets import (
    QApplication, QWidget, QVBoxLayout, QPushButton,
    QListWidget, QMessageBox
)
# fisier download : yt-dlp.exe -vU https://www.youtube.com/watch?v=xxxxxx -f bestvideo*+bestaudio/best
FOLDER_PATH = r"D:\Software"

class FFmpegMerger(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Combinare Video + Audio cu FFmpeg")
        self.resize(600, 400)

        self.layout = QVBoxLayout()
        self.file_list = QListWidget()
        self.process_button = QPushButton("Prelucrează în MP4")

        self.layout.addWidget(self.file_list)
        self.layout.addWidget(self.process_button)
        self.setLayout(self.layout)

        self.process_button.clicked.connect(self.process_files)

        self.populate_file_list()

    def populate_file_list(self):
        files = os.listdir(FOLDER_PATH)
        video_files = [f for f in files if f.endswith(".f401.mp4")]
        audio_files = [f for f in files if f.endswith(".f251-9.webm")]

        base_names = set(f.split(".f401.mp4")[0] for f in video_files)
        candidates = []

        for base in base_names:
            audio_name = f"{base}.f251-9.webm"
            output_name = f"{base}.mp4"
            if audio_name in audio_files and output_name not in files:
                candidates.append(base)

        for name in candidates:
            self.file_list.addItem(name)

    def process_files(self):
        for i in range(self.file_list.count()):
            base = self.file_list.item(i).text()
            video_path = os.path.join(FOLDER_PATH, f"{base}.f401.mp4")
            audio_path = os.path.join(FOLDER_PATH, f"{base}.f251-9.webm")
            output_path = os.path.join(FOLDER_PATH, f"{base}.mp4")

            cmd = [
                "ffmpeg",
                "-i", video_path,
                "-i", audio_path,
                "-c:v", "copy",
                "-c:a", "aac",
                "-strict", "experimental",
                output_path
            ]

            try:
                subprocess.run(cmd, check=True)
            except subprocess.CalledProcessError as e:
                QMessageBox.critical(self, "Eroare", f"Eroare la procesarea {base}: {e}")
                return

        QMessageBox.information(self, "Succes", "Toate fișierele au fost prelucrate cu succes!")

if __name__ == "__main__":
    app = QApplication([])
    window = FFmpegMerger()
    window.show()
    app.exec()

Monday, July 21, 2025

News : The geoai-py - part 001.

A powerful Python package for integrating Artificial Intelligence with geospatial data analysis and visualization
GeoAI bridges the gap between AI and geospatial analysis, providing tools for processing, analyzing, and visualizing geospatial data using advanced machine learning techniques. Whether you're working with satellite imagery, LiDAR point clouds, or vector data, GeoAI offers intuitive interfaces to apply cutting-edge AI models.
Today , I tested this python package named geoai-py. I used the pip tool:
pip install geoai-py
Successfully installed Flask-Caching-2.3.1 MarkupSafe-3.0.2 PySocks-1.7.1 PyYAML-6.0.2 absl-py-2.3.1 aenum-3.1.16 affine-2.4.0 aiohappyeyeballs-2.6.1 aiohttp-3.12.14 aiosignal-1.4.0 albucore-0.0.24 albumentations-2.0.8 aniso8601-10.0.1 annotated-types-0.7.0 antlr4-python3-runtime-4.9.3 anyio-4.9.0 anywidget-0.9.18 argon2-cffi-25.1.0 argon2-cffi-bindings-21.2.0 arrow-1.3.0 asttokens-3.0.0 beautifulsoup4-4.13.4 bitsandbytes-0.46.1 bleach-6.2.0 blinker-1.9.0 bqplot-0.12.45 branca-0.8.1 buildingregulariser-0.2.2 cachelib-0.13.0 cachetools-6.1.0 cffi-1.17.1 click-8.2.1 click-plugins-1.1.1.2 cligj-0.7.2 color-operations-0.2.0 comm-0.2.2 contextily-1.6.2 contourpy-1.3.2 cycler-0.12.1 datasets-4.0.0 decorator-5.2.1 defusedxml-0.7.1 dill-0.3.8 docstring-parser-0.17.0 duckdb-1.3.2 einops-0.8.1 eval-type-backport-0.2.2 ever-beta-0.5.1 executing-2.2.0 fastjsonschema-2.21.1 filelock-3.18.0 fiona-1.10.1 flask-3.1.1 flask-cors-6.0.1 flask-restx-1.3.0 folium-0.20.0 fonttools-4.59.0 fqdn-1.5.1 frozenlist-1.7.0 fsspec-2025.3.0 gdown-5.2.0 geoai-py-0.9.0 geographiclib-2.0 geojson-3.2.0 geopandas-1.1.1 geopy-2.4.1 gitdb-4.0.12 gitpython-3.1.44 grpcio-1.73.1 h11-0.16.0 httpcore-1.0.9 httpx-0.28.1 huggingface_hub-0.33.4 hydra-core-1.3.2 importlib-resources-6.5.2 ipyevents-2.0.2 ipyfilechooser-0.6.0 ipyleaflet-0.20.0 ipython-9.4.0 ipython-pygments-lexers-1.1.1 ipytree-0.2.2 ipyvue-1.11.2 ipyvuetify-1.11.3 ipywidgets-8.1.7 isoduration-20.11.0 itsdangerous-2.2.0 jedi-0.19.2 jinja2-3.1.6 joblib-1.5.1 jsonargparse-4.40.0 jsonnet-0.21.0 jsonpointer-3.0.0 jupyter-client-8.6.3 jupyter-core-5.8.1 jupyter-events-0.12.0 jupyter-leaflet-0.20.0 jupyter-server-2.16.0 jupyter-server-proxy-4.4.0 jupyter-server-terminals-0.5.3 jupyterlab-pygments-0.3.0 jupyterlab_widgets-3.0.15 kiwisolver-1.4.8 kornia-0.8.1 kornia_rs-0.1.9 leafmap-0.48.6 lightly-1.5.21 lightly_utils-0.0.2 lightning-2.5.2 lightning-utilities-0.14.3 localtileserver-0.10.6 mapclassify-2.10.0 maplibre-0.3.4 markdown-3.8.2 markdown-it-py-3.0.0 matplotlib-3.10.3 matplotlib-inline-0.1.7 mdurl-0.1.2 mercantile-1.2.1 mistune-3.1.3 morecantile-6.2.0 multidict-6.6.3 multiprocess-0.70.16 narwhals-1.48.0 nbclient-0.10.2 nbconvert-7.16.6 nbformat-5.10.4 numexpr-2.11.0 omegaconf-2.3.0 opencv-python-headless-4.12.0.88 overrides-7.7.0 overturemaps-0.15.0 pandas-2.3.1 pandocfilters-1.5.1 parso-0.8.4 planetary-computer-1.0.0 plotly-6.2.0 prettytable-3.16.0 prometheus-client-0.22.1 prompt_toolkit-3.0.51 propcache-0.3.2 psygnal-0.14.0 pure-eval-0.2.3 pyarrow-21.0.0 pycparser-2.22 pydantic-2.11.7 pydantic-core-2.33.2 pygments-2.19.2 pyogrio-0.11.0 pyparsing-3.2.3 pyproj-3.7.1 pystac-1.13.0 pystac-client-0.9.0 python-box-7.3.2 python-dateutil-2.9.0.post0 python-dotenv-1.1.1 python-json-logger-3.3.0 pytorch_lightning-2.5.2 pytz-2025.2 pywin32-311 pywinpty-2.0.15 pyzmq-27.0.0 rasterio-1.4.3 regex-2024.11.6 rfc3339-validator-0.1.4 rfc3986-validator-0.1.1 rich-14.0.0 rio-cogeo-5.4.2 rio-tiler-7.8.1 rioxarray-0.19.0 rtree-1.4.0 safetensors-0.5.3 scikit-learn-1.7.1 scooby-0.10.1 segmentation-models-pytorch-0.5.0 send2trash-1.8.3 sentry-sdk-2.33.0 server-thread-0.3.0 shapely-2.1.1 simpervisor-1.0.0 simsimd-6.5.0 six-1.17.0 smmap-5.0.2 sniffio-1.3.1 soupsieve-2.7 stack_data-0.6.3 stringzilla-3.12.5 tensorboard-2.20.0 tensorboard-data-server-0.7.2 tensorboardX-2.6.4 terminado-0.18.1 threadpoolctl-3.6.0 timm-1.0.17 tinycss2-1.4.0 tokenizers-0.21.2 torch-2.7.1 torchange-0.0.1 torchgeo-0.7.1 torchinfo-1.8.0 torchmetrics-1.7.4 torchvision-0.22.1 tornado-6.5.1 traitlets-5.14.3 traittypes-0.2.1 transformers-4.53.2 types-python-dateutil-2.9.0.20250708 typeshed-client-2.8.2 typing-inspection-0.4.1 tzdata-2025.2 uri-template-1.3.0 uvicorn-0.35.0 wandb-0.21.0 wcwidth-0.2.13 webcolors-24.11.1 webencodings-0.5.1 websocket-client-1.8.0 werkzeug-3.1.3 whitebox-2.3.6 whiteboxgui-2.3.0 widgetsnbextension-4.0.14 xarray-2025.7.1 xxhash-3.5.0 xyzservices-2025.4.0 yarl-1.20.1
Let's see my testing python example:
>>> import geoai
>>> dir(geoai)
['AgricultureFieldDelineator', 'Any', 'AutoConfig', 'AutoModelForMaskGeneration', 
'AutoModelForMaskedImageModeling', 'AutoProcessor', 'BoundingBox', 'BuildingFootprintExtractor',
 'CLIPSegForImageSegmentation', 'CLIPSegProcessor', 'CLIPSegmentation', 'CarDetector', 
'ChangeDetection', 'CustomDataset', 'DetectionResult', 'Dict', 'ET', 'GroundedSAM', 'Image', 
'Iterable', 'List', 'Map', 'MapLibre', 'MultiPolygon', 'NonGeoDataset', 'ObjectDetector', 
'Optional', 'OrderedDict', 'ParkingSplotDetector', 'Path', 'Polygon', 'RandomRotation', 
'ShipDetector', 'SolarPanelDetector', 'Tuple', 'Union', 'Window', '__author__', 
'__builtins__', '__cached__', '__doc__', '__email__', '__file__', '__loader__', '__name__', 
'__package__', '__path__', '__spec__', '__version__', 'adaptive_regularization', 
'add_geometric_properties', 'analyze_vector_attributes', 'batch_vector_to_raster', 'bbox_to_xy',
 'box', 'boxes_to_vector', 'calc_stats', 'change_detection', 'classify', 'classify_image', 
'classify_images', 'clip_raster_by_bbox', 'coords_to_xy', 'create_overview_image', 
'create_split_map', 'create_vector_data', 'csv', 'cv2', 'dataclass', 'deeplabv3_resnet50', 
'dict_to_image', 'dict_to_rioxarray', 'download', 'download_file', 'download_model_from_hf', 
'download_naip', 'download_overture_buildings', 'download_pc_stac_item', 'edit_vector_data', 
'export_geotiff_tiles', 'export_geotiff_tiles_batch', 'export_tiles_to_geojson', 
'export_training_data', 'extract', 'extract_building_stats', 'fasterrcnn_resnet50_fpn_v2', 
'fcn_resnet50', 'features', 'geoai', 'geojson_to_coords', 'geojson_to_xy', 'get_device', 
'get_instance_segmentation_model', 'get_model_config', 'get_model_input_channels', 
'get_overture_data', 'get_raster_info', 'get_raster_info_gdal', 'get_raster_resolution', 
'get_raster_stats', 'get_vector_info', 'get_vector_info_ogr', 'glob', 'gpd', 'hf', 
'hf_hub_download', 'hybrid_regularization', 'image_segmentation', 'inspect_pth_file', 
'install_package', 'instance_segmentation', 'instance_segmentation_batch', 
'instance_segmentation_inference_on_geotiff', 'json', 'leafmap', 'logging', 'maplibregl', 
'mapping', 'mask_generation', 'maskrcnn_resnet50_fpn', 'masks_to_vector', 'math', 
'mosaic_geotiffs', 'ndimage', 'np', 'object_detection', 'object_detection_batch', 'orthogonalize',
 'os', 'pc_collection_list', 'pc_item_asset_list', 'pc_stac_download', 'pc_stac_search', 'pd', 
'pipeline', 'plot_batch', 'plot_images', 'plot_masks', 'plot_performance_metrics', 
'plot_prediction_comparison', 'plt', 'print_raster_info', 'print_vector_info', 'raster_to_vector',
 'raster_to_vector_batch', 'rasterio', 'read_pc_item_asset', 'read_raster', 'read_vector', 
'region_groups', 'regularization', 'regularize', 'requests', 'rotate', 'rowcol_to_xy', 'rxr', 
'segment', 'semantic_segmentation', 'semantic_segmentation_batch', 'set_proj_lib_path', 'shape', 
'show', 'stack_bands', 'subprocess', 'sys', 'temp_file_path', 'time', 'torch', 'torchgeo', 'tqdm',
 'train', 'train_MaskRCNN_model', 'train_classifier', 'train_instance_segmentation_model', 
'train_segmentation_model', 'transform_bounds', 'try_common_architectures', 'utils', 
'vector_to_geojson', 'vector_to_raster', 'view_image', 'view_pc_item', 'view_pc_items', 
'view_raster', 'view_vector', 'view_vector_interactive', 'visualize_vector_by_attribute', 
'warnings', 'write_colormap', 'xr']

Sunday, July 13, 2025

Python Qt6 : simple celtic knots tool with SVG file format.

Today, I try to create SVG file with an celtic knot design tool.
I used random values from -360 up to 360 for for Twist 1, Twist 2, and Twist 3 sliders.
The basic function is this, is created by artificial intelligence and not works very well.
        # Generate star polygon vertices
        points_cw = []
        points_ccw = []
        for i in range(steps):
            t = 2 * math.pi * i / steps
            r = outer_radius if i % 2 == 0 else inner_radius
            x_cw = center[0] + r * math.cos(t)
            y_cw = center[1] + r * math.sin(t)
            x_ccw = center[0] + r * math.cos(-t + math.pi / max(lobes, 1))
            y_ccw = center[1] + r * math.sin(-t + math.pi / max(lobes, 1))
            points_cw.append((x_cw, y_cw))
            points_ccw.append((x_ccw, y_ccw))
See one random example with this tool:

Saturday, July 12, 2025

Python Qt6 : simple merge sprites images with unittest feature.

Today, I created one python tool script with the artificial intelligence to merge sprites images.
I used the artificial intelligence to add unittest to create default images with PIL to test the result.
You can select your folder , select the align of merge features or test with unittest button.
This is the result and works well:
import os
import unittest
from PIL import Image, ImageDraw, ImageFont, ImageQt
import shutil
from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton, QFileDialog, QLabel, QVBoxLayout, QWidget, QComboBox
from PyQt6.QtGui import QPixmap
from PyQt6.QtCore import Qt
import sys

def create_test_image(path, size, number):
    img = Image.new('RGBA', size, (255, 255, 255, 255))
    draw = ImageDraw.Draw(img)
    # Simplified to just draw the number with a basic color background
    draw.rectangle((0, 0, size[0], size[1]), fill=(0, 100 * number % 255, 0, 255))
    try:
        font = ImageFont.load_default()
    except:
        font = None
    draw.text((size[0]//2-5, size[1]//2-5), str(number), fill=(255, 255, 255, 255), font=font)
    img.save(path, 'PNG')

def merge_sprites(folder_path, output_horizontal, output_vertical):
    images = [Image.open(os.path.join(folder_path, f)) for f in os.listdir(folder_path) if f.endswith(('.png', '.jpg', '.jpeg'))]
    
    if not images:
        return None, None
    
    width, height = images[0].size
    
    # Horizontal merge
    total_width = width * len(images)
    horizontal_image = Image.new('RGBA', (total_width, height))
    for i, img in enumerate(images):
        horizontal_image.paste(img, (i * width, 0))
    horizontal_image.save(output_horizontal, 'PNG')
    
    # Vertical merge
    total_height = height * len(images)
    vertical_image = Image.new('RGBA', (width, total_height))
    for i, img in enumerate(images):
        vertical_image.paste(img, (0, i * height))
    vertical_image.save(output_vertical, 'PNG')
    
    return horizontal_image, vertical_image

class TestSpriteMerger(unittest.TestCase):
    def setUp(self):
        self.test_folder = 'test_images'
        self.size = (50, 20)
        os.makedirs(self.test_folder, exist_ok=True)
        for i in range(3):
            create_test_image(os.path.join(self.test_folder, f'test_{i+1}.png'), self.size, i+1)
    
    def test_merge_horizontal(self):
        output_h = 'test_merged_horizontal.png'
        output_v = 'test_merged_vertical.png'
        h_img, _ = merge_sprites(self.test_folder, output_h, output_v)
        
        self.assertIsNotNone(h_img, "Horizontal merge failed")
        self.assertEqual(h_img.size, (self.size[0] * 3, self.size[1]))
    
    def test_merge_vertical(self):
        output_h = 'test_merged_horizontal.png'
        output_v = 'test_merged_vertical.png'
        _, v_img = merge_sprites(self.test_folder, output_h, output_v)
        
        self.assertIsNotNone(v_img, "Vertical merge failed")
        self.assertEqual(v_img.size, (self.size[0], self.size[1] * 3))
    
    def tearDown(self):
        if os.path.exists(self.test_folder):
            shutil.rmtree(self.test_folder)
        for f in ['test_merged_horizontal.png', 'test_merged_vertical.png']:
            if os.path.exists(f):
                os.remove(f)

class SpriteMergerApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Sprite Merger")
        self.setGeometry(100, 100, 800, 600)
        
        self.folder_path = ""
        
        layout = QVBoxLayout()
        
        self.select_button = QPushButton("Select Folder")
        self.select_button.clicked.connect(self.select_folder)
        layout.addWidget(self.select_button)
        
        self.merge_type = QComboBox()
        self.merge_type.addItems(["Horizontal", "Vertical"])
        layout.addWidget(self.merge_type)
        
        self.process_button = QPushButton("Process Selected Folder")
        self.process_button.clicked.connect(self.process_folder)
        layout.addWidget(self.process_button)
        
        self.test_button = QPushButton("Run Unit Test")
        self.test_button.clicked.connect(self.run_unit_test)
        layout.addWidget(self.test_button)
        
        self.result_label = QLabel("No image processed")
        layout.addWidget(self.result_label)
        
        self.image_label = QLabel()
        layout.addWidget(self.image_label)
        
        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)
    
    def select_folder(self):
        self.folder_path = QFileDialog.getExistingDirectory(self, "Select Sprite Folder")
        self.result_label.setText(f"Selected: {self.folder_path}")
    
    def process_folder(self):
        if not self.folder_path:
            self.result_label.setText("Please select a folder first")
            return
        h_img, v_img = merge_sprites(self.folder_path, 'merged_horizontal.png', 'merged_vertical.png')
        selected_type = self.merge_type.currentText()
        
        img = h_img if selected_type == "Horizontal" else v_img
        if img:
            pixmap = QPixmap.fromImage(ImageQt.ImageQt(img))
            self.image_label.setPixmap(pixmap.scaled(700, 500, aspectRatioMode=Qt.AspectRatioMode.KeepAspectRatio))
            self.result_label.setText(f"{selected_type} merge completed")
        else:
            self.result_label.setText(f"{selected_type} merge failed")
    
    def run_unit_test(self):
        suite = unittest.TestLoader().loadTestsFromTestCase(TestSpriteMerger)
        result = unittest.TextTestRunner().run(suite)
        
        test_folder = 'test_images'
        os.makedirs(test_folder, exist_ok=True)
        for i in range(3):
            create_test_image(os.path.join(test_folder, f'test_{i+1}.png'), (50, 20), i+1)
        
        h_img, v_img = merge_sprites(test_folder, 'test_merged_horizontal.png', 'test_merged_vertical.png')
        selected_type = self.merge_type.currentText()
        
        img = h_img if selected_type == "Horizontal" else v_img
        if img:
            pixmap = QPixmap.fromImage(ImageQt.ImageQt(img))
            self.image_label.setPixmap(pixmap.scaled(700, 500, aspectRatioMode=Qt.AspectRatioMode.KeepAspectRatio))
            self.result_label.setText(f"Unit tests: {result.testsRun} run, {len(result.failures)} failed, showing {selected_type.lower()} merge")
        else:
            self.result_label.setText(f"Unit tests: {result.testsRun} run, {len(result.failures)} failed, merge failed")

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = SpriteMergerApp()
    window.show()
    sys.exit(app.exec())

Sunday, July 6, 2025

Python 3.13.5 : keras and torch and tensorflow on python version 3.13.5 !

I am not very satisfied with the security of the system in this area, I have noticed intrusions when I was at the Vodafone provider and now I am on mobile and they persist. I should improve my software and hardware protection and restart the system, but I have the impression that it won't help much considering the hacking capabilities that exist.
For now, I'm sticking with this solution, because it's quite good for simple tests with the Python language.
Today I will show you that the involvement of developers is somewhat lagging behind, and this can be seen from the analysis of the development progression across versions of the Python language, as it is not easy to implement in newer versions. Some packages, although important, are not available in newer versions.Today I will show you that the involvement of developers is somewhat lagging behind, and this can be seen from the analysis of the development progression across versions of the Python language, as it is not easy to implement in newer versions. Some packages, although important, are not available in newer versions. Let's see these python packages:
Keras is a deep learning API designed for human beings, not machines. Keras focuses on debugging speed, code elegance & conciseness, maintainability, and deployability. When you choose Keras, your codebase is smaller, more readable, easier to iterate on.
... read more on the official website.
pip install --upgrade keras
Collecting keras
  Downloading keras-3.10.0-py3-none-any.whl.metadata (6.0 kB)
...
Successfully installed absl-py-2.3.0 h5py-3.14.0 keras-3.10.0 ml-dtypes-0.5.1 namex-0.1.0 optree-0.16.0
TensorFlow does not support Python 3.13 as of July 2025; it supports Python 3.9–3.11. To use Keras with TensorFlow, downgrade to Python 3.11, and this install will comes with:
pip install tensorflow
ERROR: Could not find a version that satisfies the requirement tensorflow (from versions: none)
ERROR: No matching distribution found for tensorflow
You can use the torch python package:
pip install torch
Collecting torch
...
Successfully installed filelock-3.18.0 fsspec-2025.5.1 mpmath-1.3.0 sympy-1.14.0 torch-2.7.1
... during the test with these packages in running it seems that I have errors. I believe that those who develop Python didn't really have a strategy regarding the protection of installed packages because I've found all sorts of junk packages that can be installed with pip or from GitHub.

Saturday, July 5, 2025

Python 3.13.5 : the groq tested with basic example !

About groq you can find on the official Grok website .
Today I tested the groq A.P.I. with the basic example :
import os

from groq import Groq

client = Groq(
    api_key="gsk_..."
)

chat_completion = client.chat.completions.create(
    messages=[
        {
            "role": "user",
            "content": "explain what groq is",
        }
    ],
    model="llama-3.3-70b-versatile",
)

print(chat_completion.choices[0].message.content)
The result of : "explain what groq is" is that :
Groq is a relatively new and innovative technology company that focuses on developing high-performance, artificial intelligence (AI) computing hardware and software solutions. Founded in 2016 by Jonathan Ross, a former Google engineer, Groq aims to provide faster, more efficient, and scalable AI computing capabilities to various industries. Groq's primary product is a tensor processing unit (TPU) chip designed specifically for machine learning (ML) and deep learning (DL) workloads. This TPU is optimized for high-performance, low-latency, and low-power consumption, making it suitable for applications that require rapid processing of vast amounts of data, such as: 1. **Computer Vision**: Image and video recognition, object detection, and tracking. 2. **Natural Language Processing (NLP)**: Text analysis, sentiment analysis, and language translation. 3. **Speech Recognition**: Voice command processing and speech-to-text applications. Groq's innovative architecture and design philosophy are centered around the following key aspects: 1. **High-Bandwidth Memory**: Groq's TPU features high-bandwidth memory, which enables fast data transfer and processing. 2. **Scalable Architecture**: The TPU is designed to scale horizontally, allowing for easy expansion to meet growing computational demands. 3. **Low-Latency**: Groq's architecture minimizes latency, ensuring fast response times and real-time processing capabilities. 4. **Software-Defined**: The TPU is software-defined, allowing for flexibility and customization to support a wide range of AI applications and frameworks. Groq's technology has the potential to accelerate AI adoption in various industries, including: 1. **Autonomous Vehicles**: Enhanced computer vision and sensor processing for safer and more efficient autonomous driving. 2. **Healthcare**: Faster medical image analysis and diagnosis, as well as improved personalized medicine and treatment planning. 3. **Financial Services**: Enhanced risk analysis, portfolio optimization, and fraud detection using AI-powered systems. While Groq is still a relatively new company, its innovative approach to AI computing has garnered significant attention and interest from the tech industry, investors, and potential customers. As AI continues to transform various aspects of our lives, Groq's technology is poised to play a significant role in shaping the future of artificial intelligence and machine learning.

Wednesday, July 2, 2025

Python Qt6 : ... simple resize image files.

I like the combination of Python development and the inclusion of the PyQt6 module. It is very fast and stable and allows me to create all sorts of tools to use.
Today I will show you another handy script that allows you to read all the image files from a folder and, depending on the selections: height, length, and/or aspect ratio, resize them and then place them in a folder created specifically for the resulting images.
Here is how the script looks, I clearly used artificial intelligence and it didn't take more than a few minutes, my evaluation, testing, and rearranging the interface took longer ...
import sys
import os
from datetime import datetime
from PyQt6.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QPushButton, QFileDialog, QLineEdit, QCheckBox, QLabel, QMessageBox
from PyQt6.QtCore import Qt
from PIL import Image

class ResizeApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Image Resizer")
        self.setGeometry(100, 100, 400, 200)

        layout = QVBoxLayout()
        central_widget = QWidget()
        central_widget.setLayout(layout)
        self.setCentralWidget(central_widget)

        self.folder_button = QPushButton("Select Folder")
        self.folder_button.clicked.connect(self.select_folder)
        layout.addWidget(self.folder_button)

        self.width_edit = QLineEdit("800")
        self.width_edit.setPlaceholderText("Width (px)")
        layout.addWidget(QLabel("Width:"))
        layout.addWidget(self.width_edit)

        self.height_edit = QLineEdit("600")
        self.height_edit.setPlaceholderText("Height (px)")
        layout.addWidget(QLabel("Height:"))
        layout.addWidget(self.height_edit)

        self.aspect_ratio = QCheckBox("Maintain Aspect Ratio")
        self.aspect_ratio.setChecked(True)
        layout.addWidget(self.aspect_ratio)

        self.resize_button = QPushButton("Resize Images")
        self.resize_button.clicked.connect(self.resize_images)
        layout.addWidget(self.resize_button)

        self.folder_path = ""

    def select_folder(self):
        self.folder_path = QFileDialog.getExistingDirectory(self, "Select Image Folder")
        if self.folder_path:
            self.folder_button.setText(f"Selected: {os.path.basename(self.folder_path)}")

    def resize_images(self):
        if not self.folder_path:
            QMessageBox.warning(self, "Error", "Please select a folder.")
            return

        try:
            width = int(self.width_edit.text())
            height = int(self.height_edit.text())
        except ValueError:
            QMessageBox.warning(self, "Error", "Please enter valid width and height.")
            return

        if width <= 0 or height <= 0:
            QMessageBox.warning(self, "Error", "Width and height must be positive.")
            return

        date_str = datetime.now().strftime("%d%m%y_%H%M")
        aspect_str = "asp_on" if self.aspect_ratio.isChecked() else "asp_off"
        output_folder = os.path.join(self.folder_path, f"resized_{date_str}_{height}_{aspect_str}")
        os.makedirs(output_folder, exist_ok=True)

        for file_name in os.listdir(self.folder_path):
            if file_name.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.gif')):
                image_path = os.path.join(self.folder_path, file_name)
                try:
                    with Image.open(image_path) as img:
                        if self.aspect_ratio.isChecked():
                            img.thumbnail((width, height), Image.Resampling.LANCZOS)
                        else:
                            img = img.resize((width, height), Image.Resampling.LANCZOS)
                        output_path = os.path.join(output_folder, f"resized_{date_str}_{height}_{aspect_str}_{file_name}")
                        img.save(output_path)
                except Exception as e:
                    QMessageBox.warning(self, "Error", f"Failed to process {file_name}: {str(e)}")

        QMessageBox.information(self, "Success", f"Images resized and saved to {output_folder}!")

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = ResizeApp()
    window.show()
    sys.exit(app.exec())

Tuesday, July 1, 2025

Python 3.13.5 : use the jupyterlab, notebook and voila - part 001.

JupyterLab is the latest web-based interactive development environment for notebooks, code, and data. Its flexible interface allows users to configure and arrange workflows in data science, scientific computing, computational journalism, and machine learning. A modular design invites extensions to expand and enrich functionality.
Let's test with these python modules: jupyterlab, notebook and voila.
First, I install with pip tool the jupyterlab python module:
pip install jupyterlab
Collecting jupyterlab
...
Successfully installed anyio-4.9.0 argon2-cffi-25.1.0 argon2-cffi-bindings-21.2.0 arrow-1.3.0 asttokens-3.0.0 async-lru-2.0.5
attrs-25.3.0 babel-2.17.0 bleach-6.2.0 cffi-1.17.1 comm-0.2.2 debugpy-1.8.14 defusedxml-0.7.1 executing-2.2.0 
fastjsonschema-2.21.1 fqdn-1.5.1 h11-0.16.0 httpcore-1.0.9 httpx-0.28.1 ipykernel-6.29.5 ipython-9.4.0 
ipython-pygments-lexers-1.1.1 isoduration-20.11.0 jedi-0.19.2 json5-0.12.0 jsonpointer-3.0.0 jsonschema-4.24.0 
jsonschema-specifications-2025.4.1 jupyter-client-8.6.3 jupyter-core-5.8.1 jupyter-events-0.12.0 jupyter-lsp-2.2.5 
jupyter-server-2.16.0 jupyter-server-terminals-0.5.3 jupyterlab-4.4.4 jupyterlab-pygments-0.3.0 jupyterlab-server-2.27.3 
matplotlib-inline-0.1.7 mistune-3.1.3 nbclient-0.10.2 nbconvert-7.16.6 nbformat-5.10.4 nest-asyncio-1.6.0 notebook-shim-0.2.4
overrides-7.7.0 pandocfilters-1.5.1 parso-0.8.4 platformdirs-4.3.8 prometheus-client-0.22.1 prompt_toolkit-3.0.51 
psutil-7.0.0 pure-eval-0.2.3 pycparser-2.22 python-json-logger-3.3.0 pywin32-310 pywinpty-2.0.15 pyyaml-6.0.2 pyzmq-27.0.0
referencing-0.36.2 rfc3339-validator-0.1.4 rfc3986-validator-0.1.1 rpds-py-0.26.0 send2trash-1.8.3 sniffio-1.3.1 
stack_data-0.6.3 terminado-0.18.1 tinycss2-1.4.0 tornado-6.5.1 traitlets-5.14.3 types-python-dateutil-2.9.0.20250516 
uri-template-1.3.0 wcwidth-0.2.13 webcolors-24.11.1 webencodings-0.5.1 websocket-client-1.8.0
Next, the install of the notebook python module
pip install notebook
Collecting notebook
...
Installing collected packages: notebook
Successfully installed notebook-7.4.4
This python package named voila will help us to use online graphic user interfaces:
pip install voila
Collecting voila
...
Successfully installed voila-0.5.8 websockets-15.0.1
The voila package need to work with ipywidgets python package:
pip install ipywidgets
Collecting ipywidgets
...
Successfully installed ipywidgets-8.1.7 jupyterlab_widgets-3.0.15 widgetsnbextension-4.0.14
Let's start the jupiter tool with this command:
jupyter notebook
I used an default python example with a slider:
import ipywidgets as widgets
from IPython.display import display

slider = widgets.IntSlider(value=5, min=0, max=10)
display(slider)
This command will start the web with the slider using the voila command:
voila test.ipynb
The result is this:

Friday, June 27, 2025

Python 3.13.5 : the manim python module - part 001.

I used this package few days ago and now I wrote about how can used it.
Update the new release of pip is available: 25.0.1 -> 25.1.1 , run this command:
python.exe -m pip install --upgrade pip
The documentation can be found on the official website.
pip install manim
Collecting manim
  Downloading manim-0.19.0-py3-none-any.whl.metadata (11 kB)
...
Successfully installed Pillow-11.2.1 Pygments-2.19.1 audioop-lts-0.2.1 av-13.1.0 beautifulsoup4-4.13.4 click-8.2.1 cloup-3.0.7 colorama-0.4.6 decorator-5.2.1 glcontext-3.0.0 isosurfaces-0.1.2 manim-0.19.0 manimpango-0.6.0 mapbox-earcut-1.0.3 markdown-it-py-3.0.0 mdurl-0.1.2 moderngl-5.12.0 moderngl-window-3.1.1 networkx-3.5 numpy-2.3.0 pycairo-1.28.0 pydub-0.25.1 pyglet-2.1.6 pyglm-2.8.2 rich-14.0.0 scipy-1.15.3 screeninfo-0.8.1 skia-pathops-0.8.0.post2 soupsieve-2.7 srt-3.5.3 svgelements-1.9.6 tqdm-4.67.1 typing-extensions-4.14.0 watchdog-6.0.0
Let's see how can be used to see the help area:
python.exe -m manim render --help
Manim Community v0.19.0

Usage: python -m manim render ...
Let's use this source code:
from manim import *

class AdvancedAnimation(Scene):
    def construct(self):
        # Scene 1: Introduction
        title = Text("Advanced Animation with Manim").scale(0.76)
        self.play(FadeIn(title))
        self.wait(2)

        # Scene 2: Custom Animation
        circle = Circle().set_fill(color=BLUE, opacity=0.5)
        square = Square().set_fill(color=RED, opacity=0.5)
        self.add(circle, square)
        self.play(
            Rotate(circle, angle=TAU),
            Rotate(square, angle=-TAU),
            run_time=2,
            rate_func=linear
        )
        self.wait()

        # Scene 3: Text Animation
        text = Text("This is a custom text animation", font_size=40).to_edge(UP)
        self.play(Write(text), run_time=2)
        self.wait()

        # Scene 4: Shapes Manipulation
        triangle = Triangle().shift(RIGHT * 2)
        self.play(GrowFromCenter(triangle), run_time=1.5)
        self.wait()

        # Scene 5: Transition to next scene
        self.play(Uncreate(triangle), FadeOut(text))

        # Scene 6: Final Animation
        final_text = Text("This is the end of our animation", font_size=50).to_edge(DOWN)
        self.play(FadeIn(final_text), run_time=1.5)
        self.wait(2)

# Run the animation
AdvancedAnimation()
Use this command to render:
python.exe -m manim render manim_test_001.py AdvancedAnimation -p
AdvancedAnimation -p
Manim Community v0.19.0

[06/27/25 19:52:43] INFO     Animation 0 : Partial movie file      scene_file_writer.py:588
                             written in
                             'D:\PythonProjects\manim_projects\med
                             ia\videos\manim_test_001\1080p60\part
                             ial_movie_files\AdvancedAnimation\397
                             7891868_355746014_223132457.mp4'
...
[06/27/25 19:53:56] INFO     Previewed File at:                             file_ops.py:237
                             'D:\PythonProjects\manim_projects\media\videos
                             \manim_test_001\1080p60\AdvancedAnimation.mp4'
The result comes with many files, see this 1080p60 video result:

Python Qt6 : ... simple processing file.

Today I will show you an simple python script with PyQt6.
This script ai build using the artificial inteligence and I tested will process your files by slections : copy to another folder , zip all selected and delete all selected.
import sys
import os
import re
import shutil
import zipfile
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, 
                            QPushButton, QListWidget, QFileDialog, QMessageBox, QDialog, QLabel)
from PyQt6.QtCore import Qt

class CriteriaDialog(QDialog):
    def __init__(self, folder, parent=None):
        super().__init__(parent)
        self.setWindowTitle(f"Processing Folder: {folder}")
        self.layout = QVBoxLayout(self)
        self.layout.addWidget(QLabel(f"Selected folder: {folder}"))
        self.ok_button = QPushButton("OK")
        self.ok_button.clicked.connect(self.accept)
        self.layout.addWidget(self.ok_button)

class DuplicateFinder(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Duplicate File Finder")
        self.setGeometry(100, 100, 600, 400)
        
        self.central_widget = QWidget()
        self.setCentralWidget(self.central_widget)
        self.layout = QVBoxLayout(self.central_widget)
        
        self.file_list = QListWidget()
        self.file_list.setSelectionMode(QListWidget.SelectionMode.MultiSelection)
        self.layout.addWidget(self.file_list)
        
        button_layout = QHBoxLayout()
        self.select_button = QPushButton("Select Folder")
        self.select_button.clicked.connect(self.select_folder)
        button_layout.addWidget(self.select_button)
        
        self.copy_button = QPushButton("Copy To")
        self.copy_button.clicked.connect(self.copy_files)
        button_layout.addWidget(self.copy_button)
        
        self.zip_button = QPushButton("Zip All")
        self.zip_button.clicked.connect(self.zip_files)
        button_layout.addWidget(self.zip_button)
        
        self.delete_button = QPushButton("Delete All")
        self.delete_button.clicked.connect(self.delete_files)
        button_layout.addWidget(self.delete_button)
        
        self.layout.addLayout(button_layout)
        
        self.files = []
        self.selected_folder = ""
        
    def select_folder(self):
        folder = QFileDialog.getExistingDirectory(self, "Select Folder")
        if folder:
            self.selected_folder = folder
            dialog = CriteriaDialog(folder, self)
            if dialog.exec():
                self.files = []
                self.file_list.clear()
                self.scan_folder(folder)
                self.find_duplicates()
            
    def scan_folder(self, folder):
        for root, _, files in os.walk(folder):
            for file in files:
                file_path = os.path.join(root, file)
                self.files.append({
                    'path': file_path,
                    'name': os.path.basename(file_path),
                    'size': os.path.getsize(file_path),
                    'extension': os.path.splitext(file_path)[1]
                })
                
    def find_duplicates(self):
        duplicates = self.find_by_similar_name()
        self.display_duplicates(duplicates)
        
    def find_by_similar_name(self):
        duplicates = []
        name_groups = {}
        pattern = r'(.+?)(?:[\s_-]*\d{3,}|[\s_-]*\d{1,2}|_)?(?:\.\w+)?$'
        
        for file in self.files:
            match = re.match(pattern, file['name'])
            if match:
                base_name = match.group(1)
                if base_name in name_groups:
                    name_groups[base_name].append(file)
                else:
                    name_groups[base_name] = [file]
        
        for base_name, files in name_groups.items():
            if len(files) > 1:
                duplicates.extend(files)
                
        return duplicates
    
    def display_duplicates(self, duplicates):
        self.file_list.clear()
        for file in duplicates:
            self.file_list.addItem(file['path'])
            
    def copy_files(self):
        if not self.file_list.selectedItems():
            QMessageBox.warning(self, "Warning", "No files selected!")
            return
        dest_folder = QFileDialog.getExistingDirectory(self, "Select Destination Folder")
        if dest_folder:
            for item in self.file_list.selectedItems():
                file_path = item.text()
                dest_path = os.path.join(dest_folder, os.path.basename(file_path))
                shutil.copy2(file_path, dest_path)
            QMessageBox.information(self, "Success", "Files copied!")
            
    def zip_files(self):
        if not self.file_list.selectedItems():
            QMessageBox.warning(self, "Warning", "No files selected!")
            return
        zip_path = QFileDialog.getSaveFileName(self, "Save Zip File", "", "Zip Files (*.zip)")[0]
        if zip_path:
            with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
                for item in self.file_list.selectedItems():
                    file_path = item.text()
                    zipf.write(file_path, os.path.basename(file_path))
            QMessageBox.information(self, "Success", "Files zipped!")
        
    def delete_files(self):
        if not self.file_list.selectedItems():
            QMessageBox.warning(self, "Warning", "No files selected!")
            return
        reply = QMessageBox.question(self, "Confirm", "Delete selected files?",
                                  QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
        if reply == QMessageBox.StandardButton.Yes:
            for item in self.file_list.selectedItems():
                os.remove(item.text())
            self.file_list.clear()
            QMessageBox.information(self, "Success", "Files deleted!")

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = DuplicateFinder()
    window.show()
    sys.exit(app.exec())