SASGIS

Веб-картография и навигация


View Issue Details Jump to Notes ] Issue History ] Print ]
IDProjectCategoryView StatusDate SubmittedLast Update
0001203SAS.Планета[All Projects] Хотелкаpublic06-03-2012 15:1520-04-2020 21:11
ReporterParasite 
Assigned To 
PrioritynormalSeveritytweakReproducibilityN/A
StatusconfirmedResolutionopen 
PlatformWindowsOSServerOS Version2003
Product Version110418 
Target Version30xxxx.VipFixed in Version 
Summary0001203: Сделать lossless склейку в JPG (с использованием libjpeg)
DescriptionБыло бы неплохо(с), если бы САС при сведении в ЖПЕГ когда-нибудь научился юзать широко известную в узких кругах библиотеку libjpeg
http://en.wikipedia.org/wiki/Libjpeg
одной из фич которой является _беспотерьная_ трансформация JPG-изображений (в данном случае - тайлов) при операциях на глобальном полотне. Эту фичу юзает, например, общзеизвестная тулза JPEGtran (по ссылке выше), позволяющая делать сколь угодно многочисленные операции над JPG-контентом без ре-энкодинга на каждой стадии (lossless).
Additional InformationPS: юзаю JpegTran долгое время при сведении из, например, Zoomify. Тулза прекрастно вызывается из кастомных скриптов даже под виндой,
http://jpegclub.org/jpegtran/

НИКАКОЙ деградации\артефактов на результирующем изображении по сравнении с исходным - не было замечено даже при _многотысячном_ вызове jpegtran при вклеивании каждого отдельного тайла (поодиночке, в циклах по Х\Y) в общее полотно.
Tagsсклейка, скрипты
Attached Filesrar file icon jpegtran.rar [^] (47,135 bytes) 13-03-2012 13:30
zip file icon jtest.zip [^] (216,519 bytes) 23-04-2012 06:16

- Relationships

-  Notes
(0005866)
zed (manager)
06-03-2012 15:19
edited on: 06-03-2012 15:25

САС и так использует libjpeg между прочим.

(0005867)
Parasite (administrator)
06-03-2012 15:40
edited on: 06-03-2012 15:41

Уже?
В составе последней имеющейся у меня сборки вижу ijl15.dll, но не вижу ничего похожего на libjpg.

Сейчас скачаю последнюю ночнушку, гляну. Если токи да - то пардон.

(0005880)
Tolik (manager)
07-03-2012 04:34
edited on: 07-03-2012 04:39

Инфа 100%

(если память не изменяет, это Zed сделал, поэтому перевёл в resolved (zed))

P.S. Кстати, ijl15.dll в релизе и бете нет.

(0005882)
zed (manager)
07-03-2012 05:03

Использовать-то использует, но не lossless (если вообще возможна безпотерьная склейка жпегов).
(0005887)
Tolik (manager)
07-03-2012 05:23

Почему ж невозможна? Для такого красивого размерчика 256х256 всё должно отлично клеиться.

Рановато закрыл? Сделаете lossless слейку?
(0005888)
zed (manager)
07-03-2012 05:29

Ну, если Parasite покажет свои "скрипты" при помощи которых он клеит жпеги (lossless вариант), то можно подумать. Тем более, что сорцы jpegtran'а есть и "ход мыслей" можно попробовать повторить в САСе.
(0005901)
Parasite (administrator)
07-03-2012 08:39

>P.S. Кстати, ijl15.dll в релизе и бете нет.
Ключевая фраза выше "в составе последней _имеющейся у меня_".
Про текущие бетки - см.про нераспаковываемость 7z без необходимости обновлять пакер (и полсистемы зависимостей к нему).

>если Parasite покажет свои "скрипты" при помощи которых он клеит жпеги (lossless вариант)
А скрипт-то причем? Скрипт у меня просто вызывает сторонний жпегтран в фоне столько раз, сколько нужно. Раз - значит раз. Стотыщ-значит стотыщ, благо что работает оно весьма быстро, да и форкается на ура.
Нужны были ключики вызова жпегтрана, или как?
(0005905)
zed (manager)
07-03-2012 10:25

Нужен алгоритм, чтобы мы пришли от состояния "у нас на винте кучка jpeg" до "у нас на винте lossless снимок". Скажем, на примере склейки 4-х тайлов на 2-ом зуме. И чем подробнее, тем лучше.
(0005909)
Parasite (administrator)
07-03-2012 11:08

