F: Estudio de isofotas

Una isofota se define como:

«Una línea que une puntos con el mismo brillo superficial en un diagrama o una imagen de un objeto celeste como una galaxia o una nebulosa.»

El brillo superficial \(\mu\) (mu) generalmente se mide en magnitudes por segundo de arco al cuadrado. La suma de toda la luz dentro de una isofota dada se denomina magnitud isofotal.

Si el perfil luminoso de una galaxia es elíptico, sus isofotas serán elipses.

El paquete isophote proporciona herramientas para ajustar isófotas elípticas a una imagen de una galaxia. Las isófotas de la imagen se miden usando un método iterativo descrito por Jedrzejewski (1987; MNRAS 226, 747).

Para este ejemplo, vamos a crear una imagen de galaxia simulada simple:

[1]:
from astropy.modeling.models import Gaussian2D
import numpy as np
from photutils.datasets import make_noise_image
import matplotlib.pyplot as plt
[2]:
g = Gaussian2D(100., 75, 75, 20, 12, theta=40. * np.pi / 180.) # generamos una gausiana eliptica en 2d girada 40 grados
ny = nx = 150 #cantidad de pixeles en x e y
y, x = np.mgrid[0:ny, 0:nx]
noise = make_noise_image((ny, nx), distribution='gaussian', mean=0., # metemos algo de ruido
                         stddev=2., seed=1234)
data = g(x, y) + noise
[3]:
plt.figure("Galaxia", figsize=[10, 10])
plt.imshow(data, origin='lower')
plt.show()
_images/F-4-isofotas_6_0.png

Debemos proporcionar una elipse al ajuste de las isófotas como condición inicial. Esta geometría de la elipse se define con la clase EllipseGeometry. Aquí definiremos una elipse inicial cuyo ángulo de posición está desplazado respecto los datos.

[4]:
from photutils.isophote import EllipseGeometry
geometry = EllipseGeometry(x0=75, y0=75, sma=20, eps=0.5,
                           pa=20. * np.pi / 180.)

Vamos a mostrar esta elipse inicial:

[5]:
from photutils.aperture import EllipticalAperture
aper = EllipticalAperture((geometry.x0, geometry.y0), geometry.sma,
                          geometry.sma * (1 - geometry.eps),
                          geometry.pa)
plt.figure("Galaxia", figsize=[10, 10])
plt.imshow(data, origin='lower')
aper.plot(color='white')
plt.show()
_images/F-4-isofotas_10_0.png

A continuación, creamos una instancia de la clase Ellipse, donde tendremos en cuenta los datos que se ajustarán, es decir la imagen de la galaxia y la elipse inicial:

[6]:
from photutils.isophote import Ellipse
ellipse = Ellipse(data, geometry)

Para realizar el ajuste de isófotas elípticas, ejecutamos el método fit_image() :

[7]:
isolist = ellipse.fit_image()

El resultado es una lista de isófotas, cuyos atributos son los valores del ajuste para cada una de las isofotas ordenadas por la longitud del semieje mayor. Veamos los ángulos de posición de ajuste (en radianes):

[8]:
isolist.pa
[8]:
array([0.00000000e+00, 2.58557691e-02, 1.24258032e-02, 2.81651474e-03,
       3.10564077e+00, 3.01432668e+00, 2.91207413e+00, 2.55353790e+00,
       3.27667134e-01, 3.12991751e+00, 3.12991751e+00, 1.60073797e-02,
       3.13170982e+00, 8.28190557e-02, 1.04356317e-01, 1.58706115e-01,
       3.20421940e-01, 3.82976829e-01, 4.85453395e-01, 7.06300855e-01,
       1.14601352e+00, 1.00547579e+00, 9.73052854e-01, 6.24088491e-01,
       5.79963706e-01, 6.24163539e-01, 7.36384503e-01, 7.64785040e-01,
       6.98341037e-01, 6.77667042e-01, 6.81952942e-01, 6.27394019e-01,
       6.64901758e-01, 6.91061656e-01, 6.96059953e-01, 6.93651837e-01,
       6.86072223e-01, 6.78834001e-01, 6.91164283e-01, 7.06300855e-01,
       6.87382951e-01, 6.78429447e-01, 6.91862441e-01, 6.88541138e-01,
       6.89746530e-01, 7.04616954e-01, 6.98198654e-01, 7.02949641e-01,
       6.87174406e-01, 6.87174406e-01, 6.87174406e-01, 7.36770379e-01,
       7.36770379e-01, 7.36770379e-01])

También podemos mostrar los valores de las isófotas como una tabla, que nuevamente se ordena por la longitud del semieje mayor (sma):

[9]:
print(isolist.to_table())
       sma               intens            intens_err      ... niter stop_code
                                                           ...
------------------ ------------------ -------------------- ... ----- ---------
               0.0  103.3660741932861                  0.0 ...     0         0
0.5346972612827552 101.85768930454661 0.030160181107668268 ...    10         0
0.5881669874110307 101.68151143393658 0.028309622593685645 ...    10         0
0.6469836861521338   101.505670787118 0.025866912481304023 ...    11         0
0.7116820547673471   101.372109520222  0.03225393307834451 ...    10         0
0.7828502602440819 101.18631230115908   0.0361583334561795 ...    10         0
0.8611352862684901 100.96908841886025  0.03258843615160212 ...    10         0
0.9472488148953392 100.78891350007841 0.049768537796437626 ...    10         0
1.0419736963848731 100.42647733875044  0.08087063557978552 ...    19         0
1.1461710660233606  100.2813456048825  0.08525970834625504 ...    50         2
               ...                ...                  ... ...   ...       ...
 32.21020000000001  27.26171456357163  0.10957850079687152 ...    10         0
 35.43122000000001 20.802860058616794  0.09299435657992926 ...    10         0
38.974342000000014 15.038704467533424  0.09788537700939103 ...    10         0
 42.87177620000002  9.957840971954045  0.09606873371025618 ...    10         0
 47.15895382000003  6.094966160355057  0.08299661880443569 ...    10         0
51.874849202000036 3.4153177276271887  0.07834803763518963 ...    10         0
 57.06233412220004 1.5518907342094141  0.08731774884195193 ...    50         2
 62.76856753442005 0.7074698044512818  0.07602993679767285 ...    50         2
 69.04542428786206  0.250073095540471  0.06955238583559703 ...     3         5
 75.94996671664828 0.1403359017804461  0.07032595343951609 ...     2         5
Length = 54 rows
[10]:
fig, (ax1) = plt.subplots(figsize=(14, 5), nrows=1, ncols=1)
fig.subplots_adjust(left=0.04, right=0.98, bottom=0.02, top=0.98)
ax1.imshow(data, origin='lower')
ax1.set_title('Ajuste de isofotas')

smas = np.linspace(10, 50, 10) # definimos ciertos valores de sma
for sma in smas:
    iso = isolist.get_closest(sma) # vemos las isofotas que estan mas cerca de los sma definidas
    x, y, = iso.sampled_coordinates()
    ax1.plot(x, y, color='white')
_images/F-4-isofotas_19_0.png

Vemos varios parámetros de del ajuste de las isofotas

[11]:
plt.figure(figsize=(8, 8))
plt.subplots_adjust(hspace=0.35, wspace=0.35)

plt.subplot(2, 2, 1)
plt.errorbar(isolist.sma, isolist.eps, yerr=isolist.ellip_err,
             fmt='o', markersize=4)
plt.xlabel('Semimajor Axis Length (pix)')
plt.ylabel('Ellipticity')

plt.subplot(2, 2, 2)
plt.errorbar(isolist.sma, isolist.pa / np.pi * 180.,
             yerr=isolist.pa_err / np.pi * 80., fmt='o', markersize=4)
plt.xlabel('Semimajor Axis Length (pix)')
plt.ylabel('PA (deg)')

plt.subplot(2, 2, 3)
plt.errorbar(isolist.sma, isolist.x0, yerr=isolist.x0_err, fmt='o',
             markersize=4)
plt.xlabel('Semimajor Axis Length (pix)')
plt.ylabel('x0')

plt.subplot(2, 2, 4)
plt.errorbar(isolist.sma, isolist.y0, yerr=isolist.y0_err, fmt='o',
             markersize=4)
plt.xlabel('Semimajor Axis Length (pix)')
plt.ylabel('y0')
plt.show()
_images/F-4-isofotas_21_0.png

Podemos construir una imagen modelo a partir del objeto IsophoteList usando la funcion build_ellipse_model():

[12]:
from photutils.isophote import build_ellipse_model
model_image = build_ellipse_model(data.shape, isolist)

Finalmente, graficamos los datos originales, superpuestos con algunas de las isófotas, la imagen del modelo elíptico y la imagen residual:

[13]:
residual = data - model_image

fig, (ax1, ax2, ax3) = plt.subplots(figsize=(14, 5), nrows=1, ncols=3)
fig.subplots_adjust(left=0.04, right=0.98, bottom=0.02, top=0.98)
ax1.imshow(data, origin='lower')
ax1.set_title('Data')

smas = np.linspace(10, 50, 10)
for sma in smas:
    iso = isolist.get_closest(sma)
    x, y, = iso.sampled_coordinates()
    ax1.plot(x, y, color='white')

ax2.imshow(model_image, origin='lower')
ax2.set_title('Ellipse Model')

ax3.imshow(residual, origin='lower')
ax3.set_title('Residual')
[13]:
Text(0.5, 1.0, 'Residual')
_images/F-4-isofotas_25_1.png

Ejemplo de isofotas M51

Usaremos un ejemplo real de la galaxia M51. Tenemos que tener en cuenta que M51, con sus brazos espirales y regiones HII, no es la mejor galaxia para el análisis por el algoritmo de “ellipse”, ya que asume que las isófotas son principalmente de forma elíptica. Por otro lado, la imagen M51 es ideal para comprobar la eficiencia del algoritmo frente a la contaminación de la imagen por características no elípticas.

Vamos a leer nuestra imagen desde una URL

[14]:
from astropy.io import fits
from astropy.utils.data import download_file

url = 'https://github.com/astropy/photutils-datasets/raw/main/data/isophote/M51.fits'
path = download_file(url)
hdu = fits.open(path)
data = hdu[0].data
hdu.close()
[15]:
plt.figure("Galaxia M51", figsize=[10, 10])
plt.imshow(data,vmin=np.nanmin(data), vmax=np.median(data)*5, origin='lower')
plt.show()
_images/F-4-isofotas_30_0.png

A continuación, creamos una instancia de la clase Ellipse, pasando los datos como argumento:

[16]:
ellipse = Ellipse(data)

Finalmente, ejecutamos el método fit_image en la instancia, obteniendo una instancia de la clase IsophoteList.

[17]:
isolist = ellipse.fit_image()

