Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions oop1/backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# backend.py
# Adapter Pattern for plotting backends

from __future__ import annotations
from abc import ABC, abstractmethod
from typing import Any, Optional, Sequence
import matplotlib.pyplot as plt


class PlotBackend(ABC):
"""
UMAP ve diğer görseller için soyut backend arayüzü.
Adapter Pattern burada uygulanıyor.
Strategy katmanı backend hakkında bilgi sahibi değil.
"""

@abstractmethod
def init_canvas(self, width: float, height: float, dpi: int) -> None:
...

@abstractmethod
def draw_umap(
self,
embedding,
labels: Optional[Sequence] = None,
point_size: float = 5.0,
alpha: float = 0.8,
cmap: str = "viridis",
color_labels: bool = True,
) -> None:
...

@abstractmethod
def save(self, path: str) -> None:
...

@abstractmethod
def show(self) -> None:
...

@abstractmethod
def get_figure(self) -> Any:
...

@abstractmethod
def get_axes(self) -> Any:
...


class MatplotlibBackend(PlotBackend):
"""
Matplotlib için adapter backend.
VisualizationService → Strategy → Backend hiyerarşisi
sayesinde her şey OOP uyumlu çalışır.
PlotlyBackend
BokehBackend katmanları buraya eklenebilir.
"""

def __init__(self) -> None:
self._fig: Any = None
self._ax: Any = None

def init_canvas(self, width: float, height: float, dpi: int) -> None:
self._fig, self._ax = plt.subplots(figsize=(width, height), dpi=dpi)

def draw_umap(
self,
embedding,
labels: Optional[Sequence] = None,
point_size: float = 5.0,
alpha: float = 0.8,
cmap: str = "viridis",
color_labels: bool = True,
) -> None:
if self._ax is None:
self.init_canvas(8, 6, 120)

x = embedding[:, 0]
y = embedding[:, 1]

if labels is None:
sc = self._ax.scatter(x, y, s=point_size, alpha=alpha, cmap=cmap)
else:
sc = self._ax.scatter(
x,
y,
c=labels if color_labels else None,
s=point_size,
alpha=alpha,
cmap=cmap if color_labels else None,
)

self._ax.set_xlabel("UMAP-1")
self._ax.set_ylabel("UMAP-2")
self._ax.set_title("UMAP Projection")

if labels is not None and color_labels:
self._fig.colorbar(sc, ax=self._ax, label="labels")

def save(self, path: str) -> None:
if self._fig is not None:
self._fig.savefig(path, bbox_inches="tight")

def show(self) -> None:
if self._fig is not None:
plt.show()

def get_figure(self) -> Any:
return self._fig

def get_axes(self) -> Any:
return self._ax
112 changes: 112 additions & 0 deletions oop1/core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# core.py
# Veri akışı ve konfigürasyonun standartlaştırılması

from __future__ import annotations

from dataclasses import dataclass, field, asdict
from typing import Any, Optional, Sequence, Dict, List


@dataclass
class VizParams:
"""
UMAP + plot + kayıt + canvas ayarlarını tutan config sınıfı.
Strategy, backend, service hep bu obje üzerinden konuşur.
"""

# ---------- UMAP ayarları ----------
n_neighbors: int = 15
min_dist: float = 0.1
metric: str = "euclidean"
random_state: Optional[int] = 42
n_components: int = 2 # genelde 2D UMAP

# ---------- Canvas / figür boyutları ----------
width: float = 8.0 # inches (matplotlib)
height: float = 6.0
dpi: int = 120

# ---------- Nokta / scatter ayarları ----------
point_size: float = 5.0
alpha: float = 0.8

# ---------- Renk / etiket ayarları ----------
cmap: str = "viridis" # Eğer labels sayısal ise
color_labels: bool = True # Kategorik/etiket bazlı renklendirme
label_name: Optional[str] = None # Örn: "cluster", "louvain" vs.

# ---------- Kayıt / output ayarları ----------
save_dir: str = "figures"
save_name: str = "umap"
save_format: str = "png"
save_enabled: bool = True # SaveStrategy kullanırken işine yarar