Да не вопрос.
Вот на примере последней lossless-работы (http://sasgis.org/forum/viewtopic.php?f=21&t=1905&start=0)

на Питоне. Качает тайлы (многопоточно, с очередью и контролем ерроров, по зумифай-алго), сводит используя пару темповых жпегтранов через свитч друг друга, результат - один жпег максимального уровня.
По результату на ссылке выше - отработало примерно за сутки. Ради лосслесса лично я вполне готов потерпеть.
-----------------------------
import sys
import time, os
import re
import urllib.request, urllib.parse
import optparse
import tempfile, shutil
import subprocess
import queue
import threading

from math import ceil, floor

def main():
    (opts, args) = parser.parse_args()
    if (opts.url is None):
        parser.print_help()
        exit(-1)

    if (opts.out is None):
        print("ERR: The output file '-o' must be given\n")
        parser.print_help()
        exit(-1)
    Dezoomify(opts)

def urlConcat(url1, url2):
    if url1[-1] == '/':
        url1 = url1[0:-1]

    if url2[0] == '/':
        url2 = url2[1:]

    return url1 + '/' + url2

def getFilePath(level, col, row, ext):
    name = str(level) + '-' + str(col) + '-' + str(row) + '.' + ext
    return name

def getUrl(url):
    req_headers = {
        'User-Agent': 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.13 (KHTML, like Gecko) Chrome/0.A.B.C Safari/525.13',
        'Referer': 'http://google.com'
        }
    request = urllib.request.Request(url, headers=req_headers)
    opener = urllib.request.build_opener()
    response = opener.open(request)
    code = response.code
    headers = response.headers
    contents = response.read().decode(errors='ignore')
    return code, headers, contents

class Dezoomify():

    def getImage(self, imageDir, outputDestination):

        def newDownloadThread():
            t = threading.Thread(target=downloader)
            t.daemon = True
            t.start()

        def downloader():
            if downloadQueue.empty():
                return
            url, col, row = downloadQueue.get()
            destination = tileName(col, row)
            if self.debug:
                print("\tINF: Downloading tile: " + destination)
            urllib.request.urlretrieve(url, destination)
            joinQueue.put((col, row))
            if not downloadQueue.empty():
                newDownloadThread()

        def tileName(col, row):
            return os.path.join(self.tileDir, str(col) + '_' + str(row) + '.' + self.ext)

        downloadQueue = queue.Queue()
        joinQueue = queue.Queue()
        for col in range(self.xTiles):
            for row in range(self.yTiles):

                if self.nodownload:
                    joinQueue.put((col, row))
                    continue

                tileIndex = self.getTileIndex(self.zoomLevel, col, row)
                tileGroup = tileIndex // self.tileSize

                if self.debug:
                    print(("\tINF: Adding image number (row, col) to queue: " + str(row).rjust(2) + ', ' + str(col).rjust(2) + ': Index: '+ str(tileIndex).rjust(3) + ', Tilegroup: %d'% tileGroup))

                filepath = getFilePath(self.zoomLevel, col, row, self.ext)
                url = imageDir + '/' + 'TileGroup%d' % tileGroup + '/' + filepath
                downloadQueue.put((url, col, row))
        for i in range(self.nthreads):
            newDownloadThread()

        totalTiles = self.xTiles * self.yTiles
        numJoined = 0

        tmpimgs = [None, None]
        for i in range(2):
            fhandle = tempfile.NamedTemporaryFile(suffix='.jpg', delete=False)
            tmpimgs[i] = fhandle.name
            fhandle.close()
            if self.debug:
                print( '\tINF: Created temporary image file: ' + tmpimgs[i] )
        activeTmp = 0

        while numJoined < totalTiles:
            while joinQueue.empty():
                if threading.active_count() == 1 and joinQueue.empty():
                    print("ERR: Tile downloading stopped prematurely!")
                    os.unlink(tmpimgs[0])
                    os.unlink(tmpimgs[1])
                    return
                time.sleep(0.05)

            col, row = joinQueue.get()

            if numJoined == 0:
                cmd = [self.jpegtran, '-copy', 'all', '-crop', '%dx%d+0+0' % (self.width, self.height), '-outfile', tmpimgs[activeTmp], tileName(col, row)]
                subprocess.call(cmd)

            if self.debug:
                print( '\tINF: Adding tile (row ' + str(row) +', col ' + str(col) + ') to the image' )
            cmd = [self.jpegtran, '-copy', 'all', '-drop', '+%d+%d' % (col*self.tileSize, row*self.tileSize), tileName(col, row), '-outfile', tmpimgs[(activeTmp+1)%2], tmpimgs[activeTmp]]
            subprocess.call(cmd)

            activeTmp = (activeTmp+1) % 2
            numJoined += 1

        cmd = [self.jpegtran, '-copy', 'all', '-optimize', '-outfile', outputDestination, tmpimgs[activeTmp]]
        subprocess.call(cmd)

        os.unlink(tmpimgs[0])
        os.unlink(tmpimgs[1])

    def getImageDirectory(self, url):
        try:
            content = urllib.request.urlopen(url).read().decode(errors='ignore')
        except Exception:
            print(("ERR: Specified directory not found. Check the URL.\nException: %s " % sys.exc_info()[1]))
            sys.exit()

        imagePath = None
        m = re.search('zoomifyImagePath=([^\'"&]*)[\'"&]', content)
        if m:
            imagePath = m.group(1)

        if not imagePath:
            m = re.search('ZoomifyCache/[^\'"&.]+\\.\\d+x\\d+', content)
            if m:
                imagePath = m.group(0)

        if not imagePath:
            print("ERR: Source directory not found. Ensure the given URL contains a Zoomify object.")
            sys.exit()
        else:
            print(('INF: Found zoomifyImagePath: %s' % imagePath))

            netloc = urllib.parse.urlparse(imagePath).netloc
            if not netloc:
                parsedURL = urllib.parse.urlparse(url)
                pathParts = parsedURL.path.split('/')
                m = re.search('\.', pathParts[-1])
                if m:
                    del(pathParts[-1])
                path = '/'.join(pathParts)
                url = urllib.parse.urlunparse([ parsedURL.scheme, parsedURL.netloc, path, None, None, None])

                imageDir = urlConcat(url, imagePath)
            else:
                imageDir = imagePath
            if self.debug:
                print(('INF: Found image directory: ' + imageDir))
            return imageDir

    def getMaxZoom(self):
        width = int(ceil(self.maxWidth/float(self.tileSize)))
        height = int(ceil(self.maxHeight/float(self.tileSize)))
        self.levels = []
        while True:
            self.levels.append((width, height))
            if width == 1 and height == 1:
                break
            else:
                width = int(ceil(width/2.0))
                height = int(ceil(height/2.0))
        self.levels.reverse()
    def getProperties(self, imageDir, zoomLevel):
        xmlUrl = imageDir + '/ImageProperties.xml'
        code, headers, content = getUrl(xmlUrl)
        print (xmlUrl)
        m = re.search('WIDTH="(\d+)"', content)
        if m:
            self.maxWidth = int(m.group(1))
        else:
            print('ERR: Width not found in ImageProperties.xml')
            sys.exit()
        m = re.search('HEIGHT="(\d+)"', content)
        if m:
            self.maxHeight = int(m.group(1))
        else:
            print('ERR: Height not found in ImageProperties.xml')
            sys.exit()
        m = re.search('TILESIZE="(\d+)"', content)
        if m:
            self.tileSize = int(m.group(1))
        else:
            print('ERR: Tile size not found in ImageProperties.xml')
            sys.exit()
        self.getMaxZoom()
        self.maxZoom = len(self.levels)
        if not zoomLevel:
            self.zoomLevel = self.maxZoom-1
        else:
            zoomLevel = int(zoomLevel)
            if zoomLevel < self.maxZoom and zoomLevel >= 0:
                self.zoomLevel = zoomLevel
            else:
                self.zoomLevel = self.maxZoom-1
                if self.debug:
                    print(('ERR: the requested zoom level is not available, defaulting to maximum (%d)' % self.zoomLevel ))
        self.width = self.maxWidth / 2**(self.maxZoom - self.zoomLevel - 1)
        self.height = self.maxHeight / 2**(self.maxZoom - self.zoomLevel - 1)
        self.maxxTiles = self.levels[-1][0]
        self.maxyTiles = self.levels[-1][1]
        self.xTiles = self.levels[self.zoomLevel][0]
        self.yTiles = self.levels[self.zoomLevel][1]
        if self.debug:
            print(( '\tMax zoom level: %d (working zoom level: %d)' % (self.maxZoom-1, self.zoomLevel) ))
            print(( '\tWidth (overall): %d (at given zoom level: %d)' % (self.maxWidth, self.width) ))
            print(( '\tHeight (overall): %d (at given zoom level: %d)' % (self.maxHeight, self.height )))
            print(( '\tTile size: %d' % self.tileSize ))
            print(( '\tWidth (in tiles): %d (at given level: %d)' % (self.maxxTiles, self.xTiles) ))
            print(( '\tHeight (in tiles): %d (at given level: %d)' % (self.maxyTiles, self.yTiles) ))
            print(( '\tTotal tiles: %d (to be retreived: %d)' % (self.maxxTiles * self.maxyTiles, self.xTiles * self.yTiles)))

    def getTileIndex(self, level, x, y):
        index = x + y * int(ceil( floor(self.width/pow(2, self.maxZoom - level - 1)) / self.tileSize ) )

        for i in range(1, level+1):
            index += int(ceil( floor(self.width /pow(2, self.maxZoom - i)) / self.tileSize ) ) * \
                     int(ceil( floor(self.height/pow(2, self.maxZoom - i)) / self.tileSize ) )
        return index
    def getUrls(self, opts):
        if not opts.list:
            self.imageDirs = [ opts.url ]
            self.outNames = [ self.out ]
        else:
            listFile = open( opts.url, 'r')
            self.imageDirs = []
            self.outNames = []
            i = 1
            for line in listFile:
                line = line.strip().split(' ', 1)
                if len(line) == 1:
                    root, ext = os.path.splitext(self.out)
                    self.outNames.append(root + '%03d' % i + ext)
                    i += 1
                elif len(line) == 2:
                    m = re.search('\\.' + self.ext + '$', line[1])
                    if not m:
                        line[1] += '.' + self.ext
                   self.outNames.append(os.path.join(os.path.dirname(self.out), line[1]))
                else:
                    continue

                self.imageDirs.append( line[0] )
    def setupDirectory(self, destination):
        if self.store:
            root, ext = os.path.splitext(destination)
            if not os.path.exists(root):
                if self.debug:
                    print(( 'INF: Creating image storage directory: %s' % root))
                os.makedirs(root)
            self.tileDir = root
        else:
            self.tileDir = tempfile.mkdtemp(prefix='dezoomify_')
            if self.debug:
                print(( 'INF: Created temporary image storage directory: %s' % self.tileDir))
    def __init__(self, opts):
        self.debug = opts.debug
        self.tempo = opts.tempo
        self.ext = 'jpg'
        self.store = opts.store
        self.out = opts.out
        self.jpegtran = opts.jpegtran
        self.nodownload = opts.nodownload
        self.nthreads = int(opts.nthreads)
        if self.nodownload:
            self.store = True
        if self.tempo:
            tempfile.tempdir = opts.tempo
        self.getUrls(opts)
        i = 0
        for imageDir in self.imageDirs:
            destination = self.outNames[i]
            if not opts.base:
                imageDir = self.getImageDirectory(imageDir)
            self.getProperties(imageDir, opts.zoomLevel)
            self.setupDirectory(destination)
            self.getImage(imageDir, destination)
            if self.debug:
                print( 'INF: Dezoomifed image created and saved to ' + destination )
            if not self.store:
                shutil.rmtree(self.tileDir)
                if self.debug:
                    print( 'INF: Erased the temporary directory and its contents' )

            i += 1
if __name__ == "__main__":
    try:
        main()
    finally:
        None
==============================
Если коротко:
1. качаем все запланированные тайлы в кучу (отдельными потоками, не мешающими основному).
2. Создаем ЖПЕГ полотно размера равного результирующему изображению (в виде файла).
3. Создаем его копию.
4. Последовательно клеим (юзая жпегтран) каждый тайл в полотно, а результат пихая в копию полотна. По завершении жпегтрана - полотно и копию меняем местами. Повторять пока тайлы не кончатся.
5. Результат (один, самый последний жпег) сохраняем в указанное юзером место. Смываем все темпы. Всё.

PS: работает с любым зумифаем. Надеюсь. :)
(0005912)
zed (manager)
07-03-2012 11:20