Podemos mostrar los resultados como una tabla de Astropy usando el método to_table().

[18]:
isolist.to_table()
[18]:
QTable length=47
smaintensintens_errellipticityellipticity_errpapa_errgradgrad_errorgrad_rerrorx0x0_erry0y0_errndatanflagniterstop_code
degdeg
float64float64float64float64float64float64float64float64objectobjectfloat64float64float64float64int64int64int64int64
0.07599.7172947880010.00.00.00.00.00.0NoneNone256.882443790887860.0257.99150094432280.01000
0.52098684819243666872.36264232826421.759911917841850.04047304189194520.088597094039887151.3240222444863466.14496620393004-1607.599732582393626.8911588309930.3899547543616324256.882443790887860.024018209546389384257.99150094432280.023881020813287347130100
0.57308553301168036787.759664396774523.7038840180224430.04047304189194520.08574768843530917136.8840718225664.01365105815-1642.7600830264585602.96186542850340.3670419507136223256.87781198266950.025500892238101532257.980714623135670.025483584830134194130100
0.63039408631284836706.65556464604326.526086540719050.054347983380435360.08748180134503622144.110713843299448.983291412197225-1615.3563873874775611.04558669324940.37827292569257487256.85802273832150.02893807518019972257.99040045573980.02871278596271351130100
0.69343349494413326605.61802677266426.908235761019330.054347983380435360.08124398019449601140.176493342686445.49103347014007-1602.5161263897564564.69254464550680.3523786970666434256.85028471738970.0295133842778252257.98034150677490.02938049471548253130100
0.76277684443854656505.06772585666327.8626519052410340.064600260360920870.07877923782755739133.8656537304675337.306665449351435-1540.1465430721012544.13140103973840.3532984594792972256.83821902775350.03156392775638872257.97780455138510.03160890435462693130100
0.83905452888240126393.1688069216830.6046257433792160.069979156858320850.07235907908055511139.5625705713097831.720133322170252-1661.5619182739772495.47215173407480.2981966222774105256.82136526199320.0321141945219361257.9778712751450.03190232650159229130100
0.92295998177064136242.94257801430232.388178979204680.042763572339181550.06440816256955988119.8740152587359845.56179916955126-1820.2584478908482485.943641538616530.2669640907870442256.805274701304140.030779175618311422257.98868982619220.03098009487824803130502
1.01525597994770546119.22753564516830.2748911917910280.083033974841020430.049734829408131766119.8740152587359818.499909306372594-1952.862047804062400.87238233293140.20527429614585477256.805274701304140.026439072577602776257.98868982619220.027193740397464614130100
......................................................
16.105100000000004913.82526842137638.36738280089090.32236127016347270.03090531876909783552.428778287386643.305254822656207-32.262910616203217.58327830358280150.23504631661392314258.622361096578460.29789723846133903259.831290638848260.32007927557063287830502
17.715610000000005888.26077867861028.3394641947782070.3596998539043670.01875565972244291250.5794044618016441.8330817401435053-46.939059485382046.6635499077823170.14196172613679037258.92248598049980.20666631427934495259.545579534579360.22001355798309463880100
19.487171000000007755.51258509040437.5454024238636140.177236692594211430.01811116622557941835.2155209129181663.2211625027943067-50.8954935574771455.6252895873156190.11052628030736913257.90327412011590.20037282161727493259.47128207670710.190949970067073431110100
21.43588810000001684.37786265053887.56109142251852350.213610206942380030.0226831588496870335.2741590930068943.4360376986958823-35.2550061864589645.3527915683787210.15183068016123807257.228517404650740.2848900224927187258.995019778622240.269095187012275471200100
23.579476910000015668.44069570287959.525254865624690.28470255286883830.01882707663065095738.5278028272960362.224987409359727-44.3033378307398145.005512254782250.11298273448167108256.568250478615250.27406804553753245258.339443261415060.25956035288501671250100
25.937424601000018528.47658347094176.4944376161533090.229519004840608970.01489907712627500441.009504611691292.112212019410934-37.242773751528473.53666334754713360.09496240454974132257.1921377547990.22541269690902216257.43750448042860.219548615099747771430100
28.53116706110002536.48306243732167.3712838763244260.30655941800702590.01555061668060271351.355537152465351.7323117393135654-33.09643450054693.41141607487359130.10307503289568004258.455235058156630.263857034708821259.81501926554470.27958816013789541490180
31.384283767210025366.234588974457555.9163868798892950.075521134444910380.02790065586735910559.57523770307792411.026986850311804-17.468346142581482.48453860590829740.14223090071772135258.08152840216730.44992529550773663259.32264360068460.4624678575066861900502
34.52271214393103311.409379129196055.0793001425245870.075521134444910380.0382337316393739759.57523770307792415.110911321169587-9.8321051403225022.17123432898801740.220831073101889258.08152840216730.6782132353774158259.32264360068460.6971190424113989209055
37.97498335832414277.495721873665735.5098254775982490.075521134444910380.0556423092386363459.57523770307792422.00018224667125-6.2724858043567070.95888058844453530.1528709061052831258.08152840216731.0860366881464074259.32264360068461.116083568802935230035

Verificamos el tipo de resultados, debe ser una instancia de la clase IsophoteList:

[19]:
type(isolist)
[19]:
photutils.isophote.isophote.IsophoteList

Mostramos los resultados:

[20]:
plt.figure("Galaxia M51", figsize=[10, 10])
plt.imshow(data,vmin=np.nanmin(data), vmax=np.median(data)*5, origin='lower')
smas = np.linspace(10, 100, 10)
for sma in smas:
    iso = isolist.get_closest(sma)
    x, y, = iso.sampled_coordinates()
    plt.plot(x, y, color='white')
plt.show()
_images/F-4-isofotas_40_0.png

Ejecutando Ellipse de una manera más detallada:

Podemos ajustar elipses individuales, simplemente llamando al método fit_isophote en la misma instancia de Ellipse (pasando la longitud del semieje mayor al método):

[21]:
isophote = ellipse.fit_isophote(sma=20.)

isophote.to_table()
[21]:
QTable length=1
smaintensintens_errellipticityellipticity_errpapa_errgradgrad_errorgrad_rerrorx0x0_erry0y0_errndatanflagniterstop_code
degdeg
float64float64float64float64float64float64float64float64float64float64float64float64float64float64int64int64int64int64
20.0734.4221334628857.1407778198966030.185897825901346350.01773157212154350234.041746586188363.037588928039621-47.239140151319985.4406050133932480.11517155045509912257.67593319513440.2035554607358991259.400382301920960.192757728900753741140130

Tenemos que tener en cuenta que en este caso obtenemos una instancia de la clase Isophote, no IsophoteList como antes:

[22]:
type(isophote)
[22]:
photutils.isophote.isophote.Isophote

El algoritmo de ajuste es bastante sensible a las condiciones iniciales de la posición x e y del centro de la galaxia en la imagen. Cuando se usan valores predeterminados como en los ejemplos anteriores, los métodos asumen que la galaxia está exactamente centrada en la imagen, pero esto puede no ser así.

El algoritmo de ajuste también puede fallar en la convergencia si la elipticidad o el ángulo de posición (PA) del semieje mayor están demasiado alejados de los valores reales. Para anular los valores predeterminados, podemos inicializar Ellipse con una instancia de EllipeGeometry.

[23]:
# el usuario define aquí los parámetros de geometría que se utilizarán como primera suposición.
x0 = 256.    # posicion central
y0 = 256.    # posicion central
sma = 20.    # longitud del semieje mayor en pixeles
eps = 0.2    # elipticidad

#el ángulo de posición se define en radianes,
#en sentido antihorario desde el eje +X (girando hacia el eje +Y)
#aquí usamos 35 grados como una primera suposición.

pa = 35. / 180. * np.pi

# tenga en cuenta que EllipseGeometry tiene parámetros adicionales
#con valores predeterminados. Consulte la documentación para obtener más información.
g = EllipseGeometry(x0, y0, sma, eps, pa)

# la geometría personalizada se pasa al constructor Ellipse.
ellipse = Ellipse(data, geometry=g)

# el ajuste procede como de costumbre.
isophote_list = ellipse.fit_image()

isophote_list.to_table()
[23]:
QTable length=47
smaintensintens_errellipticityellipticity_errpapa_errgradgrad_errorgrad_rerrorx0x0_erry0y0_errndatanflagniterstop_code
degdeg
float64float64float64float64float64float64float64float64objectobjectfloat64float64float64float64int64int64int64int64
0.07596.4795350750310.00.00.00.00.00.0NoneNone256.87754219210390.0257.992934820771270.01000
0.53469726128275526849.00016053469123.869894043265590.034669895532328710.09256580703288413144.9489071333560480.43485553460222-1654.0356760084585649.40956609788110.3926212569157185256.87754219210390.025620961180617796257.992934820771270.025581734732417157130100
0.58816698741103076772.55110010699727.1758312815600430.045616690851727410.09642353430251077146.87298523978464.03784333221734-1624.8414287781466673.20776022341970.4143221291013367256.864802337910650.029607840762342977257.993584860762270.02941432525172341130110
0.64698368615213386677.10006461513725.1140923539918970.052748049691976590.08286778614747728134.509272344778247.766746521594335-1576.7214078942113579.57928479951450.36758509264713485256.86226245133280.02799450020404713257.97854452981250.028005768478022972130100
0.71168205476734716581.74621478073527.1226459798923720.059998608251289030.08177539003771511135.0687521486172841.59641637749309-1555.1107124551086566.1579146533660.36406277065608555256.84566900136860.03051449084987157257.97593216305340.030512216222008767130100
0.78285026024408196461.43733266941227.455001600090080.038130431689203570.07291748421344356160.3030292492031257.71201577377653-1587.2995831879064515.66325957970240.32486826371115957256.81790473482240.029672434534054257.9806138042520.029487497252663603130502
0.86113528626849016336.558215664279530.467106613577440.038130431689203570.06797559285889807128.3041078933928253.80070051682559-1737.1931043108448490.50597719497390.28235547100537256256.81790473482240.030304553830329123257.9806138042520.030361391106634775130502
0.94724881489533926226.4759523580228.932041712540850.08144775997120110.05770037560686564128.3041078933928221.8629125755565-1727.0648138775175427.32077365589370.24742602027569247256.81790473482240.028821908155805126257.9806138042520.029185350912232906130100
1.04197369638487316077.48604805427.8463854205679980.092159810655909780.04293735594344223118.1313206021489714.464965569465349-2008.1908960889973351.6564063369110.175111044981715256.805561475200760.02345005741824789257.988377296374670.024323933649597226130100
......................................................
16.52892561983471903.99360049284428.2048008238010620.32250392400207630.0280110066560487949.8998831484315062.9898235744414987-35.0290685436383747.1872490935825410.2051795663544076258.73249430360670.2805393954406921259.54519819251870.29431473144322967850100
18.18181818181818815.00242229168269.2941657209688450.185599307558295730.0236855291365842239.23668224086654.041456710808486-50.810551232691966.8834152351473780.13547216214254587258.64556451495960.24376550195329003259.77562900522060.236503163033863931030100
20.0734.83461253415197.1880607904735520.186168320147141210.017947531555144935.0276185965124543.07020165552574-47.041749833579895.4432735445880050.11571154482655796257.675592136976550.20557935370742536259.358626549878640.195581409416066941140200
22.0671.47514725386118.0090961803608830.228238812093487260.02507495638339829836.6377107652562663.5621204078837554-32.3660259628495.4370011063377360.16798482188015731257.16389211706910.3256187708559168258.87482752405170.30813918667814981210100
24.200000000000003656.42547868039659.1886462120286010.294373106121562530.01527979042964384340.0927334404802661.7529550214694376-50.653601427524434.9433258388871580.09759080696285943256.453251548958750.22877550689765366258.1397528999590.219058094916650241270100
26.620000000000005470.64111594357016.3001723849637550.183655309467597120.0248905207520759135.197637231216754.287437316885469-22.2794352635601853.41882045247408860.15345184525686095256.94916843417710.3781636080845221257.55068105128560.359649321817357761510100
29.282000000000007513.01322742681497.1267514257248010.30321057222572860.01432850105592534650.863712837850651.6078132886700334-33.940655710691583.3269037356114070.09802119805727282258.57681256123120.24930555337303292260.33291112920590.26291413043990941530170
32.21020000000001335.13726959838535.5002074290457830.018467370392817540.03404910325758143140.8637128378506753.44106317495372-13.5960105299935832.50818320260108860.18447935128234064259.755946699169330.5548318227838185259.373414180020230.55353255578116542010502
35.43122000000001291.344247761065335.917472171351610.018467370392817540.06423218869894584140.86371283785067100.7708265706513-6.9404078513156422.44546901503361760.35235234980751856259.755946699169331.1511406450664645259.373414180020231.148342182466996221035
38.974342000000014266.753536014096146.3291966934790660.018467370392817540.1885115482314048140.86371283785067296.7824343641862-2.0548370209125512.6458636475956971.2876270091827875259.755946699169333.7213184713692375259.373414180020233.714961780205796244085

