Określanie i zapisywanie figury o dokładnym rozmiarze w pikselach

152

Powiedzmy, że mam obraz o rozmiarze 3841 x 7195 pikseli. Chciałbym zapisać zawartość figury na dysku, w wyniku czego otrzymuję obraz o dokładnym rozmiarze, który określę w pikselach.

Bez osi, bez tytułów. Tylko obraz. Osobiście nie obchodzą mnie DPI, ponieważ chcę tylko określić rozmiar obrazu na ekranie w pikselach .

Czytałem inne wątki i wydaje się, że wszystkie one przeliczają na cale, a następnie określają wymiary figury w calach i dostosowują w jakiś sposób dpi. Chciałbym uniknąć potencjalnej utraty dokładności, która może wynikać z konwersji pikseli na cale.

Próbowałem z:

w = 7195
h = 3841
fig = plt.figure(frameon=False)
fig.set_size_inches(w,h)
ax = plt.Axes(fig, [0., 0., 1., 1.])
ax.set_axis_off()
fig.add_axes(ax)
ax.imshow(im_np, aspect='normal')
fig.savefig(some_path, dpi=1)

bez powodzenia (Python narzeka, że ​​szerokość i wysokość muszą być mniejsze niż 32768 (?))

Ze wszystkiego, co widziałem, matplotlibwymaga określenia rozmiaru figury w inchesi dpi, ale interesują mnie tylko piksele, które figura zajmuje na dysku. W jaki sposób mogę to zrobić?

Wyjaśnienie: szukam sposobu, aby to zrobić matplotlibz innymi bibliotekami do zapisywania obrazów, a nie z nimi.

Amelio Vazquez-Reina
źródło
W przypadku matplotlib nie można ustawić rozmiaru figury bezpośrednio w calach.
tiago

Odpowiedzi:

175

Matplotlib nie działa bezpośrednio z pikselami, ale raczej z fizycznymi rozmiarami i DPI. Jeśli chcesz wyświetlić figurę o określonym rozmiarze piksela, musisz znać DPI swojego monitora. Na przykład ten link wykryje to za Ciebie.

Jeśli masz obraz o wymiarach 3841x7195 pikseli, jest mało prawdopodobne, że monitor będzie tak duży, więc nie będziesz w stanie wyświetlić figury tego rozmiaru (matplotlib wymaga, aby figura zmieściła się na ekranie, jeśli poprosisz o rozmiar za duży, zmniejszy się do rozmiaru ekranu). Wyobraźmy sobie, że jako przykład potrzebujesz obrazu 800x800 pikseli. Oto jak wyświetlić obraz o rozdzielczości 800x800 pikseli na moim monitorze ( my_dpi=96):

plt.figure(figsize=(800/my_dpi, 800/my_dpi), dpi=my_dpi)

Więc po prostu dzielisz wymiary w calach przez swoje DPI.

Jeśli chcesz zapisać figurę o określonym rozmiarze, to inna sprawa. DPI ekranu nie są już tak ważne (chyba że poprosisz o figurę, która nie zmieści się na ekranie). Korzystając z tego samego przykładu piksela 800x800, możemy zapisać go w różnych rozdzielczościach, używając dpisłowa kluczowego z savefig. Aby zapisać go w tej samej rozdzielczości, co ekran, użyj tej samej rozdzielczości:

plt.savefig('my_fig.png', dpi=my_dpi)

Aby zapisać go jako obraz o rozdzielczości 8000x8000 pikseli, użyj rozdzielczości 10 razy większej:

plt.savefig('my_fig.png', dpi=my_dpi * 10)

Zwróć uwagę, że ustawienie DPI nie jest obsługiwane przez wszystkie backendy. Tutaj używany jest backend PNG, ale backend pdf i ps będą implementować rozmiar inaczej. Ponadto zmiana DPI i rozmiarów wpłynie również na takie rzeczy, jak rozmiar czcionki. Większy DPI zachowa te same względne rozmiary czcionek i elementów, ale jeśli chcesz mieć mniejsze czcionki dla większej figury, musisz zwiększyć rozmiar fizyczny zamiast DPI.