Зачем копия полотна и почему мы их должны менять местами, после вклеивания каждого тайла?
(0005917)
Parasite (administrator)
07-03-2012 12:30

>Зачем копия полотна и почему мы их должны менять местами, после вклеивания каждого тайла?
Потому что standalone-JPEGtran работает как <source> <destination>. Читаем исходное полотно с первого, пишем после вклейки во второе - при следующем вызове <destination> первого должен стать <source> последующего.
(0005953)
zed (manager)
08-03-2012 11:06

>при следующем вызове <destination> первого должен стать <source> последующего
Да, но имена источника и приёмника могут совпадать.

Проверь, на всякий случай?
В скрипте вместо:
cmd = [self.jpegtran, '-copy', 'all', '-drop', '+%d+%d' % (col*self.tileSize, row*self.tileSize), tileName(col, row), '-outfile', tmpimgs[(activeTmp+1)%2], tmpimgs[activeTmp]]

сделать:
cmd = [self.jpegtran, '-copy', 'all', '-drop', '+%d+%d' % (col*self.tileSize, row*self.tileSize), tileName(col, row), '-outfile', tmpimgs[activeTmp], tmpimgs[activeTmp]]

и закомментить/удалить строчку:
activeTmp = (activeTmp+1) % 2
(0005954)
Parasite (administrator)
08-03-2012 11:39