Graficando

[24]:
import matplotlib.pyplot as plt
plt.rcParams['image.origin'] = 'lower'

Los atributos de una instancia de Isophote también son atributos de una instancia de IsophoteList. La diferencia es que, mientras que las isófotas individuales tienen atributos escalares, los mismos atributos en una IsophoteList son matrices numpy que almacenan el atributo dado en todas las isófotas de la lista. Por lo tanto, los atributos en una IsophoteList se pueden usar directamente como parámetros para matplotlib.

Como ejemplo, un gráfico básico de magnitud en función de la longitud del semieje mayor se puede hacer simplemente como:

[25]:
plt.figure(figsize=(8, 4))

plt.scatter(isophote_list.sma**0.25, -2.5*np.log10(isophote_list.intens))
plt.title("M51 brightness profile")
plt.xlabel('sma**1/4')
plt.ylabel('Magnitude')
plt.gca().invert_yaxis()
_images/F-4-isofotas_52_0.png

A continuación podemos ver un gráfico que representa la geometría de la elipse en función de la longitud del semieje mayor:

[26]:
plt.figure(figsize=(10, 5))
plt.figure(1)

plt.subplot(221)
plt.errorbar(isophote_list.sma, isophote_list.eps, yerr=isophote_list.ellip_err, fmt='o', markersize=4)
plt.xlabel('Semimajor axis length')
plt.ylabel('Ellipticity')

plt.subplot(222)
plt.errorbar(isophote_list.sma, isophote_list.pa/np.pi*180., yerr=isophote_list.pa_err/np.pi* 80., fmt='o', markersize=4)
plt.xlabel('Semimajor axis length')
plt.ylabel('PA (deg)')

plt.subplot(223)
plt.errorbar(isophote_list.sma, isophote_list.x0, yerr=isophote_list.x0_err, fmt='o', markersize=4)
plt.xlabel('Semimajor axis length')
plt.ylabel('X0')

plt.subplot(224)
plt.errorbar(isophote_list.sma, isophote_list.y0, yerr=isophote_list.y0_err, fmt='o', markersize=4)
plt.xlabel('Semimajor axis length')
plt.ylabel('Y0')

plt.subplots_adjust(top=0.92, bottom=0.08, left=0.10, right=0.95, hspace=0.35, wspace=0.35)
_images/F-4-isofotas_54_0.png
[27]:
plt.figure("Galaxia M51", figsize=[10, 10])
plt.imshow(data,vmin=np.nanmin(data), vmax=np.median(data)*5, origin='lower')
smas = np.linspace(10, 100, 10)
for sma in smas:
    iso = isophote_list.get_closest(sma)
    x, y, = iso.sampled_coordinates()
    plt.plot(x, y, color='white')
plt.show()
_images/F-4-isofotas_55_0.png

El algoritmo de ajuste asume que una distribución uniforme del brillo superficial dominará la imagen y este no es el mejor caso ya que M51 tiene muchas regiones de formación de estrellas.

Podemos examinar la muestra de brillo elíptica asociada con la elipse que se muestra arriba para tener una idea de lo que está pasando. El siguiente gráfico muestra una gran contaminación de esas regiones HII brillantes.

[28]:
plt.figure(figsize=(10, 3))
plt.plot(iso.sample.values[0] / np.pi*180., iso.sample.values[2])
plt.ylabel("Intensity")
plt.xlabel("Angle (deg)")
[28]:
Text(0.5, 0, 'Angle (deg)')
_images/F-4-isofotas_58_1.png

Podemos usar sigma-clipping para tratar de mitigarlos.

Ejecutando Ellipse con sigma-clipping

[29]:
from photutils.isophote import Ellipse
ellipse = Ellipse(data, geometry=g)

El recorte de Sigma se implementa a través de parámetros en el método fit_image. En este ejemplo, debido a la importante contaminación de la imagen por elementos no elípticos, aplicamos un recorte bastante agresivo.

[30]:
isophote_list = ellipse.fit_image(sclip=2., nclip=3)
isophote_list.to_table()
[30]:
QTable length=60
smaintensintens_errellipticityellipticity_errpapa_errgradgrad_errorgrad_rerrorx0x0_erry0y0_errndatanflagniterstop_code
degdeg
float64float64float64float64float64float64float64float64objectobjectfloat64float64float64float64int64int64int64int64
0.07600.7801795433610.00.00.00.00.00.0NoneNone256.88593854568230.0257.989534681139840.01000
0.53469726128275526852.680710093588520.6920332693029820.047995732498784260.08168375281229916155.6724800153884351.622419201190525-1600.1956911555108584.78746211284320.3654474670473364256.88593854568230.022889268334231924257.989534681139840.022612742986566056130500
0.58816698741103076765.20233792274823.863401502843850.044142546717311250.05789510926236029139.916005507675339.70389279859427-2378.228949657796474.9499823695770.19970742616597856256.87163685521030.017723390884621163257.981179509055660.01767999134520011130100
0.64698368615213386630.76503025354512.3207655274334580.05448044391862060.0427309982116427146.2026225596230827.54679980163171-1641.4437959635475299.578133001216540.18250891912224174256.86605986926930.015363370740489312257.981588428191570.015202707576202429103100
0.71168205476734716575.02182121516921.2201745595253830.081857846941552470.06342699384849047167.762128235321724.940881547754714-1575.9650693889798439.58669756591990.27893175179089014256.830845322343350.024426165057937023258.014002933280550.024566435770082867121100
0.78285026024408196467.57774879708821.1243977718146530.087575563561355730.056274193112099795165.04617150520620.714748397118147-1597.7398682084472387.82453033082150.24273321211273952256.817036228217660.023829316360574854258.01420485598590.023965163985755652121100
0.86113528626849016349.13967005698323.223938132149370.088135475018342870.050389929304796126160.014479232143618.469394837894527-1781.3091980813267370.310639088713860.20788678320842932256.808482158404960.02348578883047456258.01126205347920.023600963982566447121100
0.94724881489533926221.61037091263428.8309350338567430.076175659114986890.057641846554700164128.3464922323270823.28619267887906-1733.7445527313566426.7675979203020.2461536777421844256.818088411982160.028724188114443775257.980016445615040.029043039496036017130100
1.04197369638487316057.03768752578717.7979724617763820.106651574370846880.026132750559961764118.745005720764859.147872590373101-2016.0041959581501199.129433725919850.09877431511558894256.815161462632830.015848918173123772257.972590346824350.015804893557838644112502
......................................................
57.06233412220004183.938946078732870.89113145214836520.219598242959434950.00720694688376061162.584528468130971.1066090541867246-4.7533324435955980.235087417945360950.049457390311949664253.027455808975220.2546832745527912261.13731344745240.2225020999959595527938100
62.76856753442005157.02449410043111.01492086870044870.219598242959434950.004167881105068473162.584528468130970.6238923500962802-7.207668365191359NoneNone253.027455808975220.15856412386415936261.13731344745240.140968898450717533183135
69.04542428786206165.793687894500750.72643674331849510.154470738591741870.00892619765203238881.056089195471271.8575074622314105-2.80930639085853470.157529044356239160.056073999215193356254.69655106364370.3164628867224775277.089230383782140.365938462803865133466280
75.94996671664828144.718599385382730.76082769247456180.154470738591741870.0371403084612069581.056089195471277.767147404457842-0.56660239418365290.146970655732819670.259389401177119256.448004708124761.4644037716356266276.670321905385661.670785283952046340634502
83.5449633883131140.41710979072670.81682433844971060.154470738591741870.0226307103262764581.056089195471274.646490356310346-0.8690653917290320.15182281380870350.1746966514299314256.448004708124760.9764090336713479276.670321905385661.105913451809045745133125
91.89945972714443163.248760339789210.83756145663660350.421061442659220740.00909690763342789367.536394465536970.7781879295380345-1.69240326925428940.151111642326139940.08928820043743067253.052329620022360.4856739336157087241.66561584979130.625245777171929836965190
101.08940569985889159.3635699308790.86471184177241670.45332742390463240.00861363961614499575.964166411598950.6727079741554495-1.62471928105263050.130203446238605220.08013904171448523254.691588273084480.48350220190127097249.951532419567680.686883836843033137389502
111.19834626984479137.333998346791250.8148925680648580.284254750703298530.01071507002448573356.1627534174782851.3876873632722189-1.3426055435105990.09417344026172920.07014229958822285252.778041869257720.71215605777498252.816668705006460.792952417511418250486100
122.31818089682928122.404231924385170.65770243323929160.284254750703298530.0144355298801270256.1627534174782851.9585886075503005-0.64925892353952490.078569384904244630.12101394691029083252.778041869257721.078722335283315252.816668705006461.1875451132017995519815
134.5499989865122114.462612571047430.70074077410778020.284254750703298530.0393069448516925356.1627534174782855.1956117723229890.20833943901232954NoneNone252.778041869257723.1929865229597567252.816668705006463.47853007152250276229245

