RLE (Run-Length Encoding) - to metoda kodowania strumienia informacji, czyli traktujemy wszystkie dane, które trafiają do naszej funkcji jako jeden pojedynczy wektor danych. Jeżeli dostarczone do funkcji dane posiadają więcej niż 1 wymiar, to po uprzednim ich zapisaniu dane należy spłaszczyć .flatten().

Sam algorytm kompresji RLE opiera się na redukcji powtarzalności symboli (bitów, bajtów, symboli, pikseli itd.). Ciągi powtarzalnych symboli zastępujemy parą licznik_powtórzeń, symbol. Jeżeli symbol się nie powtarza zastępuje się go parą 1 symbol, czyli wystąpił on 1 raz. Przykładowo ciąg [1,1,1,1,1,2,2,2,3,4,5,6,6,6,6,1] zostałby zakodowany jako [5,1,3,2,1,3,1,4,1,5,4,6,1,1].

Uprzedzając pytania: tak w skrajnie niesprzyjającym przypadku łańcuch skompresowany może być dwa razy dłuższy niż wejściowy. Żeby program działał szybciej, proponuję, żeby zamiast dodawać kolejne elementy do listy, założyć niekorzystny buffor i jego wypełniać. A na końcu przyciąć go tylko do finalnej długości.

np.zeros(np.prod(data.shape)*2)

Drobna sugestia — owszem pakiet numpy ma funkcję pozwalającą znaleźć następny inny element, ale przeszukuje ona cały zakres danych, co znacząco wydłuża jej działanie, dlatego wygodniej w do tego wykorzystać zwykłą pętlę for z break. Jeżeli implementujecie również ByteRun, to warto zaimplementować to jako funkcję pomocniczą, bo tam również się przyda.