>и закомментить/удалить строчку:
А на кой это всё вообще? Мы препарируем мой скрипт, или обучаем САС беспотерьке? Если второе - то функционал надо вводить в САС, а не звать сторонний жпегтран в данном случае. Ведь звать его придется ровно столько раз, сколько у нас тайлов на сведение - и распаковывать\запаковывать всё полотно в памяти, особенно если source с destination совпадать будут (придется еще и ждать снятия системных локов с постоянно перезаписываемого файла). В моем случае в зумифае число тайлов весьма небольшое как правило по сравнению с картами, и я зову жпегтран и ворочаю всё полотно исключительно в уме, до кучи и читая\записывая темпы на рам-диск - с зумифаем это пока что прокатывает, памяти пока что хватает, и можно открыть хоть сотню темпов, если в сотню потоков сводить...

В случае же САСа - и звать упаримся, и поворочать на среднестатистическом железе тоже не всегда сумеем. Мы же планируем чтобы работало у всех секретулек тоже, а не только у меня в памяти, так? Этот функционал имхо надо пихать в сводилку САСа, подсмотрев фичу в сорцах жпегтрана (например). Имхо.
(0006039)
Parasite (administrator)
13-03-2012 13:31

>Проверь, на всякий случай?
Приложил в шапку скомпиленный под винду жпегтран.
(0006418)
zed (manager)
11-04-2012 16:57
edited on: 11-04-2012 17:47