Observamos cómo la estabilidad añadida proporcionada por el sigma-clipping permite que el ajuste se haga mayor antes de detectar una condición de señal/ruido demasiado baja.

[31]:
plt.figure(figsize=(8, 4))

plt.scatter(isophote_list.sma**0.25, -2.5*np.log10(isophote_list.intens))
plt.title("M51 profile with sigma-clip")
plt.xlabel('sma**1/4')
plt.ylabel('Magnitude')
plt.gca().invert_yaxis()
_images/F-4-isofotas_65_0.png

Algunas de las isófotas en la imagen:

[32]:
fig, ax = plt.subplots(figsize=(8, 8))
ax.imshow(data, vmin=0, vmax=1000)
plt.axis([0, 511, 0, 511])

isos = []
smas = [20., 50., 90.]
for sma in smas:
    iso = isophote_list.get_closest(sma)
    isos.append(iso)
    x, y, = iso.sampled_coordinates()
    plt.plot(x, y, color='white')
_images/F-4-isofotas_67_0.png
[33]:
plt.figure(figsize=(10, 3))

for iso in isos:
    angles = ((iso.sample.values[0] + iso.sample.geometry.pa) / np.pi*180.) % 360.
    plt.scatter(angles, iso.sample.values[2], label=f"isofota {np.round(iso.sma, 2)}")

plt.xlabel("Angle (deg)")
plt.ylabel("Intensity")
plt.legend(loc='upper right')
[33]:
<matplotlib.legend.Legend at 0x7f669a5d6550>
_images/F-4-isofotas_68_1.png

Construcción de un modelo de imagen a partir de los resultados obtenidos por el ajuste Ellipse

Inicialmente ajustamos la imagen de prueba M51. Luego, a partir de los resultados de ajuste construimos una imagen modelo. Finalmente, restamos la imagen del modelo de la imagen original.

[34]:
from photutils.isophote import Ellipse
ellipse = Ellipse(data, geometry=g)
isophote_list = ellipse.fit_image(sclip=2., nclip=3)

Ahora construimos una imagen modelo.

[35]:
import numpy as np
from photutils.isophote import build_ellipse_model

model_image = build_ellipse_model(data.shape, isophote_list, fill=np.mean(data[0:10, 0:10]))

Gráfico de la región modelada:

[36]:
import matplotlib
import matplotlib.pyplot as plt

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 10))
limits = [128, 384]

ax1.imshow(data, vmin=0, vmax=1000)
ax1.set_title("M51")
ax1.set_xlim(limits)
ax1.set_ylim(limits)

ax2.imshow(model_image, vmin=0, vmax=1000)
ax2.set_title("Model")
ax2.set_xlim(limits)
ax2.set_ylim(limits)
plt.show()
_images/F-4-isofotas_75_0.png

Test de galaxia elíptica M105

Elegimos una imagen de dominio público de M105 tal como se publicó en asd.gsfc.nasa.gov.

[37]:
url = 'https://github.com/astropy/photutils-datasets/raw/main/data/isophote/M105-S001-RGB.fits'
path = download_file(url)
hdu = fits.open(path)
data = hdu[0].data[0]
hdu.close()

Repetimos el procedimiento anterior pero esta vez pasando una instancia de EllipseGeometry al constructor Ellipse, ya que el centro de la galaxia no coincide con el centro de la imagen. También pasamos valores de primera estimación para los parámetros de ángulo de posición y elipticidad, obtenidos de la inspección visual de la imagen.

[38]:
from photutils.isophote import Ellipse, EllipseGeometry, build_ellipse_model
g = EllipseGeometry(530., 511, 10., 0.1, 10./180.*np.pi)
g.find_center(data)

ellipse = Ellipse(data, geometry=g)
isolist = ellipse.fit_image()
isophote_list.to_table()
INFO: Found center at x0 = 530.0, y0 = 512.0 [photutils.isophote.geometry]
[38]:
QTable length=60
smaintensintens_errellipticityellipticity_errpapa_errgradgrad_errorgrad_rerrorx0x0_erry0y0_errndatanflagniterstop_code
degdeg
float64float64float64float64float64float64float64float64objectobjectfloat64float64float64float64int64int64int64int64
0.07600.7801795433610.00.00.00.00.00.0NoneNone256.88593854568230.0257.989534681139840.01000
0.53469726128275526852.680710093588520.6920332693029820.047995732498784260.08168375281229916155.6724800153884351.622419201190525-1600.1956911555108584.78746211284320.3654474670473364256.88593854568230.022889268334231924257.989534681139840.022612742986566056130500
0.58816698741103076765.20233792274823.863401502843850.044142546717311250.05789510926236029139.916005507675339.70389279859427-2378.228949657796474.9499823695770.19970742616597856256.87163685521030.017723390884621163257.981179509055660.01767999134520011130100
0.64698368615213386630.76503025354512.3207655274334580.05448044391862060.0427309982116427146.2026225596230827.54679980163171-1641.4437959635475299.578133001216540.18250891912224174256.86605986926930.015363370740489312257.981588428191570.015202707576202429103100
0.71168205476734716575.02182121516921.2201745595253830.081857846941552470.06342699384849047167.762128235321724.940881547754714-1575.9650693889798439.58669756591990.27893175179089014256.830845322343350.024426165057937023258.014002933280550.024566435770082867121100
0.78285026024408196467.57774879708821.1243977718146530.087575563561355730.056274193112099795165.04617150520620.714748397118147-1597.7398682084472387.82453033082150.24273321211273952256.817036228217660.023829316360574854258.01420485598590.023965163985755652121100
0.86113528626849016349.13967005698323.223938132149370.088135475018342870.050389929304796126160.014479232143618.469394837894527-1781.3091980813267370.310639088713860.20788678320842932256.808482158404960.02348578883047456258.01126205347920.023600963982566447121100
0.94724881489533926221.61037091263428.8309350338567430.076175659114986890.057641846554700164128.3464922323270823.28619267887906-1733.7445527313566426.7675979203020.2461536777421844256.818088411982160.028724188114443775257.980016445615040.029043039496036017130100
1.04197369638487316057.03768752578717.7979724617763820.106651574370846880.026132750559961764118.745005720764859.147872590373101-2016.0041959581501199.129433725919850.09877431511558894256.815161462632830.015848918173123772257.972590346824350.015804893557838644112502
......................................................
57.06233412220004183.938946078732870.89113145214836520.219598242959434950.00720694688376061162.584528468130971.1066090541867246-4.7533324435955980.235087417945360950.049457390311949664253.027455808975220.2546832745527912261.13731344745240.2225020999959595527938100
62.76856753442005157.02449410043111.01492086870044870.219598242959434950.004167881105068473162.584528468130970.6238923500962802-7.207668365191359NoneNone253.027455808975220.15856412386415936261.13731344745240.140968898450717533183135
69.04542428786206165.793687894500750.72643674331849510.154470738591741870.00892619765203238881.056089195471271.8575074622314105-2.80930639085853470.157529044356239160.056073999215193356254.69655106364370.3164628867224775277.089230383782140.365938462803865133466280
75.94996671664828144.718599385382730.76082769247456180.154470738591741870.0371403084612069581.056089195471277.767147404457842-0.56660239418365290.146970655732819670.259389401177119256.448004708124761.4644037716356266276.670321905385661.670785283952046340634502
83.5449633883131140.41710979072670.81682433844971060.154470738591741870.0226307103262764581.056089195471274.646490356310346-0.8690653917290320.15182281380870350.1746966514299314256.448004708124760.9764090336713479276.670321905385661.105913451809045745133125
91.89945972714443163.248760339789210.83756145663660350.421061442659220740.00909690763342789367.536394465536970.7781879295380345-1.69240326925428940.151111642326139940.08928820043743067253.052329620022360.4856739336157087241.66561584979130.625245777171929836965190
101.08940569985889159.3635699308790.86471184177241670.45332742390463240.00861363961614499575.964166411598950.6727079741554495-1.62471928105263050.130203446238605220.08013904171448523254.691588273084480.48350220190127097249.951532419567680.686883836843033137389502
111.19834626984479137.333998346791250.8148925680648580.284254750703298530.01071507002448573356.1627534174782851.3876873632722189-1.3426055435105990.09417344026172920.07014229958822285252.778041869257720.71215605777498252.816668705006460.792952417511418250486100
122.31818089682928122.404231924385170.65770243323929160.284254750703298530.0144355298801270256.1627534174782851.9585886075503005-0.64925892353952490.078569384904244630.12101394691029083252.778041869257721.078722335283315252.816668705006461.1875451132017995519815
134.5499989865122114.462612571047430.70074077410778020.284254750703298530.0393069448516925356.1627534174782855.1956117723229890.20833943901232954NoneNone252.778041869257723.1929865229597567252.816668705006463.47853007152250276229245
[39]:
fig, (ax1) = plt.subplots(figsize=(14, 5), nrows=1, ncols=1)
fig.subplots_adjust(left=0.04, right=0.98, bottom=0.02, top=0.98)
ax1.imshow(data, vmin=1464., vmax=1480.)
ax1.set_title('Ajuste de isofotas de la galaxia M105')
limits = [512-120, 512+150]
ax1.set_xlim(limits)
ax1.set_ylim(limits)

smas = np.linspace(10, 50, 10)
for sma in smas:
    iso = isolist.get_closest(sma)
    x, y, = iso.sampled_coordinates()
    ax1.plot(x, y, color='white')
_images/F-4-isofotas_81_0.png

Construimos una imagen modelo

[40]:
fill = np.mean(data[20:120, 20:120])
model_image = build_ellipse_model(data.shape, isolist, fill=fill)
[41]:
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 10))
limits = [512-120, 512+150]
ax1.imshow(data, vmin=1464., vmax=1480.)
ax1.set_xlim(limits)
ax1.set_ylim(limits)
ax1.set_title("Data")

ax2.imshow(model_image, vmin=1464., vmax=1480.)
ax2.set_xlim(limits)
ax2.set_ylim(limits)
ax2.set_title("Model")


# overplot a few isophotes on the residual map
iso1 = isolist.get_closest(10.)
iso2 = isolist.get_closest(40.)
iso3 = isolist.get_closest(100.)