Wracając do przykładu, jeśli chcesz zapisać obraz o rozmiarze 3841 x 7195 pikseli, możesz wykonać następujące czynności:

plt.figure(figsize=(3.841, 7.195), dpi=100)
( your code ...)
plt.savefig('myfig.png', dpi=1000)

Zauważ, że użyłem wartości 100 dpi, aby zmieścić się na większości ekranów, ale zapisałem z, dpi=1000aby uzyskać wymaganą rozdzielczość. W moim systemie daje to png o rozdzielczości 3840x7190 pikseli - wydaje się, że zapisane DPI jest zawsze o 0,02 piksela / cal mniejsze od wybranej wartości, co będzie miało (mały) wpływ na duże rozmiary obrazu. Więcej dyskusji na ten temat tutaj .

tiago
źródło
5
Warto pamiętać, że rozmiary monitorów (a tym samym standardowe rozmiary okien przeglądarki i okna interfejsu użytkownika) są zwykle określane jako 96 dpi - wielokrotności 96. Nagle liczby takie jak 1440 pikseli mają znaczenie (15 cali).
Danny Staple
Nie udało się zmusić tego do przejścia figsizedo plt.figure. Rozwiązaniem było zrobienie tego, co sugerują inne odpowiedzi i po wywołaniu go bez figsize , a następnie zadzwońfig.set_size_inches(w,h)
Trinidad
Dokumenty dla figurei savefig.
uchwyt
Łącze nie pokazuje prawidłowej wartości dla Apple Thunderbolt Display.
Dmitry,
Podoba mi się to rozwiązanie, ale mam jedno ostrzeżenie. Rozmiar tekstu skaluje się odwrotnie jako dpi. (Mój system to MacBook Pro, OS X), więc w przypadku drukowania interaktywnego ustawienie dużego dpi (np. 10 * my_dpi) zmniejsza tekst do prawie niewidoczności.
Prof Huster,
16

To zadziałało dla mnie, w oparciu o twój kod, generując obraz PNG o rozmiarze 93 MB z szumem kolorów i pożądanymi wymiarami:

import matplotlib.pyplot as plt
import numpy

w = 7195
h = 3841

im_np = numpy.random.rand(h, w)

fig = plt.figure(frameon=False)
fig.set_size_inches(w,h)
ax = plt.Axes(fig, [0., 0., 1., 1.])
ax.set_axis_off()
fig.add_axes(ax)
ax.imshow(im_np, aspect='normal')
fig.savefig('figure.png', dpi=1)

Używam ostatnich wersji PIP bibliotek Python 2.7 w Linux Mint 13.

Mam nadzieję, że to pomoże!

heltonbiker
źródło
4
Ustawienie bardzo niskiej rozdzielczości oznacza, że ​​czcionki będą prawie niewidoczne, chyba że wyraźnie zostaną użyte bardzo duże rozmiary czcionek.
tiago
1
Prawdopodobnie lepiej jest ustawić wyższą rozdzielczość i podzielić rozmiar w calach (który i tak jest arbitralny) przez tę rozdzielczość. Poza tym twoja konfiguracja zapewnia dokładny piksel do reprodukcji pikseli, dzięki!
FrenchKheldar
Próbuję tego użyć przed zapisaniem obrazów z elementami fabuły (okręgi, linie, ...). Narusza to szerokość linii, przez co elementy są ledwo widoczne.
Gauthier
5

Na podstawie zaakceptowanej odpowiedzi tiago, oto mała ogólna funkcja, która eksportuje tablicę numpy do obrazu o tej samej rozdzielczości co tablica:

import matplotlib.pyplot as plt
import numpy as np