Облом. Функции crop и drop работают с жпегами в памяти, соответственно и клеить снимки больших разрешений не выйдет:

>c:\jtest>jpegtran -crop 32000x32000+0+0 tile.jpg crop.jpg
>Insufficient memory (case 4)

Для 32-х битной системы максимальное разрешение снимка будет где-то в районе 24k*24k pix (естественно, при достаточном размере памяти. Если будет нехватка памяти, то и того меньше).

Заставить их работать по другому, не представляется возможным.

Ввиду вышесказанного фича ещё интересна?

(0006441)
Parasite (administrator)
20-04-2012 09:46
edited on: 20-04-2012 11:02

>Insufficient memory (case 4)
>Для 32-х битной системы

Ну и нормально. 24Кх24К жпега с лослессом все же лучше чем 24Кх24К без оного - и будет достаточно для 95% задач (все равно ТАКОЙ жпег потом открывать будет проблематично, даже если и создастся), а кому надо действительно БОЛЬШИЕ losless полотна - будет подождать решения 295-го тикета вместе со мной. :)

(0006442)
vdemidov (manager)
20-04-2012 10:09

Кстати. Сейчас 295-й тикет сможет реализовать почти любой чайник где-то за пару часов. Там самое сложно нарисовать формочку выбора параметров склейки.
(0006443)
zed (manager)
20-04-2012 14:16

Хм, хоть это и склейка по сути, но фактически получается что мы экспортируем jpeg тайлы в файл, на подобии экспорта в zip и проч. Поэтому даже не знаю как это лучше сделать в сасе.
Выходит, что нужно делать класс-наследник от TThreadExportAbstract, а не от TThreadMapCombineBase. И соответственно, на страничке Экспорт, появится пункт JPEG (Lossless). При этом будут недоступны опции создания привязок и разбиения на части. Полная чехорда выходит.
(0006444)
Parasite (administrator)
20-04-2012 15:34

>Сейчас 295-й тикет сможет реализовать почти любой чайник
Подождем, пока он туда придет и реализует. :)

>Хм, хоть это и склейка по сути, но фактически получается что мы экспортируем jpeg тайлы в файл, на подобии экспорта в zip и проч.
Предлагаю на время отладки просто опцию в инишнике типа "JPEGlossless=true", при наличии которой - выводить ЛЮБОЙ сводимый сасом жпег через жпегтран. При отсутствии, соответственно - как обычно.
Потом, если все срастется и всем понравится - вывод через жпегтран можно оставить единственным. Я думаю - никто же против более качественных жпегов не будет? :)
(0006445)
zed (manager)
20-04-2012 19:10

Так-с, с безпотерьной склейкой разобрался. Кода оказалось с гулькин нос, правда обнаружилось, что libjpeg-turbo не в полной мере поддерживает losslless, так что пришлось заюзать официальный libjpeg. В итоге, добавятся 2 dll-ки: jpeg8.dll (libjpeg, 190k) и transupp.dll (API для losslless трансформаций, 20k). Осталось набраться мужества и прикрутить это всё к сасу.

>выводить ЛЮБОЙ сводимый сасом жпег через жпегтран
Ага, разогнался. Без потерь можно сводить жпеги с очень большими оговорками:
- тайлы в кэше должны быть в jpeg формате
- наложение слоёв/меток невозможно
- цветокоррекция недоступна
- операция ресурсоёмкая и отжирает много ОЗУ
Так что - только опционально, по галочке. И по дефолту галочка будет снята.
(0006446)
Parasite (administrator)
21-04-2012 06:23

>- тайлы в кэше должны быть в jpeg формате
Ну разумеется. Lossless такой lossless, что если изначально не жпег - без репака не обойтись.

>- наложение слоёв/меток невозможно
>- цветокоррекция недоступна
Кстати, да. Слона-то я и не приметил...в САСе оно вроде есть, но я ни разу не пользовался - потому и не обращал внимания на эти пункты.

>- операция ресурсоёмкая и отжирает много ОЗУ
Угу. В основном из-за ворочанья глобального полотна в памяти * число вклеек тайлов.

>Так что - только опционально, по галочке. И по дефолту галочка будет снята.
Как скажешь, начальнег. Не вопрос.
(0006459)
Parasite (administrator)
23-04-2012 05:02