plt.axis([512-120, 512+150, 512-120, 512+150])
x, y, = iso1.sampled_coordinates()
plt.plot(x, y, color='white')
x, y, = iso2.sampled_coordinates()
plt.plot(x, y, color='white')
x, y, = iso3.sampled_coordinates()
plt.plot(x, y, color='white')
[41]:
[<matplotlib.lines.Line2D at 0x7f6698eb73a0>]
_images/F-4-isofotas_84_1.png

Comprobamos cómo quedan los perfiles radiales.

[42]:
plt.figure(figsize=(8, 4))

plt.scatter(isolist.sma**0.25, -2.5*np.log10(isolist.intens))
plt.title("M105 brightness profile")
plt.xlabel('sma**1/4')
plt.ylabel('Magnitude')
plt.gca().invert_yaxis()
_images/F-4-isofotas_86_0.png
[43]:
plt.figure(figsize=(10, 5))
plt.figure(1)

plt.subplot(221)
plt.errorbar(isolist.sma, isolist.eps, yerr=isolist.ellip_err, fmt='o', markersize=4)
plt.xlabel('Semimajor axis length')
plt.ylabel('Ellipticity')

plt.subplot(222)
plt.errorbar(isolist.sma, isolist.pa/np.pi*180., yerr=isolist.pa_err/np.pi* 80., fmt='o', markersize=4)
plt.xlabel('Semimajor axis length')
plt.ylabel('PA (deg)')

plt.subplot(223)
plt.errorbar(isolist.sma, isolist.x0, yerr=isolist.x0_err, fmt='o', markersize=4)
plt.xlabel('Semimajor axis length')
plt.ylabel('X0')

plt.subplot(224)
plt.errorbar(isolist.sma, isolist.y0, yerr=isolist.y0_err, fmt='o', markersize=4)
plt.xlabel('Semimajor axis length')
plt.ylabel('Y0')

plt.subplots_adjust(top=0.92, bottom=0.08, left=0.10, right=0.95, hspace=0.35, wspace=0.35)
_images/F-4-isofotas_87_0.png

Podemos observar cómo la presencia de la estrella brillante hace que aparezca un componente no elíptica alrededor de una longitud del semieje mayor de \(\sim 50\) píxeles.

Repitamos el procedimiento con el recorte sigma habilitado para eliminar la estrella y ver el efecto en la imagen residual.

Tenga en cuenta que no es necesario crear una nueva instancia de Ellipse, ya que nada cambió ni en el mapa de píxeles de entrada ni en la geometría de la elipse de entrada.

[44]:
isolist2 = ellipse.fit_image(sclip=3., nclip=3)

fill = np.mean(data[20:120, 20:120])
model_image2 = build_ellipse_model(data.shape, isolist2, fill=fill)
[45]:
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 10))
limits = [512-120, 512+150]
ax1.imshow(data, vmin=1464., vmax=1480.)
ax1.set_xlim(limits)
ax1.set_ylim(limits)
ax1.set_title("Data")

ax2.imshow(model_image2, vmin=1464., vmax=1480.)
ax2.set_xlim(limits)
ax2.set_ylim(limits)
ax2.set_title("Model")


# overplot a few isophotes on the residual map
iso1 = isolist2.get_closest(10.)
iso2 = isolist2.get_closest(40.)
iso3 = isolist2.get_closest(100.)

plt.axis([512-120, 512+150, 512-120, 512+150])
x, y, = iso1.sampled_coordinates()
plt.plot(x, y, color='white')
x, y, = iso2.sampled_coordinates()
plt.plot(x, y, color='white')
x, y, = iso3.sampled_coordinates()
plt.plot(x, y, color='white')
[45]:
[<matplotlib.lines.Line2D at 0x7f669a4863a0>]
_images/F-4-isofotas_90_1.png
[46]:
plt.figure(figsize=(8, 8))
plt.figure(1)

plt.subplot(211)
plt.scatter(isolist.sma**0.25, -2.5*np.log10(isolist.intens))
plt.title("M105 brightness profile with stars")
plt.xlabel('sma**1/4')
plt.ylabel('Magnitude')
plt.gca().invert_yaxis()

plt.subplot(212)
plt.scatter(isolist2.sma**0.25, -2.5*np.log10(isolist2.intens))
plt.title("M105 brightness profile withour stars")
plt.xlabel('sma**1/4')
plt.ylabel('Magnitude')
plt.gca().invert_yaxis()

plt.subplots_adjust(top=0.92, bottom=0.08, left=0.10, right=0.95, hspace=0.35, wspace=0.35)
_images/F-4-isofotas_91_0.png
[47]:
plt.figure(figsize=(10, 5))
plt.figure(1)

plt.subplot(221)
plt.errorbar(isolist2.sma, isolist2.eps, yerr=isolist2.ellip_err, fmt='o', markersize=4)
plt.xlabel('Semimajor axis length')
plt.ylabel('Ellipticity')

plt.subplot(222)
plt.errorbar(isolist2.sma, isolist2.pa/np.pi*180., yerr=isolist2.pa_err/np.pi* 80., fmt='o', markersize=4)
plt.xlabel('Semimajor axis length')
plt.ylabel('PA (deg)')

plt.subplot(223)
plt.errorbar(isolist2.sma, isolist2.x0, yerr=isolist2.x0_err, fmt='o', markersize=4)
plt.xlabel('Semimajor axis length')
plt.ylabel('X0')

plt.subplot(224)
plt.errorbar(isolist2.sma, isolist2.y0, yerr=isolist2.y0_err, fmt='o', markersize=4)
plt.xlabel('Semimajor axis length')
plt.ylabel('Y0')

plt.subplots_adjust(top=0.92, bottom=0.08, left=0.10, right=0.95, hspace=0.35, wspace=0.35)
_images/F-4-isofotas_92_0.png

Análisis del brillo superficial de la galaxia espiral NGC 628

Distribución del brillo superficial: ellipse fitting

[48]:
url_g = 'https://dr9.sdss.org/sas/dr9/boss/photoObj/frames/301/7845/2/frame-g-007845-2-0104.fits.bz2'
url_r = 'https://dr9.sdss.org/sas/dr9/boss/photoObj/frames/301/7845/2/frame-r-007845-2-0104.fits.bz2'
[49]:
from astropy.utils.data import download_file

#path_g = download_file(url_g)
#path_r = download_file(url_r)

Abrimos nuestras imagenes de la galaxias M78 del catalogo SDSS en las bandas G y R.

[50]:
from astropy.io import fits
path_g = 'imagenes/isofotas/g.fit'
path_r = 'imagenes/isofotas/r.fit'

g_hdu = fits.open(path_g)
g_header = g_hdu[0].header
g_data = g_hdu[0].data
g_hdu.close()

r_hdu = fits.open(path_r)
r_header = r_hdu[0].header
r_data = r_hdu[0].data
r_hdu.close()
[51]:
g_header
[51]:
SIMPLE  =                    T /
BITPIX  =                  -32 / 32 bit floating point
NAXIS   =                    2
NAXIS1  =                 2048
NAXIS2  =                 1489
EXTEND  =                    T /Extensions may be present
BZERO   =              0.00000 /Set by MRD_SCALE
BSCALE  =              1.00000 /Set by MRD_SCALE
TAI     =        4735092988.65 / 1st row Number of seconds since Nov 17 1858
RA      =            24.903097 / 1st row RA of telescope boresight (deg)
DEC     =            16.513296 / 1st row Dec of telescope boresight (degrees)
SPA     =              95.876  / 1st row Cam col position angle wrt N (deg)
IPA     =              34.842  / 1st row Inst rotator position angle (deg)
IPARATE =              0.0015  / 1st row Inst rotator anglr velocity (deg/sec)
AZ      =            273.35022 / 1st row Azimuth (encoder) of tele (0=N?) (deg)
ALT     =            36.699549 / 1st row Altitude (encoder) of tele (degrees)
FOCUS   =           -131.50000 / 1st row - Focus piston (microns?)
DATE-OBS= '2008-12-04'         / 1st row - TAI date
TAIHMS  = '07:36:28.64'        / 1st row TAI time HH:MM:SS.SS
COMMENT  TAI,RA,DEC,SPA,IPA,IPARATE,AZ,ALT,FOCUS at reading of col 0, row 0
ORIGIN  = 'SDSS    '
TELESCOP= '2.5m    '
TIMESYS = 'TAI     '
RUN     =                 7845 / Run number
FRAME   =                  112 / Frame sequence number within the run
CCDLOC  =                   52 / Survey location of CCD (e.g., rowCol)
STRIPE  =                   75 / Stripe index number (23 <--> eta=0)
STRIP   = 'S       '           / Strip in the stripe being tracked.
FLAVOR  = 'science '           / Flavor of this run
OBSERVER= 'asimmons'           / Observer
SYS_SCN = 'mean    '           / System of the scan great circle (e.g., mean)
EQNX_SCN=           2000.00    / Equinox of the scan great circle. (years)
NODE    =           95.00000   / RA of the great circle's ascending node (deg)
INCL    =           -17.50000  / Great circle's inclination wrt cel. eq. (deg)
XBORE   =           22.74      / Boresight x offset from the array center (mm)
YBORE   =           0.00       / Boresight x offset from the array center (mm)
OBJECT  = '75 S    '           / e.g., 'stripe 50.6 degrees, north strip'
EXPTIME = '53.907456'          / Exposure time (seconds)
SYSTEM  = 'FK5     '           / System of the TCC coordinates (e.g., mean)
CCDMODE = 'DRIFT   '           / 'STARING' or 'DRIFT'
C_OBS   =                26322 / CCD row clock rate (usec/row)
COLBIN  =                    1 / Binning factor perpendicular to the columns
ROWBIN  =                    1 / Binning factor perpendicular to the rows
DAVERS  = 'v15_13_1'           / Version of DA software
SCDMETHD= 'sqrtDynamic'        / scdMethod
SCDWIDTH=                 1280 / scdDisplayWidth
SCDDECMF=                    1 / scdDecimateFactor
SCDOFSET=                  410 / scdDisplayOffset
SCDDYNTH=                  -19 / scdDynamicThresh
SCDSTTHL=                   30 / scdStaticThreshL
SCDSTTHR=                   30 / scdStaticThreshR
SCDREDSZ=                  478 / scdReduceSize
SCDSKYL =                    0 / scdSkyLeft
SCDSKYR =                    0 / scdSkyRight
COMMENT  CCD-specific parameters
CAMROW  =                    5 / Row in the imaging camera
BADLINES=                    0 / Number of bad lines in frame
EQUINOX =              2000.00 /
SOFTBIAS=                 1000 / software "bias" added to all DN
BUNIT   = 'nanomaggy'          / 1 nanomaggy = 3.631e-6 Jy
FILTER  = 'g       '           / filter used
CAMCOL  =                    2 / column in the imaging camera
VERSION = 'v5_6_3  '
DERV_VER= 'NOCVS:v8_23'
ASTR_VER= 'NOCVS:v5_24'
ASTRO_ID= '2009-05-09T14:00:33 22204'
BIAS_ID = 'PS      '
FRAME_ID= '2009-09-18T10:10:26 03946'
KO_VER  = 'devel   '
PS_ID   = '2009-05-09T13:11:50 16629 camCol 2'
ATVSN   = 'NOCVS:v5_24'        / ASTROTOOLS version tag
RADECSYS= 'ICRS    '           / International Celestial Ref. System
CTYPE1  = 'RA---TAN'           /Coordinate type
CTYPE2  = 'DEC--TAN'           /Coordinate type
CUNIT1  = 'deg     '           /Units
CUNIT2  = 'deg     '           /Units
CRPIX1  =        1025.00000000 /X of reference pixel
CRPIX2  =        745.000000000 /Y of reference pixel
CRVAL1  =        24.1744890066 /RA of reference pixel (deg)
CRVAL2  =        15.8450632705 /Dec of reference pixel (deg)
CD1_1   =    1.08781205157E-05 /RA deg per column pixel
CD1_2   =    0.000109477480770 /RA deg per row pixel
CD2_1   =    0.000109448497758 /Dec deg per column pixel
CD2_2   =   -1.08698208973E-05 /Dec deg per row pixel
HISTORY GSSSPUTAST: Sep 18 10:10:32 2009
COMMENT  Calibration parameters
COMMENT  Floats truncated at 10 binary digits with FLOATCOMPRESS
NMGY    =           0.00461444 / Calibration factor [nMgy per count]
NMGYIVAR=            0.0727204 / Calibration factor inverse variance
VERSIDL = '7.0     '           / Version of IDL
VERSUTIL= 'v5_5_5  '           / Version of idlutils
VERSPOP = 'v1_11_1 '           / Version of photoop product
PCALIB  = '/clusterfs/riemann/raid006/bosswork/groups/boss/calib/2009-06-14/cal'
PSKY    = '/clusterfs/riemann/raid006/bosswork/groups/boss/photo/sky' / Value of
RERUN   = '301     '           / rerun
HISTORY SDSS_FRAME_ASTROM: Astrometry fixed for dr9 Wed Jun 27 14:24:21 2012