# ---------- Eksen / başlık ----------
title: Optional[str] = "UMAP Projection"
x_label: str = "UMAP-1"
y_label: str = "UMAP-2"
show_axis: bool = True

def update(self, **kwargs: Any) -> None:
"""
****
UMAP parametrelerini sonradan değiştirme imkanı.
Parametreleri runtime'da değiştirmek için:
ctx.params.update(n_neighbors=30, min_dist=0.05, title="Deneme UMAP")
Yanlış isim verilirse AttributeError fırlatır ki hatayı erken görelim.
"""
for key, value in kwargs.items():
if not hasattr(self, key):
raise AttributeError(f"VizParams has no attribute '{key}'")
setattr(self, key, value)


@dataclass
class VizContext:
"""
Farklı strategy'ler arasında ortak kullanılacak context yapısı.
Data, parametreler, embedding, backend ve hataları burada tutar.
"""
# Girdi tarafı
data: Any
labels: Optional[Sequence] = None
params: VizParams = field(default_factory=VizParams)

# Hesaplama sonrası doldurulacak
embedding: Optional[Any] = None

# Backend tarafının dolduracağı alanlar
figure: Any = None
ax: Any = None
backend: Any = None # Örn: MatplotlibBackend instance

# Hata ve ek artefakt yönetimi
errors: List[str] = field(default_factory=list)
artifacts: Dict[str, Any] = field(default_factory=dict)

def add_error(self, msg: str) -> None:
"""Context içinde hata mesajı biriktir."""
self.errors.append(msg)

@property
def has_errors(self) -> bool:
return len(self.errors) > 0

def attach_backend(self, backend: Any) -> None:
"""Backend instance'ını context'e kaydet.
service backend'i context ile ilişkilendirir.****
"""
self.backend = backend

def to_dict(self) -> Dict[str, Any]:
"""
Debug / logging için context özetini sözlük formatında döner.
AnnData veya büyük objeleri komple dump etmiyoruz, sadece boyut / key bilgisi.
"""
return {
"params": asdict(self.params),
"has_errors": self.has_errors,
"errors": list(self.errors),
"embedding_shape": getattr(self.embedding, "shape", None),
"artifacts": list(self.artifacts.keys()),
}
Binary file added oop1/figures/umap_pbmc3k_leiden.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added oop1/pbmc3k_raw.h5ad
Binary file not shown.
71 changes: 71 additions & 0 deletions oop1/save_strategy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# save_strategy.py
from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Optional
import os

from core import VizContext
from backend import PlotBackend


class SaveStrategy(ABC):
"""
Kaydetme davranışını soyutlayan Strategy.
Kaydetme politikası ayrı bir Strategy
İster hiç kaydetme, ister otomatik dosya adı üret, ister özel path kullan.
"""

@abstractmethod
def save(
self,
ctx: VizContext,
backend: PlotBackend,
save_path: Optional[str] = None,
) -> None:
...


class NoSaveStrategy(SaveStrategy):
"""Hiçbir şey kaydetmez, sadece figürü context'te bırakır."""

def save(
self,
ctx: VizContext,
backend: PlotBackend,
save_path: Optional[str] = None,
) -> None:
# Sadece figür referansını artifacts içine atalım
ctx.artifacts["fig"] = backend.get_figure()


class AutoPathSaveStrategy(SaveStrategy):
"""
Kaydetme için otomatik path üretir:
- Eğer save_path verilmişse onu kullanır
- Verilmemişse ctx.params içindeki save_dir, save_name, save_format üzerinden üretir
"""

def save(
self,
ctx: VizContext,
backend: PlotBackend,
save_path: Optional[str] = None,
) -> None:
params = ctx.params

# Kullanıcı kaydetmek istemiyorsa çık
if not params.save_enabled:
return

# Dışarıdan özel path verilmişse onu kullan
if save_path is None:
os.makedirs(params.save_dir, exist_ok=True)
filename = f"{params.save_name}.{params.save_format}"
save_path = os.path.join(params.save_dir, filename)

try:
backend.save(save_path)
ctx.artifacts["fig_path"] = save_path
except Exception as e:
ctx.add_error(f"Save error: {e}")
Loading