Меня, собственно, беспокоит вопрос быстродействия этого всего на относительно больших склеиваемых листах.
Так как [стандартные] алгоритмы ворочают все полотно в памяти, а при выходе - пишут на диск (в жпег разумеется), то время всей склейки будет (постоянное подчитывание глобального растра+растягивание в памяти+вклейка+запись на диск) * число_тайлов_для_склейки. При использовании стандартных алгоритмов и стандартного же жпегтрана (того что в шапке) - весь процесс весьма и весьма печален даже при наличии 32Гб памяти и рамдиска. Например вот это изображение http://sasgis.org/forum/viewtopic.php?f=21&t=1905 из тайлов вышеуказанный скрипт+жпегтран сводили около суток непрерывной работы (правда включая и выкачку тайлов с интернетов, ну да там всего 105Мб...).

Если САС будет точно так же сводить сутками - то смысл фичи теряется к сожалению, с тем же успехом оно все сводится и сторонними скриптами еще вчера. То есть, придется таки переработать сам жпегтран хотя бы для того, чтобы он постоянно не читал\писал весь растр при обработке каждого тайла - а держал в памяти пока вся задача не будет выполнена и все тайлы не вклеятся...
Zed, ты уже проводил тесты быстродействия в связке с САСом?
(0006460)
zed (manager)
23-04-2012 06:15
edited on: 23-04-2012 06:15

Чисто синтетически: время склейки 22k*22k = ~ 7 часов (~ 3 сек. на тайл).
Убийственно медленно, по сравнению с обычной склейкой.

Ща приаттачу тестовую утиль, сможешь оценить на своей системе.

(0006461)
zed (manager)
23-04-2012 06:17
edited on: 23-04-2012 06:19

В аттаче, кстати, и сорцы есть, на случай если кто решит поучаствовать.

(0006465)
Parasite (administrator)
23-04-2012 10:21

>время склейки 22k*22k = ~ 7 часов (~ 3 сек. на тайл)
Охх, щщи....... :((( Не думал, что всё будет настолько печально....

Есть ли возможность изменить логику работы всего алгоритма - чтобы он не переписывал все полотно после к.тайла, а держал бы в памяти до окончания вклейки всех тайлов - и только тогда уже сливал в файл? Имхо это единственное, что глобально повлияет на быстродействие. Все же жпегтран сам по себе - коммандлайновая утиль и ей сам Аллах велел работать читать\отрабатывать\записывать\отваливаться, а у нас-то тут практически "потоковая вклейка от обеда и до пока_кэш_не_кончится"...:)

Если возможности поднять быстродействие не найдется - имхо, хотелка становится бесполезной и ее можно будет закрыть. Ведь за то же время оно отрабатывается и простым батником с кучей натравлений штатного жпегтрана на имеющийся кэш, без потерь человеко\часов на САСовый кодинг...

>Ща приаттачу тестовую утиль
Вечерком поюзаю. Отпишусь.
(0006466)
zed (manager)
23-04-2012 12:04

>Есть ли возможность изменить логику работы всего алгоритма
Есть пара мыслей. Попробую.
(0007474)
Parasite (administrator)
19-06-2012 03:14

>>Есть ли возможность изменить логику работы всего алгоритма
>Есть пара мыслей. Попробую.
Есть ли новости?
(0007478)
zed (manager)
19-06-2012 04:49

Нет. Видимо быстрее не получится.
(0007479)
Parasite (administrator)
19-06-2012 05:05

>Нет. Видимо быстрее не получится.
Та не к спеху, собссно. Не тороплю.
Просто roadmap дай, хоть приблизительно? Месяц - знач месяц, три - знач три...
(0007480)
vdemidov (manager)
19-06-2012 05:09

Попробуй клеить сначала полоски с размерами кратными 8 пикселам в отдельный файл. А потом уже собирать эти полоски в большой файл. То есть, склеил полоску - добавил в результирующий файл и тд.
Должно быть гораздо быстрее за счет отсутствия рендомных записей на винчестер.
(0007482)
zed (manager)
19-06-2012 05:23

>roadmap дай
В том виде, как оно есть сейчас, сделать склейку можно за день максимум. Но не оптимизированный вариант, я так понимаю, нафиг кому нужен.

>Должно быть гораздо быстрее за счет отсутствия рендомных записей на винчестер
Там не из-за винта тормоза, а из-за необходимости распаковки/запаковки жпега (служебных данных) для записи/чтени каждого тайла. К слову, если загрузить жпег целиком в память (исключив винт, как возможное узкое место) и делать из него безпотерьный crop, то всё происходит так же медленно и печально.
(0007489)
vdemidov (manager)
19-06-2012 05:54

Ну если в памяти все так же медленно и печально, то можно закрывать.
>22k*22k = ~ 7 часов (~ 3 сек. на тайл).
Это явный перебор.
(0007491)
zed (manager)
19-06-2012 06:00

Перебор, но с другой стороны, фича полезная и найдёт своих маньяков. А в TODO записать: Оптимизировать losslless, и год поставить - 2020 :)
(0007497)
vdemidov (manager)
19-06-2012 06:08

Ну, как хотите :) Для меня фитча бесполезная. Я склейку использую, только если мне нужно кусок карты распечатать и отдать знакомым.
(0007499)
Parasite (administrator)
19-06-2012 06:30

