diff mbox

[FFmpeg-devel] Add FITS Decoder

Message ID 1498983220-20542-1-git-send-email-paraschadha18@gmail.com
State Superseded
Headers show

Commit Message

Paras July 2, 2017, 8:13 a.m. UTC
Made all the changes suggested
Added a new function which reads a header FITS line safely. It also makes it more modular
Added an option for the user to enter the value to be used in place of BLANK pixels
Refactored code using macros to make it short

Signed-off-by: Paras Chadha <paraschadha18@gmail.com>
---
 Changelog               |   1 +
 doc/general.texi        |   2 +
 libavcodec/Makefile     |   1 +
 libavcodec/allcodecs.c  |   1 +
 libavcodec/avcodec.h    |   1 +
 libavcodec/codec_desc.c |   8 +
 libavcodec/fitsdec.c    | 505 ++++++++++++++++++++++++++++++++++++++++++++++++
 libavcodec/version.h    |   4 +-
 libavformat/img2.c      |   1 +
 9 files changed, 522 insertions(+), 2 deletions(-)
 create mode 100644 libavcodec/fitsdec.c

--
2.4.11

Comments

Nicolas George July 4, 2017, 9:27 a.m. UTC | #1
Le quartidi 14 messidor, an CCXXV, Paras Chadha a écrit :
> Made all the changes suggested
> Added a new function which reads a header FITS line safely. It also makes it more modular
> Added an option for the user to enter the value to be used in place of BLANK pixels
> Refactored code using macros to make it short

Note: this is the commit message, it is there to stay, so it must
contain information about the whole commit, not its differences with a
previous draft of it. These, and any other kind of comment, belong
between the first triple-dash line and the beginning of the actual diff,
where Git places the shortstats of the diff.

Also, lines should be split way below 80 characters.

> 
> Signed-off-by: Paras Chadha <paraschadha18@gmail.com>
> ---
>  Changelog               |   1 +
>  doc/general.texi        |   2 +
>  libavcodec/Makefile     |   1 +
>  libavcodec/allcodecs.c  |   1 +
>  libavcodec/avcodec.h    |   1 +
>  libavcodec/codec_desc.c |   8 +
>  libavcodec/fitsdec.c    | 505 ++++++++++++++++++++++++++++++++++++++++++++++++
>  libavcodec/version.h    |   4 +-
>  libavformat/img2.c      |   1 +
>  9 files changed, 522 insertions(+), 2 deletions(-)
>  create mode 100644 libavcodec/fitsdec.c

Note: before reviewing the code, I think the boundary between the
demuxer and the decoder needs to be properly defined. See my comments in
this mail, near the end:
https://ffmpeg.org/pipermail/ffmpeg-devel/2017-July/213157.html

Regards,
Paras July 4, 2017, 4:22 p.m. UTC | #2
On Tue, Jul 4, 2017 at 2:57 PM, Nicolas George <george@nsup.org> wrote:

> Le quartidi 14 messidor, an CCXXV, Paras Chadha a écrit :
> > Made all the changes suggested
> > Added a new function which reads a header FITS line safely. It also
> makes it more modular
> > Added an option for the user to enter the value to be used in place of
> BLANK pixels
> > Refactored code using macros to make it short
>
> Note: this is the commit message, it is there to stay, so it must
> contain information about the whole commit, not its differences with a
> previous draft of it. These, and any other kind of comment, belong
> between the first triple-dash line and the beginning of the actual diff,
> where Git places the shortstats of the diff.
>
> Also, lines should be split way below 80 characters.
>

Okay, will remember this the next time


>
> >
> > Signed-off-by: Paras Chadha <paraschadha18@gmail.com>
> > ---
> >  Changelog               |   1 +
> >  doc/general.texi        |   2 +
> >  libavcodec/Makefile     |   1 +
> >  libavcodec/allcodecs.c  |   1 +
> >  libavcodec/avcodec.h    |   1 +
> >  libavcodec/codec_desc.c |   8 +
> >  libavcodec/fitsdec.c    | 505 ++++++++++++++++++++++++++++++
> ++++++++++++++++++
> >  libavcodec/version.h    |   4 +-
> >  libavformat/img2.c      |   1 +
> >  9 files changed, 522 insertions(+), 2 deletions(-)
> >  create mode 100644 libavcodec/fitsdec.c
>
> Note: before reviewing the code, I think the boundary between the
> demuxer and the decoder needs to be properly defined. See my comments in
> this mail, near the end:
> https://ffmpeg.org/pipermail/ffmpeg-devel/2017-July/213157.html


