OCA.DEV
[ 03 // ARCHITECTURE ]

How these systems are put together Bu sistemler nasıl kuruluyor

The layers each pipeline is made of, the code at the points that mattered, and the reason behind each choice. Code below is taken from the repositories unchanged, comments included. Her pipeline'ın hangi katmanlardan oluştuğu, önemli noktalardaki kod, ve her tercihin sebebi. Aşağıdaki kod repolardan olduğu gibi alındı, yorumları dâhil.

7 PIPELINE LAYERS
2,827 LINES IN src/perception
40.7 s 38.3M-ROW GRAPH RUN
otonomarac // LAYER FLOW
3 PARALLEL BRANCHES 1 FUSION POINT
The otonomarac layer flow: one video frame fans out to detection, depth and segmentation, merges at a fusion layer, and splits again into bird's-eye projection and risk before being rendered Video frame Detection YOLO • COCO classes Depth Depth Anything V2 Segmentation drivable area • lanes Tracking ByteTrack • stable IDs Fusion layer (id, class, position, depth) BEV projection homography • ground plane Risk closing speed • TTC Rendered output video
Why three branches Detection, depth and segmentation do not depend on each other, so they read the same frame independently and only meet at fusion. Tespit, derinlik ve segmentasyon birbirine bağlı değil; aynı kareyi bağımsız okuyup yalnızca füzyonda buluşuyorlar.
The critical link Speed and time-to-collision are both derived from track history, so tracker ID stability is the single point everything downstream depends on. Hız da çarpışmaya kalan süre de takip geçmişinden çıkıyor; bu yüzden takip ID kararlılığı, aşağı akıştaki her şeyin bağlı olduğu tek nokta.
No custom training Pre-trained weights throughout. The work is in the geometry, the fusion and the measurement, not in a new model. Baştan sona önceden eğitilmiş ağırlıklar. İş yeni bir modelde değil; geometride, füzyonda ve ölçümde.
[ BREAKDOWN // THE THREE DECISIONS THAT MATTERED ]

Three points where the obvious choice was the wrong one Bariz tercihin yanlış olduğu üç nokta

STAGE 01 / src/perception/depth.py

Median, not mean Ortalama değil, medyan

Depth Anything gives a value per pixel, but an object needs one number. A bounding box always contains some background pixels — sky through a windscreen, road under a bumper — and averaging lets a handful of them drag the estimate. The median does not move. The sample is taken from the lower-centre of the box, which is the part most likely to be the object itself. Depth Anything piksel başına bir değer veriyor ama bir nesne için tek sayı gerekiyor. Sınırlayıcı kutunun içinde her zaman arka plan pikselleri bulunur — camdan görünen gökyüzü, tamponun altındaki yol — ve ortalama alınca birkaç tanesi tahmini sürükler. Medyan kıpırdamaz. Örnek, kutunun alt-orta bölgesinden alınıyor; orası nesnenin kendisi olma ihtimali en yüksek kısım.

RELATIVE DEPTH NUMPY MEDIAN LOWER-CENTRE PATCH
def fuse(detections, disparity_map, config, camera=None):
    """Her tespite derinlik degeri yazar (yerinde degistirir).

    Ornek bolgesindeki degerlerin **medyani** alinir, ortalamasi degil:
    kutu icinde arka plana ait birkac piksel her zaman bulunur ve ortalama
    bunlardan etkilenir, medyan etkilenmez.
    """
    shape = disparity_map.shape[:2]

    for det in detections:
        region = sample_region(det, shape, config, camera)
        if region is None:
            det.depth = None
            continue

        x1, y1, x2, y2 = region
        patch = disparity_map[y1:y2, x1:x2]
        if patch.size == 0:
            det.depth = None
            continue

        det.depth = relative_distance(float(np.median(patch)))

    return detections
STAGE 02 / src/perception/bev.py

The car's own bonnet is not road Aracın kendi kaputu yol değildir

The projection takes the bottom edge of a box as the point where the object touches the ground, then maps it through a homography. But the bonnet fills the lower band of every dashcam frame, so a contact point that lands there is describing bodywork, not road — and the homography would happily turn it into a confident position on the map. Those points are dropped instead. İzdüşüm, kutunun alt kenarını nesnenin zemine değdiği nokta sayıp homografiyle haritaya taşıyor. Ama kaput her dashcam karesinin alt bandını dolduruyor; oraya düşen bir temas noktası yolu değil kaportayı tarif ediyor — ve homografi bunu memnuniyetle haritada kendinden emin bir konuma çevirirdi. Bu noktalar onun yerine düşürülüyor.

cv2.perspectiveTransform HOOD GUARD GROUND CONTACT POINT
def project(self, detections, shape):
    """Her tespitin zemine degme noktasini haritaya tasir.

    Kaputun altina dusen temas noktalari gecersiz sayilir: o piksel yolu
    degil aracin kendi kaputunu gosterir, dolayisiyla homografi oradan
    anlamli bir zemin konumu uretemez.
    """
    height = shape[0]
    hood_row = height * self.camera.hood_top \
        if self.camera.hood_top is not None else height

    usable, contacts = [], []
    for det in detections:
        cx, cy = det.bottom_center
        if cy > hood_row:
            det.bev_xy = None
            continue
        usable.append(det)
        contacts.append((cx, cy))

    ground = self.to_ground(np.array(contacts), shape)
STAGE 03 / src/perception/risk.py

A slope is not a speed until the fit is good Uyum iyi olmadan eğim bir hız değildir

Closing speed comes from fitting a line to the distance history, and time-to-collision follows from distance over that speed. The trap is that a line can be fitted to anything. If the residual is large the distance is not changing smoothly, which means the slope is measuring tracker jitter rather than motion — so the estimate is refused rather than reported. Objects moving away or standing still get no TTC at all, because there is none. Yaklaşma hızı, mesafe geçmişine bir doğru uydurmaktan geliyor; çarpışmaya kalan süre de mesafenin bu hıza bölümü. Tuzak şu: her şeye bir doğru uydurulabilir. Artık büyükse mesafe düzgün değişmiyordur, yani eğim hareketi değil takip titremesini ölçüyordur — bu yüzden tahmin raporlanmak yerine reddediliyor. Uzaklaşan ya da duran nesnelere hiç TTC verilmiyor, çünkü yok.

FIT-QUALITY GATE np.polyfit SLIDING WINDOW
# Yalnizca son `history_seconds` icindeki gozlemler. Daha eskisi,
# nesnenin o zamandan beri hizlanmis olabilecegi icin yaniltir.
window = [(t, d) for t, d in history if now - t <= self.config.history_seconds]

# Mesafenin zamana gore egimi. Negatif egim = mesafe azaliyor = yaklasiyor.
coeffs = np.polyfit(times, dists, 1)
slope = float(coeffs[0])
closing = -slope

# Fit kalitesi kapisi: artik buyukse mesafe duzgun degismiyor demektir
# ve egimden cikarilan "hiz" gercek hareket degil gurultudur.
residual = float(np.sqrt(np.mean((dists - np.polyval(coeffs, times)) ** 2)))
if residual > self.config.max_fit_residual_ratio * max(distance, 1.0):
    return Motion(track_id, distance, None, None, RiskLevel.NONE, len(window))

if closing < self.config.min_closing_speed:
    # Uzaklasiyor ya da duragan: TTC tanimsiz, risk yok.
    return Motion(track_id, distance, closing, None, RiskLevel.NONE, len(window))

ttc = distance / closing
[ trafikisaret // WHY TWO STAGES ]

The data decided the architecture Mimariye veri karar verdi

One detector that predicts 23 classes would have to learn each one from an average of 31 boxes, and the thinnest class has 3. Collapsing every sign into a single class means all 717 boxes train the detector at once, and telling the classes apart becomes a separate crop-classification problem where each box is a training example in its own right. 23 sınıf tahmin eden tek bir dedektör, her sınıfı ortalama 31 kutudan öğrenmek zorunda kalırdı; en ince sınıfta 3 kutu var. Bütün levhaları tek sınıfta toplamak, 717 kutunun tamamının dedektörü aynı anda eğitmesi demek; sınıfları ayırmak da her kutunun başlı başına bir eğitim örneği olduğu ayrı bir kırpma sınıflandırma problemine kalıyor.

Labelled boxes717
Labels used23
Average per class31
Thinnest class3
Supported in classifier14

Classes below the threshold were not swept into an "other" bucket. Grouping visually unrelated signs under one label teaches the classifier a contradiction; if a class is unsupported, the repository says so. Eşiğin altında kalan sınıflar bir "diğer" kutusuna doldurulmadı. Görsel olarak alakasız levhaları tek etiket altında toplamak sınıflandırıcıya çelişki öğretir; bir sınıf desteklenmiyorsa repo bunu yazar.

The two-stage recogniser: a class-agnostic detector finds boxes, then a classifier labels each crop and answers with a question mark when unsure Frame Stage 1 — class-agnostic detector all 717 boxes train one class Stage 2 — crop classifier 14 classes • 404 training crops LABEL + CONFIDENCE " ? " WHEN UNSURE TEST SET: 0.953 RECALL OVER 64 CROPS FROM 52 DISTINCT PHYSICAL SIGNS
[ PRODUCT BACKENDS // LAYERS AND ISOLATION ]

The same discipline, applied to a backend Aynı disiplin, backend'de

restaurant

respos — multi-tenant isolation

resposapp.com ↗

One backend serves many restaurants. Every request carries a tenant, every query is scoped to it, and a restaurant can sign itself up without an operator touching anything. Module and package access is granted per restaurant, so two tenants on the same deployment can see different features. Tek backend birçok restorana hizmet veriyor. Her istek bir tenant taşıyor, her sorgu ona göre kapsamlanıyor ve bir restoran hiçbir operatör müdahalesi olmadan kendi kaydını açabiliyor. Modül ve paket erişimi restoran bazında veriliyor; aynı kurulumdaki iki tenant farklı özellikler görebiliyor.

Request path
routesHTTP surface, validation, auth guard
servicesbusiness rules, tenant scoping
repositoriesdata access, one tenant at a time
JWT auth Shift & cash management Recipe-based stock Discount audit log
smart_display

Shorties — one architecture, three apps

YKS shipped first and is live on both stores. KPSS and ALES are in development on the same backend, mobile client and video engine, each with its own question bank. The layering is what makes the second and third app cheap: nothing exam-specific lives below the business layer. Önce YKS çıktı ve her iki mağazada da yayında. KPSS ve ALES aynı backend, mobil istemci ve video motoru üzerinde, her biri kendi soru bankasıyla geliştiriliyor. İkinci ve üçüncü uygulamayı ucuza mal eden şey katmanlama: iş katmanının altında sınava özgü hiçbir şey yok.

.NET Clean Architecture
APIcontrollers, DTOs
Businessexam-agnostic rules
Coreentities, shared contracts
DataAccessMSSQL, repositories
React Native (Expo) Python video engine Web admin panel shorties.tr ↗
[ smart-city // TWO ENGINES, ONE PIPELINE ]

Laptop scale and cluster scale, same code path Dizüstü ölçeği ve küme ölçeği, aynı kod yolu

Mode Engine Data size Where it runs
Local demo Pandas + NetworkX < 10M rows Laptop / PC
Cloud production PySpark + GraphFrames 10M+ rows AWS EMR / Google Dataproc
38,310,226 raw records
97.63% survived cleaning
258 / 9,990 nodes / edges
40.7 s run, t3.xlarge
[ MEASUREMENT MATRIX // WHAT THE NUMBERS CHANGED ]

Findings that changed a decision Bir kararı değiştiren bulgular

Project Assumed Measured What changed
trafikisaret Bigger inference resolution is better recall 0.379 @640 0.724 @960 0.448 @1280 Inference runs at 960 px. There is an optimum, and 1280 measuring worse is what proves it.
trafikisaret Leakage inflates a score mAP50 0.694 dirty 0.726 clean Leakage made the number meaningless, not higher. Locked behind a test; the dirty run was kept, not deleted.
plakatanima ±35° horizontal yaw ~7° over 690 labelled plates The generator was recalibrated against the labels instead of the plan.
plakatanima Sharpness does not predict night readability 1% 62% across the full range The first sample only covered the flat end of a sorted queue. The conclusion was withdrawn.
smart-city Busiest zone is the critical one JFK removal: 1 → 16 components Volume and structural role are different questions. Betweenness catches what PageRank ranks 17th.

The full record Tam kayıt

Education, work experience, project experience and skills on one page. Eğitim, iş deneyimi, proje deneyimi ve yetkinlikler tek sayfada.

OPEN THE CVCV'Yİ AÇ arrow_forward