А что если просто вклеивать body от каждого тайла напрямик в результирующее большое полотно, и потом к нему генерить хидер? То есть, вообще ничего никуда не перепаковывать, а обойтись сугубо файловыми операциями?

Насколько я помню, боди от жпега вполне себе самостоятельно и может быть распаковано даже со сторонним хидером (более того - некоторые карторесурсы отдают вообще только боди, а хидер там одинаков для всех и приклеивается "на ходу" уже на стороне клиента). Грубо говоря, взять два смежных jpeg-тайла, оторвать у обоих хидеры, два боди вклеить в результирующий файл, и приписать ему зановосгенеренный хидер уже согласно новой картинки. По идее - должно открыться как родное, и будет вполне себе lossless....
(0007515)
Dima2000 (developer)
19-06-2012 07:48

Я хотел такой способ предложить ещё с самого начала, не перепаковывать тайлы и вообще не использовать библиотеки работы с jpeg, а юзать чисто пересылки бинарных данных. Но посмотрев внимательнее на формат jpeg-а, образумился: очень похоже что какие-то таблицы, жизненно важные для распаковки, сидят в хидере и в общем случае могут быть разными для разных файлов. Конкретно, кажется таблица квантования такова. Когда jpeg файлы создаём сами, то всегда можем выбрать одинаковую таблицу для всех файлов, но вот если файлы получены из сторонних источников, это может и не соблюдаться. А тогда лишь перепаковка.
Хотя, никто не мешает один раз выбрать таблицу для всего большого jpeg-а и потом перепаковывать тайлы по ней, дозаписывая в результирующий файл уже готовое body (но перепакованое!). От перепаковки не уйти, lossless не будет, но хотя бы памяти не надо будет очень много и винту легче. А, немного не прав, если выходной формат lossless, то будет вполне себе безпотерьно, лишь от распаковки тайлов не уйти.
(0007522)
Parasite (administrator)
19-06-2012 09:42

>Конкретно, кажется таблица квантования такова. Когда jpeg файлы создаём сами, то всегда можем выбрать одинаковую таблицу для всех файлов, но вот если файлы получены из сторонних источников, это может и не соблюдаться. А тогда лишь перепаковка.
Угу. Но, как правило, картосервисы в пределах себя любимых таким не балуются (да и на тайлы обычно нарезается автоматом - с одинаковыми настройками к каждому тайлу) - и все тайлы отличаются только боди, а остальное стандартно. Как правило - но возможно есть и исключения. Теоретически можно в процессе склейки проверять каждый тайл на это дело, и по нахождении расхождений - либо еррорить, либо переквантовать этот злобный тайл к общей картинке и далее вклеивать как обычно.

Либо же перепахивать сорцы жпегтрана, отучая его перепаковывать все полотно после к.тайла - а делать это только раз, в финале...

Либо же вообще забить на этот тикет и сосредоточиться на заведомо более вкусном lossless\limitless сведении в RAW, предоставив хомякам потом делать с ним что угодно но уже своими силами. Но там свои тараканы (например куча дисковых операций + место на винте будет весьма сильно уходить, со жпегом ни в какие ворота не сравнится).
(0013342)
Globbus (reporter)
27-11-2013 14:36

Тема заглохла? Уж больно печально, когда 9 кБ тайл z19 в склейке раздувается в 30 кБайт при худшем визуальном качестве (даже на 100%). Думаю, даже многочасовая обработка не являлась бы помехой - можно запускать на ночь.
(0013344)
zed (manager)
28-11-2013 06:00

Ну, вы как бы первый, кто проявил заинтересованность в фиче, не смотря на её недостатки.
(0013414)
Globbus (reporter)
12-12-2013 09:48

Думаю ситуация здесь, как и в ряде других случаев в жизни - "а парни-то и не знают..."

Не знают о багтрекере, не знают lossless-операциях с jpeg.

- Users who viewed this issue
User List Anonymous (4866x), mrjack (2x), Parasite (1x), ingener (1x), k-dmitriy (3x)
Total Views 4873
Last View 28-03-2024 22:23