def export_figure_matplotlib(arr, f_name, dpi=200, resize_fact=1, plt_show=False):
    """
    Export array as figure in original resolution
    :param arr: array of image to save in original resolution
    :param f_name: name of file where to save figure
    :param resize_fact: resize facter wrt shape of arr, in (0, np.infty)
    :param dpi: dpi of your screen
    :param plt_show: show plot or not
    """
    fig = plt.figure(frameon=False)
    fig.set_size_inches(arr.shape[1]/dpi, arr.shape[0]/dpi)
    ax = plt.Axes(fig, [0., 0., 1., 1.])
    ax.set_axis_off()
    fig.add_axes(ax)
    ax.imshow(arr)
    plt.savefig(f_name, dpi=(dpi * resize_fact))
    if plt_show:
        plt.show()
    else:
        plt.close()

Jak wspomniano w poprzedniej odpowiedzi tiago, najpierw należy znaleźć DPI ekranu, co można zrobić na przykład tutaj: http://dpi.lv

Dodałem dodatkowy argument resize_factw funkcji, dzięki któremu można np. Wyeksportować obraz do 50% (0,5) oryginalnej rozdzielczości.

Cyryla
źródło
2

OP chce zachować dane piksela 1: 1. Jako astronom pracujący z obrazami naukowymi nie mogę pozwolić na jakąkolwiek interpolację danych obrazu, ponieważ wprowadzałaby ona nieznane i nieprzewidywalne szumy lub błędy. Na przykład, oto fragment z obrazu 480x480 zapisanego przez pyplot.savefig (): Szczegóły pikseli, które matplotlib ponownie próbkowały do ​​rozmiaru mniej więcej 2x2, ale zwróć uwagę na kolumnę 1x2 pikseli

Widać, że większość pikseli została po prostu podwojona (więc piksel 1x1 zmienia się na 2x2), ale niektóre kolumny i wiersze zmieniły się na 1x2 lub 2x1 na piksel, co oznacza, że ​​oryginalne dane naukowe zostały zmienione.

Jak zasugerował Alka, plt.imsave (), który osiągnie to, o co prosi PO. Powiedzmy, że masz dane obrazu przechowywane w image array im, wtedy można zrobić coś takiego

plt.imsave(fname='my_image.png', arr=im, cmap='gray_r', format='png')

gdzie nazwa pliku ma rozszerzenie „png” w tym przykładzie (ale i tak musisz określić format za pomocą format = 'png', o ile wiem), tablica obrazu to arr, a my wybraliśmy odwróconą skalę szarości „gray_r” jako mapa kolorów. Zwykle dodaję vmin i vmax, aby określić zakres dynamiczny, ale są one opcjonalne.

Końcowym wynikiem jest plik png o dokładnie takich samych wymiarach w pikselach jak tablica im.

Uwaga: OP nie określił żadnych osi itp., Co dokładnie robi to rozwiązanie. Jeśli ktoś chce dodać osie, znaczniki, itp., Moim preferowanym podejściem jest zrobienie tego na oddzielnym wykresie, zapisanie z przezroczystym = True (PNG lub PDF), a następnie nałożenie tego ostatniego na obraz. Gwarantuje to, że oryginalne piksele pozostały nienaruszone.

Colin Orion Chandler
źródło
Dzięki. Minęło kilka lat, odkąd zadałem to pytanie, ale z tego, co pamiętam i widzę w Twojej odpowiedzi, działałoby to dobrze, gdy próbujesz zapisać tablicę danych jako obraz, ale co, jeśli chcesz zapisać, to rzeczywisty figura (niezależnie od jej zawartości) i nadal dokładnie kontroluje wymiary w pikselach wynikowego pliku obrazu?
Amelio Vazquez-Reina
-1

plt.imsave pracował dla mnie. Dokumentację można znaleźć tutaj: https://matplotlib.org/3.2.1/api/_as_gen/matplotlib.pyplot.imsave.html

#file_path = directory address where the image will be stored along with file name and extension
#array = variable where the image is stored. I think for the original post this variable is im_np
plt.imsave(file_path, array)
Alka
źródło
Dodaj przykładowy kod pokazujący dokładnie, który parametr ustawiasz i zalecane wartości dla przypadku użycia oryginalnego posta.
Sarah Messer