Code effizienter und ressourcenschonender gestalten

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
G-Rizzle
User
Beiträge: 90
Registriert: Donnerstag 18. Februar 2021, 12:26

Hi,

ich nutze Google Colab, um Objekte auf Bildern zu erkennen und die Lokalisierungsinformationen daraus zu verwerten. Die Ortsinformationen erhalte ich als 2D-Boolsche Maske im Format 1024 x 1024. Diese Maske vergrößere ich darauf. Ergibt gerade beim Lesen ohne Kontext wahrscheinlich wenig Sinn, eine wirkliche Erläuterung wäre aber unnötig viel.
Die besagte Funktion ist in unten stehendem Code implementiert und funktioniert - hier benötige ich keine Hilfe.
Leider scheint der Code aber sehr ineffizient zu sein. In Colab crasht die Session, wenn 25 GB Ram aufgebraucht sind, was hier schon mal passiert, wenn er bereits vorbelastet ist. Kann mir jemand sagen, wie ich das ganze effizienter gestalten kann?

Beste Grüße

Code: Alles auswählen


# function to resize a defect mask 
def resize_mask(_xyz_resized_contour_coordinates_x, _xyz_resized_contour_coordinates_y, _dimension_x, _dimension_y):

  # create a 2D array of zeros with dimensions of x and y of the xyz-file. this will be filled with the resized mask
  _xyz_resized_mask = np.zeros(((_dimension_y), (_dimension_x))) 

  # fill the empty, resized mask with 1's with the resized contour (still with holes (empty rows etc. ) through the resizing)
  _xyz_resized_mask[_xyz_resized_contour_coordinates_y, _xyz_resized_contour_coordinates_x] = 1

  # loop to fill the resized mask with 1
  # We're iterating from the first row where the contour begins to the last row
  # In every loop pass we check, if we have an empty row by counting the non-zeros in the row.
  # If we don't have an empty row, we extract the index of the minimal and maximal column in this row that is occupied with a 1. This is the new contour.
  # If we have an empty row, we use the contour coordinates of the prior row.
  # After this check we set all values in the xyz_resized_mask array between the just determined contours to 1
  # The result is a fully filled resized mask according to the xyz-file dimensions.
  _min_row, _max_row = min(_xyz_resized_contour_coordinates_y), max(_xyz_resized_contour_coordinates_x)
  _row_delta = _max_row - _min_row
  _row_list = [_min_row + i for i in range((_row_delta + 1))]  
  
  for _row_index in _row_list:    
    _non_zeros_in_this_row = np.count_nonzero(_xyz_resized_mask[_row_index][:])   

    if _non_zeros_in_this_row > 0:
      _non_zero_cols = (np.where(_xyz_resized_mask[_row_index][:] == 1))[0]
      _min_non_zero_col = min(_non_zero_cols)
      _max_non_zero_col = max(_non_zero_cols)
    else:
      pass

    _xyz_resized_mask[_row_index][_min_non_zero_col : _max_non_zero_col + 1] = 1   

  return _xyz_resized_mask






# create a list to store xyz_resized_mask in it
list_of_xyz_resized_masks = []

# loop over each defect by its id
for id in results['defect_ids']:
  percentual_progress = round(100 * (id / (len(results['defect_ids']))), 2)
  print("{} %".format(percentual_progress))

  # call the function from above and append its return to the list_of_xyz_resized_masks
  list_of_xyz_resized_masks.append(resize_mask(_percentual_contour_coordinates_x = results['percentual_contour_coordinates_x'][id], 
                                               _percentual_contour_coordinates_y = results['percentual_contour_coordinates_y'][id], 
                                               _dimension_x = xyz_dimension_x, 
                                               _dimension_y = xyz_dimension_y
                                               )
  
# add the list_of_xyz_resized_masks to the results-dict
results['xyz_resized_masks'] = list_of_xyz_resized_masks
Sirius3
User
Beiträge: 17712
Registriert: Sonntag 21. Oktober 2012, 17:20

Das ist ziemlich gruseliger Code. Eingerückt wird immer mit 4 Leerzeichen pro Ebene, nicht 2.
Die führenden Unterstriche bei allen Variablennamen müssen weg.
Dass es sich um xyz_resized-Masken oder -Koordinaten handelt, ist für das Verständnis der Funktion unerheblich, es wird also lesbarer, wenn man es wegläßt.
Die Maske ist ein bool-Array, Du benutzt also den falschen Typ.
max_row ist nicht die maximale Zeile, sondern die maximale Spalte.
`range` kennt einen Start- und einen End-Wert, die Erzeugung von `row_list` ist daher unnötig.
Wenn Du nur wissen willst, ob eine Zahl ungleich 0 ist, ist count_nonzero unnötig komliziert, da reicht ein `any`. Da Du aber danach sowieso nochmal die ganze Reihe durchgehst, um die Indizes zu suchen, ist dieser Schritt überflüssig.
`where` sucht Zahlen != 0, die Maske hat aber nur 0 und 1, der Vergleich mit 1 ist daher unnötig.
Ein ›else: pass‹ macht nichts, kann also weg.

Code: Alles auswählen

def resize_mask(contour_coordinates_x, contour_coordinates_y, dimension_x, dimension_y):
    mask = np.zeros((dimension_y, dimension_x), dtype=bool)
    mask[contour_coordinates_y, contour_coordinates_x] = True

    min_row, max_row = min(contour_coordinates_y), max(contour_coordinates_y)
    for index in range(min_row, max_row + 1):
        non_zeros = np.where(mask[index])
        if non_zeros.size:
            min_col = non_zeros.min()
            max_col = non_zeros.max()
        mask[index][min_col : max_col + 1] = True
  return mask
Schleifen sind in mit numpy relativ unperformant.
Diese Variante gefällt mir noch nicht ganz, weil doch zum Schluß eine Schleife gebraucht wird, ist aber besser, was das Füllen der Kontur betrifft:

Code: Alles auswählen

def resize_mask(contour_coordinates_x, contour_coordinates_y, dimension_x, dimension_y):
    mask = np.zeros((dimension_y, dimension_x), dtype=bool)
    mask[contour_coordinates_y, contour_coordinates_x] = True
    mask2 = mask[:, ::-1].cumsum(axis=1, dtype=bool)[:, ::-1]
    mask.cumsum(axis=1, dtype=bool, out=mask)
    mask |= mask2
    rows = mask2[:, 0].cumsum(dtype=bool)
    rows |= mask2[::-1, 0].cumsum(dtype=bool)[::-1]
    rows ^= mask2[:, 0]
    for index in numpy.where(rows)[0]:
        mask[index] = mask[index - 1]
    return mask
Um Konturen zu füllen, gibt es aber deutlich bessere Algorithmen. Wenn Du aber Arrays hast, die Du in der Größe verändern willst, dann sind Funktionen aus scipy.ndimage besser.
Da Du aber nicht verrätst, was Du eigentlich machen willst, kann man da nicht weiterhelfen.

Je nachdem, wieviele defect_ids Du hast, kann das natürlich recht viele Masken werden.
Da Du nicht verrätst, was Du mit der Liste list_of_xyz_resized_masks machen willst, kann man da aber nicht weiterhelfen.
Antworten