Ajuste de isofotas en la banda G

[52]:
import matplotlib.pyplot as plt
import numpy as np
plt.figure(figsize=(8, 8))
plt.imshow(g_data, vmin=np.min(g_data), vmax=np.mean(g_data)*6 ,origin='lower')
[52]:
<matplotlib.image.AxesImage at 0x7f669a4cb070>
_images/F-4-isofotas_101_1.png
[53]:
from photutils.isophote import EllipseGeometry, Ellipse
x0 = 470.    # center position
y0 = 790.    # center position
sma = 50.   # starting semi-major axis length in pixels
eps = 0.5    # ellipticity
pa = 120. / 180. * np.pi  # position angle

g = EllipseGeometry(x0, y0, sma, eps, pa)
g.find_center(g_data)
ellipse = Ellipse(g_data, geometry=g)
INFO: Found center at x0 = 468.0, y0 = 795.0 [photutils.isophote.geometry]
[54]:
isolist_g = ellipse.fit_image(sclip=3., nclip=3)
[55]:
isolist_g.to_table()
[55]:
QTable length=55
smaintensintens_errellipticityellipticity_errpapa_errgradgrad_errorgrad_rerrorx0x0_erry0y0_errndatanflagniterstop_code
degdeg
float64float64float64float64float64float64float64float64objectobjectfloat64float64float64float64int64int64int64int64
0.07.0366261904377790.00.00.00.00.00.0NoneNone467.91790928804450.0794.3839774104540.01000
0.51537235240978816.9785596189376840.0085761141523880180.66944800818385720.247007694355536289.9010037853597516.41472690185171-0.079128108463303540.20969838454924982.6501124394563225467.91790928804450.06568770710530847794.3839774104540.19267481565129643130100
0.56690958765076696.9609155425919930.011639918688903070.65075496121916410.3249504118587575688.1623910091960121.91215467932307-0.078112406593100560.25243198700756083.2316503615426613467.90952186880270.09540039146789599794.40240420955520.2638530060191995130150
0.62360054641584366.9134450601928530.0148103886873270020.52141087329496240.0701661274555427485.750127023369065.388612235549459-0.57411851735790350.3710183634040910.646240022202244467.86182055198650.02277388330252793794.43206410481080.04565890241578605130140
0.6859606010574286.81713115477196750.0184947608849817950.262136837498832540.0858592509350103677.4807887655580611.158710663601077-0.81965852995302140.401632168633686670.48999937651683584467.770478192584160.03091016946456364794.44732462000790.03954854930982435130100
0.75455666116317086.7195466130523890.019175740887787710.110809646743708970.077884654785935355.425198153305222.027911471709082-1.02901869254660560.361945995426345260.35173898982398927467.71837865484570.03124025101747533794.4507735802510.03221662954732579130100
0.83001232727948786.6331369813381380.0173072657303664050.094901310016221460.0575488202145463248.0585898863210918.91411388213538-1.16547794057116680.30051549199427020.25784743025423223467.70446339292750.025410765475871275794.46793984542740.025607024778430708130100
0.91301356000743666.5400136431898320.017614713036793380.099604264535822450.0502809080026741847.7386317337024515.730298876801987-1.22760124066209950.272828141747900150.22224492181251942467.71220310621190.024533449000052554794.47843205088620.024707965348026936130100
1.00431491600818036.42511876933630250.0175380803275857170.093348864600662180.04389242892774114549.1732534227912714.601134368666187-1.28200888919800040.24012251591979080.18730175581700276467.71942492538490.023440130888446712794.48730809968120.023669185220263057130100
......................................................
34.150672768253530.90859467528824480.0084127691573376140.123290792314380170.01641455567270688429.2376536656514154.31423584572952-0.036813551381137810.00321048627196730530.0872093604533972467.703583474855750.3164089445231044794.54286078093460.301838505775557719110100
37.565740045078880.80726714868235380.0055626729824468530.145161004938555830.0159079793206716737.9573998788821853.572650807765996-0.0219132459240821770.0020400552360197220.09309689870170014467.641771518139360.33637204448118596795.7280621950.328898773086562120811502
41.322314049586770.742607168143880.0046067630046800980.145161004938555830.0157752574108033837.9573998788821853.351490240800742-0.017273677899608010.00155380633458027580.08995225820527405465.507807307389840.35738323885825807795.94325931781650.348476772288783272392120
45.454545454545450.66756552608299180.0040190526630121270.0203344198344883940.014574681190688476174.4588619240685720.83832436329162-0.016922916731026290.00125612028647197930.07422599227052994468.348878395407840.3372038338318546792.853234851230.33363627301774812840100
50.00.62336883958968230.00388577905409357530.071924256725026250.01147381705192033222.632415663298924.762788596013085-0.0178849781876629470.00104459188556825680.05840610341302043468.956293515140030.3039528132731592791.92910573385850.29316497160677713040200
55.000000000000010.55273540456157890.00341367963282345660.080847398838907960.01225701517667810657.359340461840814.536065027367982-0.0132100502087497560.00084118190305232030.0636774190680334468.48302691967940.34810202647243166793.0834076473480.35616602609241113321100
60.5000000000000140.46415620412939470.00263702994007906960.061933623553993440.03881331602562674425.9224592486083318.478098648962256-0.00296531001394098950.00032065364541499920.10813494842275884471.36183050051141.2351977734310622794.48267435057151.1881607983069993904502
66.550000000000030.45734947961380690.00274040389972325050.061933623553993440.018325023223018527171.467679335710078.846787668315997-0.005923878112679460.00057521165551172350.09710052174782957470.0789495209370.6510994486824244794.67514209950710.615764631911143539710502
73.205000000000040.417874584660021140.00267428088762793840.061933623553993440.019220655684728024171.467679335710079.370177531086478-0.004467627814594760.00050171094664014130.11229918145848268470.0789495209370.7549754474956365794.67514209950710.71322454463177074331585
80.525500000000050.385213704703377240.00251307878474201550.061933623553993440.007370023089225932171.467679335710073.5531305987650277-0.0091538954567166NoneNone470.0789495209370.31431303411502626794.67514209950710.301570248026490134893175
[56]:
fig, ax = plt.subplots(figsize=(8, 8))
plt.imshow(g_data, vmin=np.min(g_data), vmax=np.mean(g_data)*6 ,origin='lower')
#ax.set_title("791 wide filter")
#ax.set_xlim([300, 900])
#ax.set_ylim([700, 1200])

# go to the outermost successfully fitted ellipse at sma=235
isos = []
for sma in [50., 100., 150., 200., 700.]:
    iso = isolist_g.get_closest(sma)
    isos.append(iso)
    x, y, = iso.sampled_coordinates()
    plt.plot(x, y, color='w')
_images/F-4-isofotas_105_0.png
[57]:
plt.figure(figsize=(10, 6))
plt.scatter(isolist_g.sma, isolist_g.intens)
plt.title("M78 G filter")
plt.xlabel('sma')
plt.ylabel('Intensidad')
[57]:
Text(0, 0.5, 'Intensidad')
_images/F-4-isofotas_106_1.png

Ajuste de isofotas en la banda R