- Issue History
Date Modified Username Field Change
06-03-2012 15:15 Parasite New Issue
06-03-2012 15:18 Parasite Additional Information Updated View Revisions
06-03-2012 15:19 zed Note Added: 0005866
06-03-2012 15:22 zed Note Edited: 0005866 View Revisions
06-03-2012 15:25 zed Note Edited: 0005866 View Revisions
06-03-2012 15:29 vdemidov Status new => resolved
06-03-2012 15:29 vdemidov Resolution open => no change required
06-03-2012 15:29 vdemidov Assigned To => vdemidov
06-03-2012 15:29 vdemidov Status resolved => closed
06-03-2012 15:40 Parasite Note Added: 0005867
06-03-2012 15:40 Parasite Status closed => feedback
06-03-2012 15:40 Parasite Resolution no change required => reopened
06-03-2012 15:41 Parasite Note Edited: 0005867 View Revisions
07-03-2012 04:34 Tolik Note Added: 0005880
07-03-2012 04:35 Tolik Status feedback => resolved
07-03-2012 04:35 Tolik Fixed in Version => 120808
07-03-2012 04:35 Tolik Resolution reopened => fixed
07-03-2012 04:35 Tolik Assigned To vdemidov => zed
07-03-2012 04:36 Tolik Note Edited: 0005880 View Revisions
07-03-2012 04:38 Tolik Product Version .Nightly => 110418
07-03-2012 04:38 Tolik Target Version => 120808
07-03-2012 04:39 Tolik Note Edited: 0005880 View Revisions
07-03-2012 05:03 zed Note Added: 0005882
07-03-2012 05:23 Tolik Note Added: 0005887
07-03-2012 05:29 zed Note Added: 0005888
07-03-2012 07:10 Tolik Assigned To zed =>
07-03-2012 07:10 Tolik Status resolved => acknowledged
07-03-2012 07:10 Tolik Resolution fixed => open
07-03-2012 07:10 Tolik Fixed in Version 120808 =>
07-03-2012 07:10 Tolik Target Version 120808 =>
07-03-2012 07:10 Tolik Summary Юзание libjpeg при сведении в JPG => Сделать lossless склейку в JPG (с использованием libjpeg)
07-03-2012 07:17 vdemidov Status acknowledged => confirmed
07-03-2012 07:18 vdemidov Target Version => 50xxxx
07-03-2012 08:39 Parasite Note Added: 0005901
07-03-2012 10:25 zed Note Added: 0005905
07-03-2012 11:08 Parasite Note Added: 0005909
07-03-2012 11:20 zed Note Added: 0005912
07-03-2012 12:30 Parasite Note Added: 0005917
08-03-2012 11:06 zed Note Added: 0005953
08-03-2012 11:39 Parasite Note Added: 0005954
13-03-2012 13:30 Parasite File Added: jpegtran.rar
13-03-2012 13:31 Parasite Note Added: 0006039
22-03-2012 08:27 gpsMax Tag Attached: склейка
22-03-2012 08:28 gpsMax Tag Attached: скрипты
11-04-2012 16:57 zed Note Added: 0006418
11-04-2012 17:47 zed Note Edited: 0006418 View Revisions
20-04-2012 09:46 Parasite Note Added: 0006441
20-04-2012 10:09 vdemidov Note Added: 0006442
20-04-2012 11:02 Parasite Note Edited: 0006441 View Revisions
20-04-2012 14:16 zed Note Added: 0006443
20-04-2012 15:34 Parasite Note Added: 0006444
20-04-2012 19:10 zed Note Added: 0006445
21-04-2012 06:23 Parasite Note Added: 0006446
23-04-2012 05:02 Parasite Note Added: 0006459
23-04-2012 06:15 zed Note Added: 0006460
23-04-2012 06:15 zed Note Edited: 0006460 View Revisions
23-04-2012 06:16 zed File Added: jtest.zip
23-04-2012 06:17 zed Note Added: 0006461
23-04-2012 06:19 zed Note Edited: 0006461 View Revisions
23-04-2012 10:21 Parasite Note Added: 0006465
23-04-2012 12:04 zed Note Added: 0006466
19-06-2012 03:14 Parasite Note Added: 0007474
19-06-2012 04:49 zed Note Added: 0007478
19-06-2012 05:05 Parasite Note Added: 0007479
19-06-2012 05:09 vdemidov Note Added: 0007480
19-06-2012 05:23 zed Note Added: 0007482
19-06-2012 05:54 vdemidov Note Added: 0007489
19-06-2012 06:00 zed Note Added: 0007491
19-06-2012 06:08 vdemidov Note Added: 0007497
19-06-2012 06:30 Parasite Note Added: 0007499
19-06-2012 07:48 Dima2000 Note Added: 0007515
19-06-2012 09:42 Parasite Note Added: 0007522
27-11-2013 14:36 Globbus Note Added: 0013342
28-11-2013 06:00 zed Note Added: 0013344
12-12-2013 09:48 Globbus Note Added: 0013414
13-10-2015 08:20 vdemidov Target Version 50xxxx => 30xxxx.Vip



Copyright © 2007 - 2024 SAS.Planet Team