okay


>
>
> Regards,
>
> --
>   Nicolas George
>
diff mbox

Patch

diff --git a/Changelog b/Changelog
index a8726c6..2c2bdec 100644
--- a/Changelog
+++ b/Changelog
@@ -26,6 +26,7 @@  version <next>:
   --x86asmexe=yasm to configure to restore the old behavior.
 - additional frame format support for Interplay MVE movies
 - support for decoding through D3D11VA in ffmpeg
+- FITS demuxer and decoder

 version 3.3:
 - CrystalHD decoder moved to new decode API
diff --git a/doc/general.texi b/doc/general.texi
index 8f582d5..c00ce32 100644
--- a/doc/general.texi
+++ b/doc/general.texi
@@ -591,6 +591,8 @@  following image formats are supported:
     @tab Digital Picture Exchange
 @item EXR          @tab   @tab X
     @tab OpenEXR
+@item FITS         @tab   @tab X
+    @tab Flexible Image Transport System
 @item JPEG         @tab X @tab X
     @tab Progressive JPEG is not supported.
 @item JPEG 2000    @tab X @tab X
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index b440a00..729e95e 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -291,6 +291,7 @@  OBJS-$(CONFIG_FFV1_DECODER)            += ffv1dec.o ffv1.o
 OBJS-$(CONFIG_FFV1_ENCODER)            += ffv1enc.o ffv1.o
 OBJS-$(CONFIG_FFWAVESYNTH_DECODER)     += ffwavesynth.o
 OBJS-$(CONFIG_FIC_DECODER)             += fic.o
+OBJS-$(CONFIG_FITS_DECODER)            += fitsdec.o
 OBJS-$(CONFIG_FLAC_DECODER)            += flacdec.o flacdata.o flac.o
 OBJS-$(CONFIG_FLAC_ENCODER)            += flacenc.o flacdata.o flac.o vorbis_data.o
 OBJS-$(CONFIG_FLASHSV_DECODER)         += flashsv.o
diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
index 0243f47..a4cfd80 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -192,6 +192,7 @@  static void register_all(void)
     REGISTER_ENCDEC (FFV1,              ffv1);
     REGISTER_ENCDEC (FFVHUFF,           ffvhuff);
     REGISTER_DECODER(FIC,               fic);
+    REGISTER_DECODER(FITS,              fits);
     REGISTER_ENCDEC (FLASHSV,           flashsv);
     REGISTER_ENCDEC (FLASHSV2,          flashsv2);
     REGISTER_DECODER(FLIC,              flic);