[58]:
ellipse = Ellipse(r_data, geometry=g)
isolist_r = ellipse.fit_image(sclip=3., nclip=3)
[59]:
isolist_r.to_table()
[59]:
QTable length=78
smaintensintens_errellipticityellipticity_errpapa_errgradgrad_errorgrad_rerrorx0x0_erry0y0_errndatanflagniterstop_code
degdeg
float64float64float64float64float64float64float64float64objectobjectfloat64float64float64float64int64int64int64int64
0.015.1158617758253550.00.00.00.00.00.0NoneNone467.318767207482150.0782.81028668417250.01000
0.515372352409788114.808544810095320.035507339238436540.328079296244303150.090058917773013541.63762618082300429.723721962589002-1.82668344861556741.07604179537861340.5890685636825248467.318767207482150.03456715553460349782.81028668417250.023938229986199124130100
0.566909587650766914.7449557115368750.0392245700057405260.327547329929183060.090666291489842012.10829278748246959.798557862520317-1.82387447498815921.0806998624676990.5925297367159628467.348963450222640.03823642987592174782.79161868946810.02654433985595171130100
0.623600546415843614.6542458755510620.044176515625469080.28986504426650840.077122567849285872.10829278748246959.211522229889454-2.31389963001174471.06320704689010710.45948710700330186467.38485349734490.03388159760027299782.75683017451490.024835168866018397130100
0.68596060105742814.4113007329305840.057980612294524390.105496969285975990.102922889933046935.39486242521470730.489754168893068-2.60796548335835651.30554005523565290.5005971373342216467.391768033646260.03947324043118913782.68361194740080.03646434693179429130100
0.754556661163170814.2087861919430340.060514257374226010.074794759718069910.0771240072607246325.41755270931850731.712353702209455-3.41928380154904371.1008596445861110.3219562073459321467.412753113721240.03121613664354069782.65196818621510.030304149880922733130100
0.830012327279487813.9234810651832670.0618544134882967060.046236178422252490.0672344456437519928.22688091193050444.06783015281843-3.75703473137323570.99498999182545430.2648338551455911467.41393068223270.02917443555385493782.63956544727350.02890583912793097130100
0.913013560007436613.5766505477636180.054558425057228650.025568286932148710.05345472256566316529.22998577663881362.696055514492244-3.87113211698253860.78297596259763920.20226020165076453467.41563656541370.025094954453855286782.64140165368590.025159388406748737130100
1.004314916008180313.2134468158983580.045729367578994140.0207005247718056280.0405108987876839429.1435807422416358.541994646833736-3.9117432731890380.58449247010816330.14941994637384712467.41786990986150.02084172098100342782.63776942894480.020949201363302142130100
......................................................
305.79545224207320.22990159318119560.0010498056561823390.190955319531195180.005189826786289222127.573669921801780.8681957482579648-0.00150836152893990874.475764347657719e-050.0296730211012696480.137505575125430.8724555385440977782.48648164571090.9111775803513292164785100
336.374997466280550.1810105034863120.00087446488304693050.16833084907797460.006292500062859602137.282849326528321.166042036308538-0.00097306770783939063.746726993110771e-050.038504278406587364483.909803775671261.1717128433199169783.5974106741441.1579123149338777188052110
370.01249721290860.155710829558654620.00082550357949574340.138057945323315860.007075611162730078165.178054300197121.5753762262279287-0.00076898947689951443.148916814676029e-050.04094876340014605458.655903089547961.4741959372390454780.18280187076791.3474643596834859211946100
407.01374693419950.132397412641636960.00084350507890374390.160305076441637220.006153825994055101175.232890535110241.183832185580419-0.00080418187719654642.62531979343177e-050.03264584626781048453.15247623750781.4571926858382886782.39128467539411.2687280560778178225793100
447.71512162761950.087263374792218320.00062692979150885990.084628836994794080.009286654737438373.52340800759144963.319459852170743-0.00038178935781030191.9023298963903486e-050.04982668734667956453.15247623750782.2674873288501125782.39128467539412.107069908896665260696502
492.48663379038150.078031969901253810.00056861097941944560.15206002948226070.007675133489490562425.005240224122541.5563953270358457-0.00036955119330917651.6096127382462112e-050.0435558798723609456.902686336330132.2220902527134654774.35085316470851.91779962350258512509348110
541.73529716941970.063110410791409650.00050392725633366290.15206002948226070.00906369866911755344.5260072749946052.1729338915531096-0.000237911416958818841.4014423267378346e-050.05890605607129886458.928216716980672.963895358717135750.58946394614082.95547828693056452692451110
595.90882688636170.0423103122564762250.000445758127497384650.093318504823109480.01434740952437122971.97668897308734.493907112347438-0.000145741628652913161.1662918694213359e-050.0800246216679028486.52370060516574.743155976582619739.69782422767424.18861403568235252986592502
655.4997095749980.0335153374261713960.000456602766786714160.110953214769442840.011876145386797527105.972888002105592.961628201424166-0.000171501118409220359.548347123206663e-060.055675130353513305486.52370060516574.516828683942675739.69782422767423.60277768462567273069828502
721.04968053249790.022273444095268270.000428085339799240.110953214769442840.01252961880425357105.972888002105593.0404016718398816-0.0001463153480676158.39892768526509e-060.05740291634602674486.52370060516575.670728370967659739.69782422767424.05216343588236153213107421
[60]:
fig, ax = plt.subplots(figsize=(8, 8))
plt.imshow(r_data, vmin=np.min(g_data), vmax=np.mean(g_data)*6 ,origin='lower')
#ax.set_title("791 wide filter")
#ax.set_xlim([300, 900])
#ax.set_ylim([700, 1200])

# go to the outermost successfully fitted ellipse at sma=235
isos = []
for sma in [50., 100., 150., 200., 700.]:
    iso = isolist_r.get_closest(sma)
    isos.append(iso)
    x, y, = iso.sampled_coordinates()
    plt.plot(x, y, color='w')
_images/F-4-isofotas_110_0.png
[61]:
plt.figure(figsize=(10, 6))
plt.scatter(isolist_r.sma, -isolist_r.intens)
plt.title("M78 R filter")
plt.xlabel('sma')
plt.ylabel('Intensidad')
plt.gca().invert_yaxis()
_images/F-4-isofotas_111_0.png

Nivel de Ruido de las Imágenes (stdev)

Vamos a obtener el nivel de ruido que tenemos en nuestras imágenes. Para ello nos iremos a una zona donde sepamos que no haya brillo de la galaxia ni de estrellas, es decir una zona donde sea lo mas oscuro posible y solo veamos píxeles de ruido. Una vez que tengamos la zona localizada mediante la desviación cuadrática media stdev podremos ver el nivel de ruido que tenemos en nuestra imagen.

Esto nos servirá para poder distingir señal de ruido a medida que nos vayamos alejando del centro de la galaxia.

[62]:
import matplotlib.pyplot as plt
import numpy as np
plt.figure(figsize=(8, 8))
plt.imshow(g_data[0:100,1900:2000], vmin=np.min(g_data), vmax=np.mean(g_data)*6 ,origin='lower')
[62]:
<matplotlib.image.AxesImage at 0x7f6697404160>
_images/F-4-isofotas_114_1.png
[63]:
g_std = np.std(g_data[0:100,1900:2000])
r_std = np.std(r_data[0:100,1900:2000])

g_std, r_std
[63]:
(0.018639611, 0.031717688)

Escala de píxel en arcsec/pix y en pc/pix de las imágenes.

Mediante los parámetros observados en la cabecera de nuestras imágenes calculamos el diámetro angular como: $ D = \sqrt{CD1\_1^2 + CD2\_1^2} *3600 $

[64]:
D_arc = np.sqrt(g_header['CD1_1']**2+g_header['CD2_1']**2)*3600 #arcsec/pix
D_arc
[64]:
0.39595593301525844

Para obtener el valor de parsec por pixel deberemos hacer la transformación del diámetro angular en arcoseg/pixel a radianes/pixel y este multiplicarlo por la distancia a la galaxia. Para el valor de la distancia a la galaxia hemos tomado como referencia el método SNII optical en la banda B, \(d = 7.2 Mpc\).

[65]:
D_pc = (D_arc/3600)*(np.pi/180)*(7.2*10**6)
D_pc #pc/pixel
[65]:
13.821469447844759

Calibración del flujo

Para calcular el brillo superficial que tiene nuestra galaxia medido a través de las imágenes recogidas, tenemos que tener en cuenta que la medida de intensidad de las imágenes esta en nanomaggies. Podemos pasar de nanomaggies a magnitudes usando la siguiente formula:

\[m=22.5[mag]-2.5log(F[nanomaggies])\]

y por lo tanto podemos calcular el brillo superficial mediante el uso de la resolución angular.

\[\mu=2.5log(\frac{F}{F_{0}D^{2}})=-2.5\left(log\frac{F}{F_{0}}-log\left(D^{2}\right)\right)=m+2.5log\left(D^{2}\right) =22.5[mag]-2.5log(F)+2.5log\left(D^{2}\right)=\boxed{22.5[mag]-2.5log(F/D^{2})}\]
[66]:
def mu(nanomaggies, D):
    return 22.5-2.5*np.log10(nanomaggies/D**2)

Para calcular la magnitud límite vamos a considerar que el brillo mas bajo medible es 3 veces el ruido de fondo que tengamos en nuestra imagen.

[67]:
mag_lim_g = mu(3*g_std, D_arc) #mag/arcsec**2
mag_lim_r = mu(3*r_std, D_arc) #mag/arcsec**2

Ajustar el perfil de brillo del disco y obtener la longitud de escala

Banda G

[68]:
plt.figure(figsize=(10, 6))
plt.scatter(isolist_g.sma, mu(isolist_g.intens, D_arc))
plt.title("M78 G filter")
plt.xlabel('sma [pix]')
plt.ylabel('Magnitude')
plt.gca().invert_yaxis()
_images/F-4-isofotas_131_0.png
[69]:
np.where(isolist_g.sma <=90)
[69]:
(array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,
        17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,
        34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
        51, 52, 53, 54]),)
[70]:
plt.figure(figsize=(10, 6))
plt.scatter(isolist_g.sma[55:], mu(isolist_g.intens[55:], D_arc), label= 'Perfil de brillo')
plt.title("M78 G filter")
plt.xlabel('sma [pix]')
plt.ylabel('Magnitude')
plt.gca().invert_yaxis()
plt.axhline(y=mag_lim_g, label='Magnitud límite')
plt.legend()
[70]:
<matplotlib.legend.Legend at 0x7f6697090ee0>
_images/F-4-isofotas_133_1.png
[71]:
np.where(isolist_g.sma <=500)
[71]:
(array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,
        17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,
        34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
        51, 52, 53, 54]),)
[72]:
plt.figure(figsize=(10, 6))
plt.scatter(isolist_g.sma[55:73], mu(isolist_g.intens[55:73], D_arc), label= 'Perfil de brillo')
plt.title("M78 g filter")
plt.xlabel('sma')
plt.ylabel('Magnitude')
plt.gca().invert_yaxis()
plt.legend()
[72]:
<matplotlib.legend.Legend at 0x7f669705dc70>
_images/F-4-isofotas_135_1.png

Ajuste lineal en la banda G

[73]:
import numpy as np
import matplotlib.pyplot as plt
from astropy.modeling import models, fitting

# Definimos el modelo lineal
line_orig = models.Linear1D(slope=1.0, intercept=0.5)
fit = fitting.LinearLSQFitter()
# Inicializamos el modelo lineal
line_init = models.Linear1D()

# Ajuste de los datos
fitted_line_g = fit(line_init, isolist_g.sma[55:73], mu(isolist_g.intens[55:73], D_arc))
/home/zerjillo/anaconda3/envs/cursoAstronomia2/lib/python3.8/site-packages/astropy/modeling/fitting.py:828: RuntimeWarning: invalid value encountered in true_divide
  lacoef /= scl[:, np.newaxis] if scl.ndim < rhs.ndim else scl