diff --git a/libavcodec/avcodec.h b/libavcodec/avcodec.h
index b697afa..8eba460 100644
--- a/libavcodec/avcodec.h
+++ b/libavcodec/avcodec.h
@@ -447,6 +447,7 @@  enum AVCodecID {
     AV_CODEC_ID_SRGC,
     AV_CODEC_ID_SVG,
     AV_CODEC_ID_GDV,
+    AV_CODEC_ID_FITS,

     /* various PCM "codecs" */
     AV_CODEC_ID_FIRST_AUDIO = 0x10000,     ///< A dummy id pointing at the start of audio codecs
diff --git a/libavcodec/codec_desc.c b/libavcodec/codec_desc.c
index cf1246e..0112517 100644
--- a/libavcodec/codec_desc.c
+++ b/libavcodec/codec_desc.c
@@ -1464,6 +1464,14 @@  static const AVCodecDescriptor codec_descriptors[] = {
                      AV_CODEC_PROP_LOSSLESS,
     },
     {
+        .id        = AV_CODEC_ID_FITS,
+        .type      = AVMEDIA_TYPE_VIDEO,
+        .name      = "fits",
+        .long_name = NULL_IF_CONFIG_SMALL("Flexible Image Transport System"),
+        .props     = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSY |
+                     AV_CODEC_PROP_LOSSLESS,
+    },
+    {
         .id        = AV_CODEC_ID_GIF,
         .type      = AVMEDIA_TYPE_VIDEO,
         .name      = "gif",
diff --git a/libavcodec/fitsdec.c b/libavcodec/fitsdec.c
new file mode 100644
index 0000000..4efc675
--- /dev/null
+++ b/libavcodec/fitsdec.c
@@ -0,0 +1,505 @@ 
+/*
+ * FITS image decoder
+ * Copyright (c) 2017 Paras Chadha
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * FITS image decoder
+ *
+ * Specification: https://fits.gsfc.nasa.gov/fits_standard.html Version 3.0
+ *
+ * Support all 2d images alongwith, bzero, bscale and blank keywords.
+ * RGBA images are supported as NAXIS3 = 3 or 4 i.e. Planes in RGBA order. Also CTYPE = 'RGB ' should be present.
+ * Also to interpret data, values are linearly scaled using min-max scaling but not RGB images.
+ */
+
+#include "avcodec.h"
+#include "internal.h"
+#include <float.h>
+#include "libavutil/intreadwrite.h"
+#include "libavutil/intfloat.h"
+#include "libavutil/dict.h"
+#include "libavutil/opt.h"
+
+typedef struct FITSContext {
+    const AVClass *class;
+    int blank_val;
+} FITSContext;
+
+/**
+ * Structure to store the header keywords in FITS file
+ */
+typedef struct FITSHeader {
+    char simple;
+    int bitpix;
+    int64_t blank;
+    int blank_found;
+    int naxis;
+    int naxisn[3];
+    int rgb; /**< 1 if file contains RGB image, 0 otherwise */
+    double bscale;
+    double bzero;
+    double data_min;
+    double data_max;
+} FITSHeader;
+
+/**
+ * Calculate the data_min and data_max values from the data.
+ * This is called if the values are not present in the header.
+ * @param ptr8 pointer to the data
+ * @param header pointer to the header
+ * @param end pointer to end of packet
+ * @return 0 if calculated successfully otherwise AVERROR_INVALIDDATA
+ */
+static int fill_data_min_max(const uint8_t * ptr8, FITSHeader * header, const uint8_t * end)
+{
+    uint8_t t8;
+    int16_t t16;
+    int32_t t32;
+    int64_t t64;
+    float tflt;
+    double tdbl;
+    int i, j;
+
+    header->data_min = DBL_MAX;
+    header->data_max = DBL_MIN;
+    switch (header->bitpix) {
+    #define case_n(a, t, rd) \
+        case a: \
+            for (i = 0; i < header->naxisn[1]; i++) { \
+                for (j = 0; j < header->naxisn[0]; j++) { \
+                    t = rd; \
+                    if (!header->blank_found || t != header->blank) { \
+                        if (t > header->data_max) \
+                            header->data_max = t; \
+                        if (t < header->data_min) \
+                            header->data_min = t; \
+                    } \
+                    ptr8 += abs(a) >> 3; \
+                } \
+            } \
+            break
+
+        case_n(-64, tdbl, av_int2double(AV_RB64(ptr8)));
+        case_n(-32, tflt, av_int2float(AV_RB32(ptr8)));
+        case_n(8, t8, ptr8[0]);
+        case_n(16, t16, AV_RB16(ptr8));
+        case_n(32, t32, AV_RB32(ptr8));
+        case_n(64, t64, AV_RB64(ptr8));
+        default:
+            return AVERROR_INVALIDDATA;
+    }
+    return 0;
+}
+
+/**
+ * Extract keyword and value from a header line (80 bytes) and store them in keyword and value strings respectively
+ * @param ptr8 pointer to the data
+ * @param keyword pointer to the char array in which keyword is to be stored
+ * @param value pointer to the char array in which value is to be stored
+ * @param end pointer to end of packet
+ * @return 0 if calculated successfully otherwise AVERROR_INVALIDDATA
+ */
+static int read_keyword_value(const uint8_t * ptr8, char * keyword, char * value, const uint8_t * end)
+{
+    int i;
+
+    if (end - ptr8 < 80)
+        return AVERROR_INVALIDDATA;
+
+    for (i = 0; i < 8 && ptr8[i] != ' '; i++) {
+        keyword[i] = ptr8[i];
+    }
+    keyword[i] = '\0';
+
+    if (ptr8[8] == '=') {
+        i = 10;
+        while (i < 80 && ptr8[i] == ' ') {
+            i++;
+        }
+
+        if (i < 80) {
+            *value++ = ptr8[i];
+            i++;
+            if (ptr8[i-1] == '\'') {
+                for (; i < 80 && ptr8[i] != '\''; i++) {
+                    *value++ = ptr8[i];
+                }
+                *value++ = '\'';
+            } else if (ptr8[i-1] == '(') {
+                for (; i < 80 && ptr8[i] != ')'; i++) {
+                    *value++ = ptr8[i];
+                }
+                *value++ = ')';
+            } else {
+                for (; i < 80 && ptr8[i] != ' ' && ptr8[i] != '/'; i++) {
+                    *value++ = ptr8[i];
+                }
+            }
+        }
+    }
+    *value = '\0';
+    return 0;
+}
+
+/**
+ * Read the fits header and store the values in FITSHeader pointed by header
+ * @param avctx AVCodec context
+ * @param ptr pointer to pointer to the data
+ * @param header pointer to the FITSHeader
+ * @param end pointer to end of packet
+ * @param meta pointer to pointer to AVDictionary to store metadata
+ * @return 0 if calculated successfully otherwise AVERROR_INVALIDDATA
+ */
+static int fits_read_header(AVCodecContext *avctx, const uint8_t **ptr, FITSHeader * header,
+                            const uint8_t * end, AVDictionary **meta)
+{
+    const uint8_t *ptr8 = *ptr;
+    int lines_read = 0, i, dim_no, data_min_found = 0, data_max_found = 0, ret;
+    int64_t t, size = 1;
+    double d;
+    AVDictionary *metadata = NULL;
+    char keyword[10], value[72];
+
+    header->blank_found = 0;
+    header->bscale = 1.0;
+    header->bzero = 0;
+    header->rgb = 0;
+
+    if ((ret = read_keyword_value(ptr8, keyword, value, end)) < 0)
+        return ret;
+    ptr8 += 80;
+    lines_read++;
+
+    if (!strncmp(keyword, "SIMPLE", 6)) {
+        header->simple = value[0];
+        if (header->simple == 'F') {
+            av_log(avctx, AV_LOG_WARNING, "not a standard FITS file\n");
+        } else if (header->simple != 'T') {
+            av_log(avctx, AV_LOG_ERROR, "invalid SIMPLE value, SIMPLE = %c\n", header->simple);
+            return AVERROR_INVALIDDATA;
+        }
+    } else if (strncmp(keyword, "XTENSION", 8) || strncmp(value, "'IMAGE   '", 10)) {
+        av_log(avctx, AV_LOG_ERROR, "missing SIMPLE keyword or invalid XTENSION\n");
+        return AVERROR_INVALIDDATA;
+    }
+
+    av_dict_set(&metadata, keyword, value, 0);
+
+    if ((ret = read_keyword_value(ptr8, keyword, value, end)) < 0)
+        return ret;
+    ptr8 += 80;
+    lines_read++;
+
+    if (!strncmp(keyword, "BITPIX", 6) && sscanf(value, "%d", &header->bitpix) != 1) {
+        av_log(avctx, AV_LOG_ERROR, "missing BITPIX or invalid value of BITPIX, found %s = %s\n", keyword, value);
+        return AVERROR_INVALIDDATA;
+    }
+
+    size = abs(header->bitpix) >> 3;
+    av_dict_set(&metadata, keyword, value, 0);
+
+    if ((ret = read_keyword_value(ptr8, keyword, value, end)) < 0)
+        return ret;
+    ptr8 += 80;
+    lines_read++;
+
+    if (!strncmp(keyword, "NAXIS", 5) && sscanf(value, "%d", &header->naxis) != 1) {
+        av_log(avctx, AV_LOG_ERROR, "missing NAXIS or invalid value of NAXIS, found %s = %s\n", keyword, value);
+        return AVERROR_INVALIDDATA;
+    }
+
+    if (!header->naxis) {
+        av_log(avctx, AV_LOG_ERROR, "No image data found, NAXIS = %d\n", header->naxis);
+        return AVERROR_INVALIDDATA;
+    }
+
+    if (header->naxis != 2 && header->naxis != 3) {
+        av_log(avctx, AV_LOG_ERROR, "unsupported number of dimensions, NAXIS = %d\n", header->naxis);
+        return AVERROR_INVALIDDATA;
+    }
+
+    av_dict_set(&metadata, keyword, value, 0);
+
+    for (i = 0; i < header->naxis; i++) {
+
+        if ((ret = read_keyword_value(ptr8, keyword, value, end)) < 0)
+            return ret;
+        ptr8 += 80;
+        lines_read++;
+
+        if (sscanf(keyword, "NAXIS%d", &dim_no) != 1) {
+            av_log(avctx, AV_LOG_ERROR, "missing NAXIS%d keyword", i+1);
+            return AVERROR_INVALIDDATA;
+        }
+
+        if (dim_no != i+1) {
+            av_log(avctx, AV_LOG_ERROR, "expected NAXIS%d keyword, found %s = %s\n", i+1, keyword, value);
+            return AVERROR_INVALIDDATA;
+        }
+
+        if (sscanf(value, "%d", &header->naxisn[i]) != 1) {
+            av_log(avctx, AV_LOG_ERROR, "invalid value of NAXIS%d = %s\n", i+1, value);
+            return AVERROR_INVALIDDATA;
+        }
+
+        av_dict_set(&metadata, keyword, value, 0);
+        size *= header->naxisn[i];
+
+        if (size <= 0) {
+            av_log(avctx, AV_LOG_ERROR, "unsupported size of FITS image");
+            return AVERROR_INVALIDDATA;
+        }
+    }
+
+    if ((ret = read_keyword_value(ptr8, keyword, value, end)) < 0)
+        return ret;
+    ptr8 += 80;
+    lines_read++;
+
+    while (strncmp(keyword, "END", 3)) {
+        if (!strncmp(keyword, "BLANK", 5) && sscanf(value, "%ld", &t) == 1) {
+            header->blank = t;
+            header->blank_found = 1;
+        } else if (!strncmp(keyword, "BSCALE", 6) && sscanf(value, "%lf", &d) == 1) {
+            header->bscale = d;
+        } else if (!strncmp(keyword, "BZERO", 5) && sscanf(value, "%lf", &d) == 1) {
+            header->bzero = d;
+        } else if (!strncmp(keyword, "DATAMAX", 7) && sscanf(value, "%lf", &d) == 1) {
+            data_max_found = 1;
+            header->data_max = d;
+        } else if (!strncmp(keyword, "DATAMIN", 7) && sscanf(value, "%lf", &d) == 1) {
+            data_min_found = 1;
+            header->data_min = d;
+        } else if (!strncmp(keyword, "CTYPE3", 6) && !strncmp(value, "'RGB", 4)) {
+            header->rgb = 1;
+            if (header->naxis != 3 || (header->naxisn[2] != 3 && header->naxisn[2] != 4)) {
+                av_log(avctx, AV_LOG_ERROR, "File contains RGB image but NAXIS = %d and NAXIS3 = %d\n", header->naxis, header->naxisn[2]);
+                return AVERROR_INVALIDDATA;
+            }
+        }
+
+        av_dict_set(&metadata, keyword, value, 0);
+
+        if ((ret = read_keyword_value(ptr8, keyword, value, end)) < 0)
+            return ret;
+        ptr8 += 80;
+        lines_read++;
+    }
+
+    if (!header->rgb && header->naxis != 2) {
+        av_log(avctx, AV_LOG_ERROR, "unsupported number of dimensions, NAXIS = %d\n", header->naxis);
+        return AVERROR_INVALIDDATA;
+    }
+
+    if (header->blank_found && (header->bitpix == -32 || header->bitpix == -64)) {
+        av_log(avctx, AV_LOG_WARNING, "BLANK keyword found but BITPIX = %d\n. Ignoring BLANK", header->bitpix);
+        header->blank_found = 0;
+    }
+
+    lines_read %= 36;
+
+    t = ((36 - lines_read) % 36) * 80;
+    if (end - ptr8 < t)
+        return AVERROR_INVALIDDATA;
+    ptr8 += t;
+    *ptr = ptr8;
+
+    if (end - ptr8 < size)
+        return AVERROR_INVALIDDATA;
+
+    if (!header->rgb && (!data_min_found || !data_max_found)) {
+        if ((ret = fill_data_min_max(ptr8, header, end)) < 0) {
+            av_log(avctx, AV_LOG_ERROR, "invalid BITPIX, %d\n", header->bitpix);
+            return ret;
+        }
+    } else {
+        /*
+         * instead of applying bscale and bzero to every element, we can do inverse transformation on data_min and
+         * data_max
+         */
+        header->data_min = (header->data_min - header->bzero) / header->bscale;
+        header->data_max = (header->data_max - header->bzero) / header->bscale;
+    }
+
+    *meta = metadata;
+    return 0;
+}
+
+static int fits_decode_frame(AVCodecContext *avctx, void *data, int *got_frame, AVPacket *avpkt)
+{
+    AVFrame *p=data;
+    const uint8_t *ptr8 = avpkt->data, *end;
+    uint8_t t8;
+    int16_t t16;
+    int32_t t32;
+    int64_t t64;
+    float   tflt;
+    double  tdbl;
+    int ret, i, j;
+    uint8_t *dst8;
+    uint16_t *dst16;
+    uint32_t *dst32;
+    uint64_t *dst64, size, r, g, b, a, t;
+    FITSHeader header;
+    FITSContext * fitsctx = avctx->priv_data;
+
+    end = ptr8 + avpkt->size;
+    if ((ret = fits_read_header(avctx, &ptr8, &header, end, &p->metadata)) < 0)
+        return ret;
+
+    size = (header.naxisn[0]) * (header.naxisn[1]);
+
+    if (header.rgb) {
+        if (header.bitpix == 8) {
+            avctx->pix_fmt = AV_PIX_FMT_RGB32;
+        } else if (header.bitpix == 16) {
+            avctx->pix_fmt = AV_PIX_FMT_RGBA64;
+        } else {
+            av_log(avctx, AV_LOG_ERROR, "unsupported BITPIX = %d\n", header.bitpix);
+            return AVERROR_INVALIDDATA;
+        }
+    } else {
+        if (header.bitpix == 8) {
+            avctx->pix_fmt = AV_PIX_FMT_GRAY8;
+        } else {
+            avctx->pix_fmt = AV_PIX_FMT_GRAY16;
+        }
+    }
+
+    if ((ret = ff_set_dimensions(avctx, header.naxisn[0], header.naxisn[1])) < 0)
+        return ret;
+
+    if ((ret = ff_get_buffer(avctx, p, 0)) < 0)
+        return ret;
+
+    /*
+     * FITS stores images with bottom row first. Therefore we have
+     * to fill the image from bottom to top.
+     */
+    if (header.rgb) {
+        switch(header.bitpix) {
+        #define case_rgb(cas, dst, type, dref) \
+            case cas: \
+                for (i = 0; i < avctx->height; i++) { \
+                    dst = (type *) (p->data[0] + (avctx->height-i-1)* p->linesize[0]); \
+                    for (j = 0; j < avctx->width; j++) { \
+                        if (header.naxisn[2] == 4) { \
+                            t = dref(ptr8 + size * 3); \
+                            if (!header.blank_found || t != header.blank) { \
+                                t = t * header.bscale + header.bzero; \
+                            } else { \
+                                t = fitsctx->blank_val; \
+                            } \
+                            a = t << (cas * 3); \
+                        } else { \
+                            a = (((1ULL << cas) - 1) << (cas * 3)); \
+                        } \
+                        t = dref(ptr8); \
+                        if (!header.blank_found || t != header.blank) { \
+                            t = t * header.bscale + header.bzero; \
+                        } else { \
+                            t = fitsctx->blank_val; \
+                        } \
+                        r = t << (cas * 2); \
+                        t = dref(ptr8 + size); \
+                        if (!header.blank_found || t != header.blank) { \
+                            t = t * header.bscale + header.bzero; \
+                        } else { \
+                            t = fitsctx->blank_val; \
+                        } \
+                        g = t << cas; \
+                        t = dref(ptr8 + size * 2); \
+                        if (!header.blank_found || t != header.blank) { \
+                            t = t * header.bscale + header.bzero; \
+                        } else { \
+                            t = fitsctx->blank_val; \
+                        } \
+                        b = t; \
+                        *dst++ = ((type)a) | ((type)r) | ((type)g) | ((type)b); \
+                        ptr8 += cas >> 3; \
+                    } \
+                } \
+                break
+
+            case_rgb(8, dst32, uint32_t, *);
+            case_rgb(16, dst64, uint64_t, AV_RB16);
+        }
+    } else {
+        switch (header.bitpix) {
+        #define case_gray(cas, dst, type, t, rd) \
+            case cas: \
+                for (i = 0; i < avctx->height; i++) { \
+                    dst = (type *) (p->data[0] + (avctx->height-i-1)* p->linesize[0]); \
+                    for (j = 0; j < avctx->width; j++) { \
+                        t = rd; \
+                        if (!header.blank_found || t != header.blank) { \
+                            t = ((t - header.data_min) * ((1 << (sizeof(type) * 8)) - 1)) / (header.data_max - header.data_min); \
+                        } else { \
+                            t = fitsctx->blank_val; \
+                        } \
+                        *dst++ = (type) t; \
+                        ptr8 += abs(cas) >> 3; \
+                    } \
+                } \
+                break
+
+            case_gray(-64, dst16, uint16_t, tdbl, av_int2double(AV_RB64(ptr8)));
+            case_gray(-32, dst16, uint16_t, tflt, av_int2float(AV_RB32(ptr8)));
+            case_gray(8, dst8, uint8_t, t8, ptr8[0]);
+            case_gray(16, dst16, uint16_t, t16, AV_RB16(ptr8));
+            case_gray(32, dst16, uint16_t, t32, AV_RB32(ptr8));
+            case_gray(64, dst16, uint16_t, t64, AV_RB64(ptr8));
+            default:
+                av_log(avctx, AV_LOG_ERROR, "invalid BITPIX, %d\n", header.bitpix);
+                return AVERROR_INVALIDDATA;
+        }
+    }
+
+    p->key_frame = 1;
+    p->pict_type = AV_PICTURE_TYPE_I;
+
+    *got_frame = 1;
+
+    return avpkt->size;
+}
+
+static const AVOption fits_options[] = {
+    { "blank_value", "value that is used to replace BLANK pixels in data array", offsetof(FITSContext, blank_val), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 65535, AV_OPT_FLAG_DECODING_PARAM | AV_OPT_FLAG_VIDEO_PARAM},
+    { NULL },
+};
+
+static const AVClass fits_decoder_class = {
+    .class_name = "FITS decoder",
+    .item_name  = av_default_item_name,
+    .option     = fits_options,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
+AVCodec ff_fits_decoder = {
+    .name           = "fits",
+    .type           = AVMEDIA_TYPE_VIDEO,
+    .id             = AV_CODEC_ID_FITS,
+    .priv_data_size = sizeof(FITSContext),
+    .decode         = fits_decode_frame,
+    .capabilities   = AV_CODEC_CAP_DR1,
+    .long_name      = NULL_IF_CONFIG_SMALL("Flexible Image Transport System"),
+    .priv_class     = &fits_decoder_class
+};
diff --git a/libavcodec/version.h b/libavcodec/version.h
index 0661526..5b99785 100644
--- a/libavcodec/version.h
+++ b/libavcodec/version.h
@@ -28,8 +28,8 @@ 
 #include "libavutil/version.h"

 #define LIBAVCODEC_VERSION_MAJOR  57
-#define LIBAVCODEC_VERSION_MINOR 100
-#define LIBAVCODEC_VERSION_MICRO 102
+#define LIBAVCODEC_VERSION_MINOR 101
+#define LIBAVCODEC_VERSION_MICRO 100

 #define LIBAVCODEC_VERSION_INT  AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
                                                LIBAVCODEC_VERSION_MINOR, \
diff --git a/libavformat/img2.c b/libavformat/img2.c
index 8432cc0..e405df8 100644
--- a/libavformat/img2.c
+++ b/libavformat/img2.c
@@ -80,6 +80,7 @@  const IdStrMap ff_img_tags[] = {
     { AV_CODEC_ID_XPM,        "xpm"      },
     { AV_CODEC_ID_XFACE,      "xface"    },
     { AV_CODEC_ID_XWD,        "xwd"      },
+    { AV_CODEC_ID_FITS,       "fits"     },
     { AV_CODEC_ID_NONE,       NULL       }
 };