[74]:
fitted_line_g
[74]:
<Linear1D(slope=nan, intercept=nan)>
[75]:
plt.figure(figsize=(10, 6))
plt.scatter(isolist_g.sma[55:73], mu(isolist_g.intens[55:73], D_arc), label= 'Perfil de brillo')
plt.plot(x, fitted_line_g(x), 'k-', label='Ajuste lineal')
plt.title("M78 g filter")
plt.xlabel('sma')
plt.ylabel('Magnitude')
plt.xlim(80, 500)
plt.gca().invert_yaxis()
plt.legend()
[75]:
<matplotlib.legend.Legend at 0x7f6696f9e3d0>
_images/F-4-isofotas_139_1.png

La longitud de escala es el radio en el que la galaxia es un factor e (~2,7) menos brillante que en su centro. Debido a la diversidad de formas y tamaños de las galaxias, no todos los discos galácticos siguen esta sencilla forma exponencial en sus perfiles de brillo.

\[I_r = I_0\:exp \left[ \frac{-R}{H_r}\right]\]

En galaxias espirales el perfil de brillo superficial en el disco se modeliza como:

\[\mu_{disco} \left( R \right)=\mu_{0}+1.086\left(\frac{R}{H_{R}}\right)\]

donde \(\mu_0\) es el brillo de la superficial central y \(H_r\) la longitud de la escala del disco.

Como hemos obtenido la representacion de µ frente a R y hemos ajustado la recta, podemos obtener \(H_R\) mediante la pendiente:

\[H_r = \frac{1.086}{b}\]

Con un error asociado de :

\[\Delta H_r = \frac{1.086}{b^2}\Delta b\]
[76]:
fitted_line_g.slope[0] # pendiente
[76]:
nan
[77]:
1.086/fitted_line_g.slope[0] #pixeles
[77]:
nan
[78]:
1.086/(fitted_line_g.slope[0]*D_arc) #arcsec
[78]:
nan
[79]:
Hr_g = 1.086/fitted_line_g.slope[0]*D_pc #pc

Banda r

[80]:
plt.figure(figsize=(10, 6))
plt.scatter(isolist_r.sma, mu(isolist_r.intens, D_arc))
plt.title("M78 r filter")
plt.xlabel('sma**1/4')
plt.ylabel('Magnitude')
plt.gca().invert_yaxis()
_images/F-4-isofotas_148_0.png
[81]:
np.where(isolist_r.sma <=70)
[81]:
(array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,
        17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,
        34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
        51, 52]),)
[82]:
plt.figure(figsize=(10, 6))
plt.scatter(isolist_r.sma[52:], mu(isolist_r.intens[52:], D_arc), label= 'Perfil de brillo')
plt.title("M78 r filter")
plt.xlabel('sma [pix]')
plt.ylabel('Magnitude')
plt.gca().invert_yaxis()
plt.axhline(y=mag_lim_r, label='Magnitud límite')
plt.legend()
[82]:
<matplotlib.legend.Legend at 0x7f66970b70a0>
_images/F-4-isofotas_150_1.png
[83]:
np.where(isolist_r.sma <=450)
[83]:
(array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,
        17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,
        34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
        51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67,
        68, 69, 70, 71, 72]),)
[84]:
plt.figure(figsize=(10, 6))
plt.scatter(isolist_g.sma[55:72], mu(isolist_g.intens[55:72], D_arc), label= 'Perfil de brillo')
plt.title("M78 r filter")
plt.xlabel('sma')
plt.ylabel('Magnitude')
plt.gca().invert_yaxis()
plt.legend()
[84]:
<matplotlib.legend.Legend at 0x7f669a9ca340>
_images/F-4-isofotas_152_1.png

Ajuste lineal en la banda r

[85]:
import numpy as np
import matplotlib.pyplot as plt
from astropy.modeling import models, fitting

# Definimos el modelo lineal
line_orig = models.Linear1D(slope=1.0, intercept=0.5)

# Inicializamos el modelo lineal
line_init = models.Linear1D()

# Ajuste de los datos
fitted_line_r = fit(line_init, isolist_r.sma[52:72], mu(isolist_r.intens[52:72], D_arc))
[86]:
fitted_line_r
[86]:
<Linear1D(slope=0.00599491, intercept=20.28690473)>
[87]:
plt.figure(figsize=(10, 6))
plt.scatter(isolist_r.sma[55:73], mu(isolist_r.intens[55:73], D_arc), label= 'Perfil de brillo')
plt.plot(x, fitted_line_r(x), 'k-', label='Ajuste lineal')
plt.title("M78 r filter")
plt.xlabel('sma')
plt.ylabel('Magnitude')
plt.xlim(80, 500)
plt.gca().invert_yaxis()
plt.legend()
[87]:
<matplotlib.legend.Legend at 0x7f669ab69070>
_images/F-4-isofotas_156_1.png

Longitudes de escala

[88]:
fitted_line_r.slope[0]
[88]:
0.0059949075577498375
[89]:
1.086/fitted_line_r.slope[0] #pixeles
[89]:
181.1537525038377
[90]:
1.086/fitted_line_r.slope[0]*D_arc #arcsec
[90]:
71.72890309187227
[91]:
Hr_r = 1.086/fitted_line_r.slope[0]*D_pc #pc

Banda

H_r[arcsec]

H_r[pc]

g

72.06

2789.89

r

79.92

2515.59

[92]:
Hr_g, Hr_r
[92]:
(nan, 2503.8110555942235)

En este articulo podemos encontrar valores aproximados a los que hemos obtenido nosotros, teniendo en cuenta que ellos han cogido una distancia a la galaxia de \(10.4 \: Mpc\):

Banda

H_r[pc]

U

4.26

B

3.44

V

3.27

R

3.29

I

3.31

[93]:
D_pc = (D_arc/3600)*(np.pi/180)*(10.4*10**6)
[94]:
Hr_g = 1.086/fitted_line_g.slope[0]*D_pc #pc
Hr_r = 1.086/fitted_line_r.slope[0]*D_pc #pc
Hr_g, Hr_r
[94]:
(nan, 3616.6159691916564)

Diagramas g-r

Como hemos calculado el brillo superficial de la galaxia podemos calcular el color simplemente restando los dos brillos superficiales µG y µR, ya que

[95]:
g_data_mag = 22.5-2.5 * np.log10(g_data/D_arc**2)
r_data_mag = 22.5-2.5 * np.log10(r_data/D_arc**2)
<ipython-input-95-dfe2658c2d03>:1: RuntimeWarning: invalid value encountered in log10
  g_data_mag = 22.5-2.5 * np.log10(g_data/D_arc**2)
<ipython-input-95-dfe2658c2d03>:2: RuntimeWarning: invalid value encountered in log10
  r_data_mag = 22.5-2.5 * np.log10(r_data/D_arc**2)
[96]:
g_r = g_data_mag - r_data_mag
[97]:
plt.figure(figsize=(10, 6))

plt.imshow(g_r, cmap='gray')
[97]:
<matplotlib.image.AxesImage at 0x7f66a1b13970>
_images/F-4-isofotas_172_1.png
[98]:
plt.figure(figsize=(10, 6))
plt.plot(range(len(g_r[795,468:1000])), g_r[795,468:1000], 'k-', label='Ajuste lineal')
plt.legend()
[98]:
<matplotlib.legend.Legend at 0x7f669abf7fd0>
_images/F-4-isofotas_173_1.png
[99]:
g_r = mu(isolist_g.intens[:73],D_arc) - mu(isolist_r.intens[:73],D_arc)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[99], line 1
----> 1 g_r = mu(isolist_g.intens[:73],D_arc) - mu(isolist_r.intens[:73],D_arc)

ValueError: operands could not be broadcast together with shapes (55,) (73,)
[100]:
plt.figure(figsize=(10, 6))
plt.plot(isolist_r.sma[:73], g_r, 'k-', label='Ajuste lineal')
plt.xlabel('sma')
plt.ylabel('g-r')
plt.legend()
plt.show()
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[100], line 2
      1 plt.figure(figsize=(10, 6))
----> 2 plt.plot(isolist_r.sma[:73], g_r, 'k-', label='Ajuste lineal')
      3 plt.xlabel('sma')
      4 plt.ylabel('g-r')

File ~/anaconda3/envs/cursoAstronomia2/lib/python3.8/site-packages/matplotlib/pyplot.py:2812, in plot(scalex, scaley, data, *args, **kwargs)
   2810 @_copy_docstring_and_deprecators(Axes.plot)
   2811 def plot(*args, scalex=True, scaley=True, data=None, **kwargs):
-> 2812     return gca().plot(
   2813         *args, scalex=scalex, scaley=scaley,
   2814         **({"data": data} if data is not None else {}), **kwargs)

File ~/anaconda3/envs/cursoAstronomia2/lib/python3.8/site-packages/matplotlib/axes/_axes.py:1688, in Axes.plot(self, scalex, scaley, data, *args, **kwargs)
   1445 """
   1446 Plot y versus x as lines and/or markers.
   1447
   (...)
   1685 (``'green'``) or hex strings (``'#008000'``).
   1686 """
   1687 kwargs = cbook.normalize_kwargs(kwargs, mlines.Line2D)
-> 1688 lines = [*self._get_lines(*args, data=data, **kwargs)]
   1689 for line in lines:
   1690     self.add_line(line)

File ~/anaconda3/envs/cursoAstronomia2/lib/python3.8/site-packages/matplotlib/axes/_base.py:311, in _process_plot_var_args.__call__(self, data, *args, **kwargs)
    309     this += args[0],
    310     args = args[1:]
--> 311 yield from self._plot_args(
    312     this, kwargs, ambiguous_fmt_datakey=ambiguous_fmt_datakey)

File ~/anaconda3/envs/cursoAstronomia2/lib/python3.8/site-packages/matplotlib/axes/_base.py:504, in _process_plot_var_args._plot_args(self, tup, kwargs, return_kwargs, ambiguous_fmt_datakey)
    501     self.axes.yaxis.update_units(y)
    503 if x.shape[0] != y.shape[0]:
--> 504     raise ValueError(f"x and y must have same first dimension, but "
    505                      f"have shapes {x.shape} and {y.shape}")
    506 if x.ndim > 2 or y.ndim > 2:
    507     raise ValueError(f"x and y can be no greater than 2D, but have "
    508                      f"shapes {x.shape} and {y.shape}")

ValueError: x and y must have same first dimension, but have shapes (73,) and (1489, 2048)
_images/F-4-isofotas_175_1.png

Podemos ver un decaimiento en el índice de color hacia el rojo a medida que nos alejamos del radio pero tenemos zonas donde el indice de color se desplaza hacia el azul que se debe a las diferentes edades de las poblaciones estelares del disco, el cual está dominado en las partes externas por estrellas más jóvenes.